Spotify 的年度总结我连续看了好几年,但说实话,除了那张能发朋友圈的卡片,我几乎记不住自己过去一年到底在听什么。去年冬天我花了半小时把 Spotify 的 JSON 下载包翻出来,用 Python 从头到尾分析了一遍自己的听歌数据,这次体验比年度总结带来的冲击大得多。你会在那些 StreamingHistory 文件里看到真实但不怎么体面的事实:某首歌反复循环了四十遍,某位艺术家出现了 500 多次,某些深夜时段你连续切歌半小时。这些细节原本被埋在一个庞大的 JSON 文件里,但当你用 Python 把它解析成 DataFrame,一切都会变得非常具体。这篇文章我会带你走一遍完整流程,从向 Spotify 申请数据、清洗 JSON 文件,到算出你真正关心的指标,最后把结果画成图表。适合有一定 Python 基础但对文件处理不熟悉的读者,也适合只学过 pandas 基础、想找个真实项目练手的人。
1. 为什么值得自己下载数据,而不是只靠年度总结
很多人觉得 Spotify 年度总结已经够用了,但年度总结本质上是被加工的榜单,它只会告诉你最受欢迎的歌手、最常听的歌、总收听时长这类高光指标。它不会告诉你,你每天早上通勤时反复听的是同一张专辑但从来没把它听完,不会告诉你深夜 11 点到 1 点之间的切歌率有多高,也不会告诉你某个周末你突然从电子音乐跳到钢琴纯音乐。这些细节只有原始收听记录里才有。
自己下载数据分析还有一个直接的价值,就是你可以把数据变成自己的知识库,而不是等平台每年年底给你一个浓缩版本。数据在你手里,意味着你可以用任意维度去切。你想按小时看、按星期几看、按季节看,还是按“和谁一起听”看,都不依赖平台有没有这个功能。对学数据分析的人来说,这也是一个难得的、不算干净但非常真实的练手数据集。它包含时间字段、文本字段、数值字段,还有一些缺失值和脏数据情况,和网上那些整理好的 csv 完全不同,训练的是你面对真实数据时的判断力。
在线上的数据分析项目中,我们经常抱怨找不到感兴趣的数据集,Spotify 这个下载包却是少见的、高时效性的、跟个人兴趣高度相关的数据源。对于一个学 Python 的人来说,下载数据、读取 JSON、清洗、分组聚合、可视化这一整套流程,就是一个微小但完整的商业分析项目。它能让你反复练到 pandas 的日期处理和分组聚合,又不会像“泰坦尼克号生存预测”那样无聊。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 第一步不是写代码,而是去拿到那份 JSON 数据
2.1 申请导出:别以为点了按钮马上就能下载
进入 Spotify 的账户页面,找到“隐私设置”相关入口,里面通常有一个申请下载个人数据的选项。提交申请之后,平台不会立刻给你文件,一般要等几天,有时候甚至要一周。平台的界面和菜单文字在不同地区可能有变化,你找“Download your data”或者“Request data”这类关键词就行。
比较关键的一点是,申请下载时要看清楚页面上描述了数据范围。不同时候政策不一样,有人导出的包只包含一年的数据,有人能覆盖更长历史。我第一次申请时没有注意,后来打开文件夹才发现只是最近 12 个月的一部分,很多更早的记录并没有包含进去。所以你在做时间趋势分析前,最好先确认导出数据里最早的记录是哪一天,再判断自己能分析的时间范围,不然画出来的趋势图会让人误以为你只听了几个月歌。
收到邮件通知后,下载的文件一般是个 zip 压缩包,解压后会得到一个包含多个 JSON 子文件的文件夹。这个文件夹里不仅有收听历史,还有曲库、播放列表、偏好设置、账户信息等多类数据。我们要用的主要是 StreamingHistory 或 Streaming_History 相关的文件。
2.2 StreamingHistory 里每个字段的含义
用文本编辑器打开一个 StreamingHistory JSON 文件,你会看到类似这样的结构:
json复制[
{
"endTime": "2024-06-15 08:22",
"artistName": "某歌手",
"trackName": "某首歌",
"msPlayed": 215000
}
]
字段含义非常直白:
| 字段 | 含义 | 常见注意事项 |
|---|---|---|
| endTime | 这首歌停止播放的时间点 | 没有时区信息,而且它记录的是“结束时间”而不是“开始时间” |
| artistName | 歌手名 | 同一个歌手可能会因不同地区、不同发行版本而出现多种写法 |
| trackName | 歌曲名 | 可能有同名歌曲,单靠它无法唯一定位一首歌 |
| msPlayed | 本次播放持续的毫秒数 | 这是你判断“是否听完整首歌”或“是否跳过”的核心字段 |
新版导出文件有时会包含 more 字段,例如 spotify_track_uri、episode_name、episode_uri 等,如果文件包含这类字段,说明它给每一首歌提供了 Spotify 的 URI。这个 URI 对后续对接 API 很重要,如果导出版本没有它,你后续想获取每首歌的音频特征时就会麻烦一些。
2.3 如果你的文件命名我预想的不太一样
网络上的教程大多直接写 StreamingHistory0.json,但我发现实际下载的文件命名并非完全固定。老版本的导出可能是 StreamingHistory0.json、StreamingHistory1.json,新版本可能用 Streaming_History_Audio_2024_0.json 这种按年份分组命名。我在分析时看到的情况是,一种导出格式是按日期区间拆到多个文件,另一种是把音频历史和播客历史分开。
所以建议你不要把文件名写死在代码里。最稳妥的方式是先把解压后的文件夹结构弄清楚,再决定从哪里读文件。我的做法是先拿到整个 MyData 根目录下所有 JSON 文件列表,然后只筛选出文件名中带 “Streaming” 且内容是播放历史的文件。这样无论文件是叫 StreamingHistory0 还是 Streaming_History_Audio_2024_0,都能被统一捕捉到。后续的年份、批次拆分会由文件名反映出来,但解析逻辑不用变。
3. 把零散的 JSON 加载到 DataFrame
3.1 用 pathlib 读取多个文件,而不要只盯着一个文件
当你
