
每年的Spotify Wrapped都是社交平台上的固定节目大家乐此不疲地晒出自己的年度歌单、TOP歌手、播放时长。但说实话那个报告一年只出一次能看的维度也就固定几样满足不了真正的数据控。作为一个天天写Python的人我早就想绕过Wrapped的限制随时随地把自己的听歌数据拿下来想怎么分析就怎么分析。这篇文章就完整讲讲我折腾这件事的整个过程从Spotify开发者权限申请、Python环境准备到OAuth授权、数据拉取、清洗和分析可视化所有代码都是跑通验证过的你可以直接照抄改改就能用。1. 项目拆解分析Spotify数据到底能玩出什么花1.1 这个项目能帮你搞清楚什么问题先说个最直接的问题你觉得你了解自己的听歌偏好但数据会告诉你完全不一样的答案。比如我拉完数据之后发现自己循环播放最多的歌手根本不是我最常手动点开的那位而是那些我顺手点了下一首就从没关掉的歌。这就是数据分析的魅力把你的印象和直觉全部推翻用数据重构一个更真实的行为画像。具体来说这个项目能帮你回答这么几类问题你最近一个月、一周、甚至一天的听歌行为有什么规律是通勤时段听得多还是深夜凌晨才是你的音乐主场你常听的歌曲有哪些共同特征是BPM速度普遍偏快还是能量值普遍偏低你的音乐口味是不是真的杂食用聚类算法一跑会发现你的喜好其实就集中在两三类风格上。你循环最多的歌手和歌曲和你的主观认知一致吗这些问题有了数据支撑之后你对自己音乐偏好的理解会上升一个维度。而且说实话整个过程本身就是一次很好的Python实践从API调用、JSON解析到pandas清洗、可视化覆盖了数据分析的完整流程。1.2 技术选型为什么这么定选型这件事我在动手前认真纠结过一轮。当时摆在我面前的有两条路一用现成的Spotify数据导出工具比如一些第三方网站能帮你导出播放记录。优点是省事缺点是数据格式受限很多字段拿不到而且把个人听歌数据交给第三方隐私上总觉得不太放心。二直接用Spotify官方Web API。优点是可以拿到完整数据包括艺术家、专辑、音轨ID、播放时间戳甚至每首歌的音频特征情绪值、能量值、舞蹈性、BPM等这些信息是官方导出的数据里没有的。缺点是需要自己处理OAuth认证技术上麻烦一点。我最后选了官方API理由很朴素既然要玩数据分析就要拿到最原始、最丰富的字段。而且官方API的音频特征分析功能是Spotify独家的其他平台根本拿不到这种级别的数据。这几年我用下来说实话这条路是最值得的虽然初期配置麻烦点但后面所有分析维度都是建立在这些丰富数据上的。2. 拿到钥匙注册API与搭建环境2.1 在Spotify开发者后台创建应用这一步是整个项目的起点也是很多新手第一次卡住的地方。你需要先有一个Spotify账号免费的就行然后访问Spotify for Developers仪表盘登录后点击Create App创建一个新应用。创建应用时它会让你填两项信息App name应用名称随便取比如 My Music AnalyzerApp description应用描述简单写一句就行创建完成之后你会进入应用管理页面这里有几个关键信息你需要记下来字段说明存放位置Client ID一串32位长度的十六进制字符串相当于你的应用ID应用主页面上就能看到Client Secret一串64位长度的字符串相当于你的应用密钥需要点Show Client Secret才能看到这两个值就是你的API钥匙整篇文章后面所有的接口调用都要用到它们。2.2 配置Redirect URI创建好应用之后还有一步关键配置设置Redirect URI重定向地址。这是OAuth认证流程里的一个环节Spotify会在用户授权完成后把授权码发送到你设置的这个地址上。在应用设置页面找到Redirect URIs那一栏点击Add添加地址。本地开发的话我建议直接填http://localhost:8888/callback之所以用8888端口只是我习惯了你用其他端口完全没问题关键是你填的地址必须和你代码里写的完全一致一个字符都不能差。我在这一步被坑过当时填了localhost和127.0.0.1两个看起来差不多的地址结果代码里用127.0.0.1Dashboard上填的是localhost授权的时候直接报错排查了好久才发现是地址不匹配。2.3 本地Python环境准备接下来是Python环境。我强烈建议你用虚拟环境别把依赖直接装到系统Python里不然过段时间环境一乱你都不知道是哪个包在打架。创建虚拟环境并激活python3 -m venv spotify_env source spotify_env/bin/activate # macOS/Linux # Windows下是 spotify_env\Scripts\activate然后安装依赖。这个项目的核心依赖其实不多pip install pandas matplotlib seaborn requests scikit-learn如果你打算用现成的Spotify封装库再加一个pip install spotipyspotipy是社区维护的Spotify API Python封装库把OAuth流程封装得很简洁能省去不少样板代码。但我个人建议第一遍跑的时候自己用requests手写一遍OAuth把流程彻底搞懂之后再用spotipy就很快了。这就像你先学手动挡再开自动挡以后遇到问题不会两眼一抹黑。3. 核心实现手把手把数据抓下来3.1 自己实现OAuth授权流程Spotify的OAuth授权流程我们按步骤拆开来看。第一步引导用户打开授权页面。用户访问你构造的这个URL看到Spotify的授权确认页点同意之后Spotify会把用户引导到你在Dashboard里配置的Redirect URI并在地址后面带上一个?codexxxx参数。import requests import webbrowser import urllib.parse CLIENT_ID 你的CLIENT_ID REDIRECT_URI http://localhost:8888/callback SCOPE user-read-recently-played user-top-read params { client_id: CLIENT_ID, response_type: code, redirect_uri: REDIRECT_URI, scope: SCOPE, } url https://accounts.spotify.com/authorize? urllib.parse.urlencode(params) webbrowser.open(url) # 自动打开浏览器让用户授权第二步启动本地服务器接收回调。这里需要一个本地HTTP服务来监听Redirect URI。我用Python内置的http.server模块就能搞定不用额外装框架。核心逻辑是启动一个临时web服务器监听在8888端口一旦收到回调请求就从中取出code参数然后立刻关闭服务器。from http.server import HTTPServer, BaseHTTPRequestHandler class CallbackHandler(BaseHTTPRequestHandler): code None def do_GET(self): # 从URL中提取code参数 parsed urllib.parse.urlparse(self.path) query urllib.parse.parse_qs(parsed.query) if code in query: CallbackHandler.code query[code][0] print(已捕获到授权码:, CallbackHandler.code[:20] ...) self.send_response(200) self.send_header(Content-Type, text/html; charsetutf-8) self.end_headers() self.wfile.write(授权成功可以关闭本页面。.encode(utf-8)) else: self.send_response(400) self.end_headers() # 关闭服务器 server.shutdown() server HTTPServer((localhost, 8888), CallbackHandler) server.handle_request() # 只处理一次请求就退出第三步用授权码换取Access Token。这个POST请求是OAuth流程中最关键的一次交换。auth_url https://accounts.spotify.com/api/token payload { grant_type: authorization_code, code: CallbackHandler.code, redirect_uri: REDIRECT_URI, client_id: CLIENT_ID, client_secret: CLIENT_SECRET, } resp requests.post(auth_url, datapayload) token_data resp.json() access_token token_data[access_token] refresh_token token_data[refresh_token] expires_in token_data[expires_in] # 通常为3600秒这里拿到的access_token有效期只有1小时。好在Spotify支持refresh token过期之后我们可以用它重新换一个新的access_token不用再让用户重复授权。3.2 拉取最近播放记录拿到的access_token之后就可以通过Authorization Header调用Spotify的API了。获取最近播放记录的接口长这样headers { Authorization: fBearer {access_token} } # 获取最近播放的50条记录 url https://api.spotify.com/v1/me/player/recently-played?limit50 resp requests.get(url, headersheaders) data resp.json()这个API有一个限制最多只能返回最近50条播放记录而且时间范围大概是最近30天左右。想要更长的历史数据单靠这个接口是不行的需要定时定期抓取积累或者用Spotify官方的账号数据导出功能那个才包含完整的播放历史文件。拿到数据之后关键是提取出有用的字段。每条播放记录长这样我做了一些字段省略items data[items] records [] for item in items: track item[track] played_at item[played_at] artist_name track[artists][0][name] track_name track[name] track_id track[id] duration_ms track[duration_ms] popularity track[popularity] records.append({ track_id: track_id, track_name: track_name, artist_name: artist_name, played_at: played_at, duration_ms: duration_ms, popularity: popularity, })3.3 批量获取每首歌的音频特征这是我觉得整个项目里最有技术含量的一步。Spotify的API能返回每首歌的音频特征数据包括danceability舞蹈性0.0到1.0值越高越适合跳舞energy能量0.0到1.0值越高表示歌曲越激烈、越热闹valence情感积极程度0.0到1.0值越高表示越欢快、越积极tempoBPM每分钟节拍数acousticness原声程度值越高表示越接近纯原声乐器instrumentalness器乐程度值越高表示越接近纯音乐这三个指标组合起来基本就能勾勒出一首歌的听觉气质。我拿到播放记录中的track_id之后用/v1/audio-features/{track_id}这个接口逐首歌去查询def get_audio_features(track_id, headers): url fhttps://api.spotify.com/v1/audio-features/{track_id} resp requests.get(url, headersheaders) if resp.status_code 200: feat resp.json() return { track_id: track_id, danceability: feat[danceability], energy: feat[energy], valence: feat[valence], tempo: feat[tempo], acousticness: feat[acousticness], instrumentalness: feat[instrumentalness], } else: return None如果你一次性要拉的歌曲数量很多建议每首歌之间加一个短暂延时后面我会专门讲限流的问题。我当时拉100首歌大概花了一分多钟完全可接受。4. 深度分析让数据替你说话4.1 数据处理与热门歌手排行拿到原始播放记录之后先把数据整理成DataFrameimport pandas as pd df pd.DataFrame(records) df[played_at] pd.to_datetime(df[played_at]) df[hour] df[played_at].dt.hour df[weekday] df[played_at].dt.day_name() print(df.head())接着算谁是你真正的本命歌手。这里我用两个维度播放次数和累计播放时长。# 歌手播放次数排行 top_artists_by_count df[artist_name].value_counts().head(10) # 歌手累计播放时长排行 df[duration_min] df[duration_ms] / 60000 top_artists_by_duration df.groupby(artist_name)[duration_min].sum().sort_values(ascendingFalse).head(10)有意思的是这两个排行结果往往不一样。有些歌手会频繁出现在你的播放历史里但每首歌都听不完有些歌手虽然出现的次数少但每次都能让你循环很久。4.2 你的听歌习惯隐藏规律接下来看时间维度的分析。我分析了自己每天的听歌时段分布用pivot_table做了个二维表hour_weekday pd.crosstab(df[weekday], df[hour])再画成热力图之后规律的显现特别直观。我自己最明显的发现是工作日早上8-9点和晚上18-19点各有一个明显的播放高峰这是通勤时段晚上的高峰一直到凌晨1点才消停。周末的分布则是另一番景象下午两点开始持续走高深夜比工作日还要活跃说明周末基本是夜猫子模式。这种听歌生物学作息表你用肉眼是感知不到的只有把时间戳拆开细细看才能发现自己的音乐生活节奏原来如此规律。4.3 音频特征你的口味画像把播放记录和音频特征数据合并之后就可以画出你的音乐口味画像了。我画了一张按曲目分布的箱线图merged df.merge(features_df, ontrack_id, howinner) fig, axes plt.subplots(1, 3, figsize(12, 4)) metrics [energy, valence, danceability] for ax, metric in zip(axes, metrics): merged[metric].plot(kindbox, axax) ax.set_title(metric) plt.tight_layout() plt.show()看这三个指标的中位数能快速判断你的总体口味是偏向高能量快节奏的鸡血型还是偏向低能量慢节奏的解压型。我当时测出来的结果很惊人energy的中位数是0.68valence的中位数是0.42说明我虽然平时听起来好像很爱嗨歌实际上整体偏好是节奏感强但情绪不算特别高昂的歌曲。4.4 用聚类算法看你的音乐世界分几派接下来这步是我最喜欢的一步用KMeans聚类算法把所有歌曲按音频特征分成若干类别看看你的音乐世界到底分几派。from sklearn.preprocessing import StandardScaler from sklearn.cluster import KMeans features merged[[energy, valence, danceability, tempo, acousticness]] scaler StandardScaler() features_scaled scaler.fit_transform(features) # 用肘部法则大致确定K值 inertias [] for k in range(2, 9): km KMeans(n_clustersk, random_state42, n_init10) km.fit(features_scaled) inertias.append(km.inertia_) # 我试下来K3效果最好能明显区分出三类歌曲 km KMeans(n_clusters3, random_state42, n_init10) merged[cluster] km.fit_predict(features_scaled)聚类结果出来之后我给它定义了三个类型分派Cluster 0高能量、高舞蹈性、高情绪值这是跑步时候听的歌Cluster 1高原声、低能量、低节奏这是深夜安眠曲Cluster 2各项指标都在中间属于日常循环不腻的歌用散点图可视化聚类效果plt.figure(figsize(8, 6)) scatter plt.scatter(merged[energy], merged[valence], cmerged[cluster], cmapSet2, alpha0.7) plt.xlabel(energy) plt.ylabel(valence) plt.title(Songs colored by cluster) plt.colorbar(scatter) plt.show()你会发现散点图上的分界特别清楚三个类别几乎没有交叉重叠。这说明Spotify的音频特征算法对歌曲气质的刻画是非常有效的也说明我的听歌偏好虽然看起来杂实际上暗含清晰的流派偏好。5. 踩坑记录这些问题我全遇到过5.1 OAuth授权过程中的各种报错这是整个项目里新手最频繁卡住的地方我列几个我遇到过的报错 invalid_clientClient ID或Client Secret填错了。去Dashboard重新复制一遍注意别多复制了空格。推荐的做法是存在环境变量里别硬编码在代码中export SPOTIPY_CLIENT_ID你的ID export SPOTIPY_CLIENT_SECRET你的密钥报错 redirect_uri_mismatch代码里的Redirect URI和Dashboard里配置的完全不一致。注意大小写、端口、路径都要一字节不差。授权页面能打开但回调不触发检查一下回调地址的端口是否被占用以及是不是被防火墙拦了。本地起的服务器改用127.0.0.1而不是localhost试试有时候系统DNS解析会出幺蛾子。5.2 API限流问题Spotify API的限流策略是每30秒最多发多少请求超出后会返回HTTP 429状态码响应头里会有Retry-After字段告诉你需要等待多少秒。之前我一次性拉100首歌用了个简单循环结果每拉几首就被限流了。后来我在代码里加了个简单的延时配合退避机制import time def get_features_with_retry(track_id, headers, max_retries3): for i in range(max_retries): resp requests.get(fhttps://api.spotify.com/v1/audio-features/{track_id}, headersheaders) if resp.status_code 200: return resp.json() elif resp.status_code 429: retry_after int(resp.headers.get(Retry-After, 1)) time.sleep(retry_after 1) else: time.sleep(0.5) return None加了重试逻辑之后整个流程稳了很多。5.3 数据遗漏与偏差这里有个特别重要的认知recently-played这个API只能返回最近50条记录所以如果你的听歌量比较大这个数据代表的是你最近几小时的偏好不是你长期的完整画像。想拉更长时间跨度的数据有几个备选思路写个定时任务每半小时拉一次数据存进SQLite积累几周之后数据量就很可观了用Spotify官方的扩展播放历史导出Privacy Settings里申请导出数据Spotify会以邮件方式发给你CSV/JSON文件用/v1/me/top/artists和/v1/me/top/tracks接口获取长期Top统计这个接口有time_range参数支持short_term约1个月、medium_term约6个月、long_term数年三个范围顺便提醒一个细节那个长期Top接口的输出是按Spotify算法加权过的不是简单的播放次数累加所以结果和你自己统计的播放次数TOP可能会不太一样两边交叉对比着看才好玩。注意你的数据只跑在本地不要把它传到任何第三方平台上。音乐口味虽然是小事但数据隐私的习惯要从每一件小事养起。写在末尾最后分享几个我在实际操作之后的体会。第一分析自己听的歌这件事最大的乐趣反而不是那些图表多好看而是你终于能跳出来用第三人称视角观察自己的行为习惯。我以前一直以为自己听歌很杂结果聚类跑完发现其实就两大派还发现工作日中午12点的听歌量几乎为零可能是那时候都在开会吧。第二这个项目是很典型的一鱼多吃拿自己的数据练手不会觉得枯燥学习的效率远高于看一百遍教程。爬数据练的是requests和API调试分析练的是pandas和数据分析思维可视化练的是matplotlib/seaborn聚类练的是sklearn。一个项目把Python数据分析的整个链路都过了一遍。如果你想继续扩展这个项目我提供几个思路一是把长期定时采集的数据积累到数据库里做一个真正意义上的个人听歌年鉴二是把音频特征和情绪标签关联起来训练一个分类模型分析未来听的新歌更接近你的哪种偏好。这些方向都不难但玩起来会越来越有意思。数据这个东西积少成多之后总会给你惊喜的。