你有没有试过,在年终总结的时候点开Spotify的年度回顾,看着那几个“你最爱的艺人”“你循环最多的歌”发愣?它给的榜单确实好看,但我更在意的是那些它没告诉我的东西:我到底一年听了多少小时的音乐?我是不是总在星期一的深夜emo?我的听歌口味真的和我自己以为的一样吗?想要回答这些问题,最直接的办法就是自己动手把数据拉出来做分析,而Python加Spotify的官方API就是一套非常趁手的工具组合。
这篇文章是我实际折腾了一整天的完整记录。我会从零开始,讲清楚怎么注册Spotify开发者应用、怎么用Python拿到你账号的播放记录、再到怎么从时间、风格、音频特征几个维度拆解你的收听习惯,最后会把数据画成图。整个过程完全基于Spotify官方API,不需要爬虫,不需要逆向协议,干净合法。适合对Python数据分析刚入门、又恰好有Spotify账号的朋友,也适合那些单纯好奇“大数据眼里的我到底在听什么”的人。如果你对音频特征、推荐算法、个人数据可视化这些话题感兴趣,这篇的很多思路也可以直接迁移过去用。
1. 项目拆解与方案选型
1.1 这个项目到底能拿到哪些数据
开始写代码之前,先把底摸清楚。Spotify官方API针对用户数据开放了两个我这次会用到的核心接口:一个是最近播放记录接口,返回你过去一段时间听过的歌曲、艺人、专辑、播放时间点;另一个是用户个人排行榜接口,返回你的Top艺人和Top歌曲。前者能还原你的收听时间线,后者能告诉你最钟爱的口味是什么。更进一步,每首歌都能通过音频特征接口拿到一组描述曲目“物理属性”的数字,比如跳舞性、能量值、情绪值、节奏速度,这些结合起来就能做很多有趣的分析。
需要注意一点:Spotify API返回的数据是有保留时间窗口的。最近播放记录最多能往回看大概90天,而且单次请求最多返回50条。如果你想分析自己一整年的收听轨迹,光靠这个接口还不够,必须定期采集并保存到本地。我这次分享的方案会同时覆盖“一次性拉取最近数据”和“用定时任务持续累积数据”两种思路,你可以根据自己的需要选择。
1.2 技术选型:为什么是Spotipy而不是手写HTTP请求
老实说,Spotify的API本身设计得挺规整,直接用requests手动调用也不是不行,但里面最麻烦的是OAuth授权流程。你需要自己处理回调地址、token刷新、scope权限拼装,这些代码写起来烦,而且很容易在细节上出错。Spotipy这个第三方库把这一切都封装好了,安装一条命令,初始化之后就能直接调用接口,代码量至少省掉一大半。
我知道有些朋友对第三方库有顾虑,担心封装太深出问题不好排查。但Spotipy在Python社区里是Spotify API事实上的标准客户端,维护非常活跃,文档也全,用它的风险远低于自己手搓。我做这个项目的时候也对比过直接requests调API和用Spotipy的代码复杂度,最终确定用Spotipy,把精力放在数据和可视化上,而不是跟鉴权机制较劲。
1.3 你最终会得到一份什么样的输出
我做完这套分析之后,得到了这样几个东西:一张按天和按小时统计的播放热力图,能看出自己哪天听得最多、哪个时段是肾上腺素时刻;一张艺人播放时长榜,能看出到底哪个乐队真正占据了我耳朵最大的份额;一张歌曲音频特征的雷达图,能看出我最近偏好的歌曲在能量、舞动性、情绪这些维度上的平均值;还有一份整理好的CSV文件,把每一首歌、播放时间、艺人、音频特征都存了下来。整个过程不复杂,但出来的结果确实让我重新审视了自己的听歌习惯,原来我在深夜和洗澡时听的完全是两类风格。
2. 环境准备与Spotify开发者配置
2.1 Python环境与依赖安装
这个项目对Python版本没有特殊要求,3.8以上都没问题。如果你是从零开始,我建议直接用官方安装包装最新稳定版,安装的时候记得勾选“Add Python to PATH”。我自己用的是3.11,跑这套代码完全没遇到兼容性问题。
依赖方面只需要三个库:Spotipy负责和API通信,pandas负责数据处理,matplotlib负责画图。如果还想做交互式图表,可以额外装一个plotly。安装命令如下:
pip install spotipy pandas matplotlib国内用户如果下载速度慢,可以在pip命令后面加-i https://pypi.tuna.tsinghua.edu.cn/simple指定镜像源。这三个库都很常见,装完不需要额外配置什么。
2.2 注册开发者应用,拿到密钥
想要调用Spotify API,必须去开发者后台创建一个应用。打开Spotify Developer Dashboard,登录你的账号,点“Create App”,填一个应用名字和描述,然后会得到一个Client ID和Client Secret,这两个字符串就是你的应用凭证,相当于钥匙。
创建应用的时候有个关键步骤很多人会忽略:在应用设置里必须手动添加一个Redirect URI,也就是回调地址。这是OAuth授权流程的一部分,授权成功之后Spotify会把用户重定向到这个地址,并在URL参数里附带一个授权码。开发阶段最简单的方式是填http://localhost:8888/callback,Spotipy会在本地起一个服务来接这个回调,整个过程是全自动的。如果你没有填写这个地址,后面授权的时候会直接报错。
2.3 两种授权方式怎么选
Spotify API的OAuth授权方式有好几种,我这次用到的是Authorization Code模式,也是获取用户私人数据唯一合法的模式。这种模式拿到的token带refresh能力,有效期一个小时,过期之后可以用refresh_token自动续期,不需要用户重新授权,非常适合定时采集数据。
还有一种是Client Credentials模式,只能访问公共数据比如专辑信息、艺人资料,拿不到用户播放历史。我做了一个表格方便你区分:
| 模式 | 能拿什么数据 | 需要用户授权 | Token有效期 | 适合场景 |
|---|---|---|---|---|
| Authorization Code | 用户播放记录、Top榜单、收藏 | 需要 | 1小时,可刷新 | 私人数据分析 |
| Client Credentials | 专辑、艺人、曲目信息 | 不需要 | 1小时,不可刷新 | 公共数据查询 |
我这个项目必须用第一种。授权的时候Spotify会弹一个网页,问用户是否同意把数据授权给这个应用,点同意就行,这个步骤本质上是告诉Spotify“这个应用可以读我的听歌数据”,非常安全,不会暴露密码。
3. 数据获取实战:从API拉取你的听歌记录
3.1 初始化客户端,处理跨平台的缓存问题
代码的第一步是初始化Spotipy客户端,把Client ID、Client Secret、Redirect URI这几个参数传进去,同时声明需要哪些访问权限。Spotify的scope就是权限列表,比如“读取最近播放记录”“读取Top榜单”,你申请的scope越少,用户越放心,这是个好习惯。
我在这一步踩过一个坑,就是token缓存文件的权限问题。Spotipy默认会把token缓存信息写到当前用户目录下的配置文件里,Windows和Linux的行为不太一样,有时候会出现Permission denied的错误。我把缓存文件指定到项目目录下的本地文件,就彻底规避了这个问题。初始化代码如下:
import spotipy from spotipy.oauth2 import SpotifyOAuth SPOTIPY_CLIENT_ID = "你的Client ID" SPOTIPY_CLIENT_SECRET = "你的Client Secret" SPOTIPY_REDIRECT_URI = "http://localhost:8888/callback" scope = "user-read-recently-played user-top-read" sp = spotipy.Spotify(auth_manager=SpotifyOAuth( client_id=SPOTIPY_CLIENT_ID, client_secret=SPOTIPY_CLIENT_SECRET, redirect_uri=SPOTIPY_REDIRECT_URI, scope=scope, cache_path=".spotify_token_cache" ))第一次运行这段代码,终端里会提示你打开一个网址,复制到浏览器授权,然后Spotify会重定向到本地回调地址,把授权码交给Spotipy,之后一切就顺畅了。整个过程只需要手动操作一次,后面刷新token都是自动的。
3.2 拉取最近播放记录:时区问题与去重逻辑
最近播放记录接口是sp.current_user_recently_played(),默认返回你最近24小时内的播放记录,最多50条。但我想要的是过去90天的数据,这需要对接口的before参数做分页,每次取完一页就用最早一条的播放时间戳作为下一次请求的锚点,往前翻页。
这里有一个很坑的地方:API返回的played_at是UTC标准时间,如果你直接拿来当本地时间用,会发现所有播放记录的时间都比真实时间早了8个小时左右,你的深夜收听高峰期会平白无故跑到凌晨去。解决方法是用Python的zoneinfo模块把UTC时间转换到本地时区。
还有一个很容易忽视的点:最近播放记录在播放密集的时候会出现同一首歌短时间内反复被记录的情况,比如你单曲循环一首歌三遍,接口里就会有三条几乎相同的记录。做时间统计的时候这些数据是合理的,但做“歌曲播放次数”统计时如果你只想算听了一遍还是一直在洗脑循环,就得根据情况决定要不要去重。我通常保留所有记录,因为它们确实代表了你真实的播放行为。核心代码大致是这样:
import pandas as pd from datetime import datetime, timedelta from zoneinfo import ZoneInfo def fetch_full_history(sp, days=90): records = [] before = None end_time = datetime.now(ZoneInfo("UTC")) - timedelta(days=days) while True: batch = sp.current_user_recently_played(limit=50, before=before) if not batch["items"]: break records.extend(batch["items"]) oldest = datetime.fromisoformat(records[-1]["played_at"].replace("Z", "+00:00")) if oldest < end_time: break before = int(oldest.timestamp() * 1000) df = pd.DataFrame([{ "song": item["track"]["name"], "artist": ", ".join(a["name"] for a in item["track"]["artists"]), "album": item["track"]["album"]["name"], "played_at_local": datetime.fromisoformat( item["played_at"].replace("Z", "+00:00") ).astimezone(ZoneInfo("Asia/Shanghai")), } for item in records]) return df这段代码跑完,你会得到一个完整的DataFrame,每一行就是你的一次播放行为。我实测下来,一天不落连续听歌的人,90天大概有几千条数据,跑起来毫无压力。
3.3 获取Top榜单和音频特征
Top榜单接口比播放记录简单得多,调用sp.current_user_top_tracks()和sp.current_user_top_artists(),传入time_range参数就可以拿到不同时间范围的排名。time_range有三个值:short_term(约一个月)、medium_term(约六个月)、long_term(约一年)。我建议三个都拉一份,分别存起来,因为对比不同时间范围的口味变化本身就是很有趣的分析方向。
音频特征就更直接了,把曲目ID批量传给sp.audio_features(),每次最多传100个ID,就能拿到每首歌的12项音频特征指标。这些指标包括:跳舞性、能量、音量、语音密度、原声性、乐器性、现场感、情绪积极度、速度、调性等。拿到之后和歌曲信息合并到一个表里,后面做特征分析就方便了。我这里贴一段拉取Top歌曲并补充音频特征的代码:
def get_top_tracks_with_features(sp, time_range="medium_term"): top = sp.current_user_top_tracks(limit=50, time_range=time_range) tracks = [item["id"] for item in top["items"]] features = sp.audio_features(tracks) rows = [] for track, feat in zip(top["items"], features): if feat is None: continue rows.append({ "song": track["name"], "artist": ", ".join(a["name"] for a in track["artists"]), "danceability": feat["danceability"], "energy": feat["energy"], "valence": feat["valence"], "acousticness": feat["acousticness"], "tempo": feat["tempo"], "duration_ms": track["duration_ms"], "popularity": track["popularity"], }) return pd.DataFrame(rows)如果你想把Top歌曲的完整歌词风格分类,也可以顺手调用sp.artist()拿艺人的genres字段,不过要注意,不是所有艺人都填了风格标签,有些小众艺人这个字段是空的。这个数据后面做风格统计会用到,所以我一般是把Top艺人的信息也一起拉下来。
4. 数据分析:从时间、风格、音频特征三个维度看你的口味
4.1 时间维度:找出你的“标准收听时刻”
数据拿到手后,第一件事我建议先做时间维度的分析,因为这是效果最直观、也是最能带来“啊原来我是这样的人”感觉的分析。把播放记录里的时间拆成小时、星期几、月份几个字段,然后用pandas的groupby统计不同时间段的播放次数。
我自己的结果很有意思:工作日上午10点到11点出现一个小高峰,对应我通勤和开始上班摸鱼放松的时间;晚上21点到23点是绝对的主力时段;而凌晨2点到5点播放量几乎为零,说明我的睡眠习惯还算正常。按星期几来看,周六的播放量比工作日高出一截,看来我的耳朵周末比工作日忙多了。
如果你的数据是跨了三个月的,你甚至可以看到随着季节变化听歌口味的变化。这个分析还可以再做细一点,比如按“小时+星期几”生成一个7x24的热力矩阵,能更精准地看出工作日和周末的收听结构差异。用pivot_table透视一下,再扔给seaborn画热力图,一张图就能装下所有信息。
4.2 艺术家与风格:谁才是你真正的本命
把Top榜数据和播放记录里的artist字段聚合,按播放次数排序,能快速得出你最常听的艺人排名。这个排名和Spotify给你的年度报告有些微差异,因为年度报告用的是官方算法,可能考虑了跳过率、完播率这些因素,而你自己的统计就是最朴素的出现次数,各有各的参考价值。
我观察了一下自己的艺人榜单,发现第二名其实不是我主动去听最多的,而是因为我常听的歌单里几乎每首都有他参与featuring。这就是数据的魅力,它和你的主观感受对上了一部分,又在某些地方给你出乎意料的反馈。
风格维度需要一点额外处理。艺术家的genres字段如果没有,我就用歌曲所在专辑的类型信息做近似。统计了一下我的风格分布之后,我看清了一个事实:我的播放列表里indie rock的占比远超我自己的预期,而电子音乐反而没我记忆中那么重要。这种“数据纠正记忆”的体验挺奇妙的,推荐每个做这个项目的朋友都感受一下。
4.3 音频特征:用数字描述歌曲的“性格”
音频特征分析是这个项目里最有技术含量、也最好玩的部分。这12项特征是Spotify算法直接分析音频波形得出来的,比如danceability分数越高越适合蹦迪,energy越高听起来越“炸”,valence越高听起来越阳光积极,acousticness越高越像不插电的现场。
我把自己最近听的Top 50首歌的音频特征算了个平均值,发现我偏好的歌普遍是中等能量、中等跳舞性、情绪值偏低的,这下算是在数据层面印证了我朋友说我“爱听丧歌”的评价。如果你想更认真一点,可以把这些特征按不同的时间段分组对比,比如工作日的歌和周末的歌在能量和跳舞性上有没有显著差异。我做过一次,结果工作日听的能量值明显低于周末,看来我的大脑比我诚实得多。
这里还有一个值得做的事情:用tempo也就是BPM,来分析你的收听节奏。把歌曲的BPM画成直方图,能看出你的收听偏好是偏快歌还是偏慢歌。我的分布峰值在110到130之间,属于比较典型的流行和电子音乐区间,同时也有一小撮慢歌集中在80左右,对应那些我深夜听的Ballad类歌曲。
5. 可视化:把数据变成能发朋友圈的图
5.1 用matplotlib快速出图
数据分析得再深入,如果最后只是印在终端里的一堆数字,说服力总差那么点意思。所以可视化是必须的。我这次的主力绘图库是matplotlib,虽然它默认样式丑了点,但胜在功能强、能精确控制每个细节,而且装好就能用,不用额外折腾。
先拿播放记录画一个“按小时播放次数”的柱状图,代码不超过十行,却能立刻看出你的收听习惯曲线。我做的时候顺便把x轴的标签旋转了一下,不然0点到23点这些数字挤在一起根本看不清。这在解决坐标轴标签太密集的问题上是最常用的手段,网上很多教程都没提这个细节。
import matplotlib.pyplot as plt hour_counts = df["played_at_local"].dt.hour.value_counts().sort_index() plt.figure(figsize=(12, 5)) plt.bar(hour_counts.index, hour_counts.values, color="#1DB954") plt.xticks(range(24)) plt.xlabel("Hour of day") plt.ylabel("Play count") plt.title("My Listening Activity by Hour") plt.savefig("listening_by_hour.png", dpi=150, bbox_inches="tight") plt.show()如果想画得更高级一点,可以用seaborn的热力图来做星期几和小时的二维分布,视觉冲击力会强很多。我之前一直觉得seaborn和matplotlib是两套体系,后来发现seaborn其实只是建立在matplotlib之上的高级接口,用的还是同一套坐标系,学了不亏。
5.2 艺人Top榜和音频特征雷达图
艺人的排名可以画成横向柱状图,把最长的柱子放在最上面,一眼就能看出谁是你的“音乐本命”。横向柱状图在艺人的名字特别长时比竖向柱状图友好得多,不用旋转标签也不会重叠。
音频特征雷达图是我觉得最适合发朋友圈的图。一个五边形或六边形的雷达图上,把你的歌曲平均特征画成一条闭合曲线,直观展示你在各个音质维度上的偏好偏向哪个方向。我用了matplotlib的极坐标功能来实现,其实就是在极坐标系下画一个多边形,然后填充颜色,做出来的效果非常像那些音乐App里的“音乐人格测试”。
5.3 保存数据:给未来的自己留一份档案
最后强烈建议把整理好的DataFrame保存成CSV文件。一方面,Spotify的最近播放记录有90天窗口,不保存就永远找不回来了;另一方面,存下来的数据是后续所有分析和可视化的基础,后续想玩什么新花样,直接读这个文件就行。
df.to_csv("spotify_play_history.csv", index=False, encoding="utf-8-sig")这里用utf-8-sig编码而不是默认的utf-8是有原因的:Windows上的Excel打开CSV时默认按ANSI编码解析,如果用普通UTF-8会有中文乱码问题。加一个BOM头(就是utf-8-sig干的事),Excel就能正确识别。这个细节我第一次写的时候没注意,交付文件给朋友后被吐槽了才反应过来。
6. 常见问题排查与避坑实录
6.1 鉴权失败与权限不足
跑代码最常见的报错是spotipy.exceptions.SpotifyException加上一堆401或403的HTTP状态码。401通常是Client ID或Client Secret写错了,或者token过期没有被正确刷新;403多半是scope权限问题,比如你没有申请user-read-recently-played就去调用播放历史接口。
还有一个比较隐蔽的原因,是你在开发者后台把Redirect URI写错了,或者在多个应用之间复制配置时把URI搞混了。我的建议是,遇到权限相关的报错先把异常信息完整打出来看一遍,Spotify的报错信息里通常会明确指出“仅授予了XXX权限,需要XXX权限”,照着改就行。
6.2 请求频率限制
Spotify API对请求频率有明确规定,大概是每30秒最多请求一定次数的级别,具体数字以官方文档为准。一旦超过会被返回429状态码,表示请求太频繁了。这个问题的解决方案有两个:一个是控制代码里的请求节奏,在循环里加入time.sleep等待;另一个是用官方的Retry-After头,如果收到429就等它告诉你的秒数再继续。
我在拉取音频特征的时候遇到过这个问题,因为要处理的歌曲有几百首,每次批量100个ID看起来不多,但连续请求还是容易撞上限制。后来我加了一个简单的指数退避逻辑,第一次失败等2秒,第二次等4秒,最多等30秒,从那以后就再也没被限流过。
6.3 时区和播放时间对不上
这个坑我在前面提到过,但值得再强调一次。played_at字段是UTC时间,如果你不做时区转换,分析出来的小时分布会整体偏移8个小时,那些明明发生在晚上9点的播放会被显示成凌晨5点。我在代码里用ZoneInfo("Asia/Shanghai")做的转换,你可以根据自己所在的时区来修改。
6.4 接口返回的字段比想象中复杂
有朋友可能第一次看到API响应会被吓到,items数组里三层外三层全是嵌套的字典,比如item["track"]["album"]["images"][0]["url"]这种路径。直接用这样的嵌套结构做数据分析很容易晕,我建议在第一步就把它拍平成表格,只保留自己关心的字段。我的做法是:先打印一条原始记录看看结构,确认字段路径无误后再写解析函数,避免盲猜。
7. 进阶玩法:让这套项目更值钱
7.1 定时采集搭建个人音乐档案
90天窗口是硬限制,想突破就只能在数据产生的那一刻及时采集。我现在的做法是把采集脚本用系统自带的定时任务每天晚上跑一次,把当天新增的播放记录追加到本地数据库里。这样坚持几个月后,你会拥有一个完全属于自己的历史听歌数据库,到时候做年度报告不用等Spotify发,自己生成的还能更个性化。这应该是这个项目最有长期价值的延伸方向。
7.2 结合排行榜和艺人风格做推荐分析
当你攒了几千条历史数据之后,可以做的事就更多了。比如用Top艺人的风格标签计算风格偏好权重,然后去匹配你还没听过的同类艺人;或者用音频特征数据做聚类分析,找出你播放记录里潜在的几种“收听场景”,每个聚类代表一类特定场景下的音乐偏好。我个人觉得再往深走一步,这些特征完全可以喂给推荐模型,给自己做一个私人推荐系统。
7.3 把你的分析结果导出成网页报告
matplotlib画出来的静态图虽然清晰,但总觉得少了点互动性。如果你想做个能给别人点着玩的年度报告,可以考虑把生成的结果整理成一份HTML文件,或者直接用Streamlit做一个本地网页应用,把图表、表格、统计摘要全都放进去。Streamlit的优势在于代码量极低,拖一个横条就能筛选时间段。去年年底我做了一个给朋友玩的“听歌人格测试”页面,用的数据和图表底层就是我这次分析的思路。
8. 最后想说的几句实在话
做数据分析这件事,很多时候难的其实不是写代码,而是从一堆毫无温度的数据里找到一个值得问的问题。我的体会是,用Python分析Spotify听歌数据最大的收获,不是那个漂亮的雷达图,也不是那几张柱状图,而是它让我换了一个角度去观察自己的日常。数据不会撒谎,它会温柔地戳破你“我其实什么都听”的错觉,然后告诉你,你就是规律本身。如果你也想试试和自己的生活数据对话,我建议你今天就申请一个Spotify开发者账号,跑通第一个接口,后面的事情会自然长出来。