☰
用Python分析Spotify听歌数据:从JSON解析到可视化报告
2026/10/7 11:15:04 网站建设 项目流程

前几天我在 Spotify 上翻自己的年度总结,一年下来累计播放了两万多分钟,数字看着挺唬人,可官方总结翻来覆去就那么几页:Top 艺人、Top 歌曲、总时长。至于我最常在哪个时间段单曲循环某首歌、哪个月几乎没听歌、哪张专辑被我从头到尾听了多少遍,总结里全都不会告诉你。

于是我把 Spotify 导出的原始听歌数据整个拉下来,用 Python 从零做了一次完整的个人听歌数据分析。这篇文章记录我拿到数据、解析文件、清洗数据、可视化分析的全过程,中间穿插踩过的坑。如果你也听 Spotify、也会一点 Python,完全可以照着这套流程做一份属于自己的听歌报告。用到的工具不多,一个装了 Python 的电脑加三个数据分析库就够了,后面我会把每一步的命令和代码都贴出来。

1. 为什么我决定自己分析听歌数据

1.1 官方年度总结真的不够看

相信很多人和我一样,每年年末最期待的平台内容之一就是 Spotify 的年度总结。它确实设计得很漂亮,几页卡片告诉你今年听了哪位歌手、哪首歌最上头、总播放时长有多少。但用过几年之后你会发现,它的信息粒度非常有限:没有具体到每一首歌的播放轨迹,没有时间维度上的细颗粒度分析,更不会告诉你“你每周三凌晨一点都在重复同一首老歌”。

我开始想弄清楚几个问题:我一年到底完整听完了多少首歌?哪些歌被我播放过很多次但永远排不进 Top 榜?我的听歌高峰一般在几点?工作日和周末的听歌偏好有没有明显差异?这些问题官方总结一个都回答不了,唯一的办法是拿到原始播放记录自己算。

Spotify 其实提供了一个非常完整的个人数据导出机制,所有播放历史、搜索记录、歌单、收藏内容都能以 JSON 文件的形式打包给你,拿到之后用 Python 一分析,很多东西会变得非常清晰。这篇博文就是完整记录这个过程,给同样有好奇心的朋友一份可以直接复现的攻略。

1.2 一条完整的数据分析路径

整个项目并不复杂,但流程要清晰,我把它拆成了五步:

  • 在 Spotify 账户设置里申请导出个人数据,等官方邮件发来下载链接
  • 解压后找到播放历史相关的 JSON 文件,搞清楚里面每一字段的含义
  • 用 pandas 批量读取这些 JSON,做基础的结构化整理
  • 在整理好的表上做多维统计:总时长、时间段偏好、Top 艺人、Top 歌曲
  • 用 matplotlib 和 seaborn 把结果画出来,形成一份可视化的个人听歌报告

这条路径本质上是一个很标准的数据分析小项目,数据量虽然不大(一般也就几万个播放记录),但麻雀虽小五脏俱全,从数据获取到清洗到可视化全流程都覆盖了。做完之后你会对“自己平时到底在听什么”有很直观的认识,甚至能发现一些官方年度总结完全没提示过的行为规律。

2. 申请导出数据包与文件结构解析

2.1 从隐私中心申请数据导出的具体操作

第一步不是写代码,而是把数据拿到手。Spotify 的导出申请入口藏得有点深,路径是:打开 Spotify 官网 → 登录账户 → 点击右上角头像 → Account → 下拉到 Privacy settings → 找到 Request your data 的入口。

点进去之后会让你确认导出范围。这里有个重要选项:是只导出“Account data”(基础账户信息),还是连同“Extended streaming history”(扩展播放历史)一起导出。如果你只想拿播放记录,建议选完整导出,因为 Extended streaming history 除了播放历史之外,还包括你的搜索记录、收藏歌曲、歌单操作等,后续如果想做更深入的分析,这些数据都很有价值。

提交申请之后就是等待。官方给的提示是“最长可能 30 天”,但实际上我等了大约 4 天就收到了邮件,邮件里有一个 zip 文件的下载链接。这个链接的有效期是有限的,下载之后记得第一时间解压保存,避免链接过期又要重新申请。

2.2 解压后的核心文件有哪些

解压之后你会看到一堆文件,结构类似下面这样,不同账户可能有细微差异,但大体一致:

  • MyData/Spotify Account Data/:账户基础信息目录
  • MyData/StreamingHistory0.json、StreamingHistory1.json:播放历史文件,数字编号可能有很多个
  • MyData/YourLibrary/:收藏歌曲、歌单信息
  • MyData/SearchQueries.json:搜索记录
  • MyData/Playlist1.json、Playlist2.json:你创建的歌单内容

对这次分析来说,最核心的就是StreamingHistory*.json这一批文件。Spotify 每个这样的文件里大约包含 1500 条播放记录,如果你的历史很长,会有StreamingHistory0.json、StreamingHistory1.json一直到很多个。这也是为什么后面读取数据时需要用循环批量处理,而不是只读一个文件就完事。

3. 环境准备与批量读取 JSON 数据

3.1 需要的 Python 环境和依赖库

在写读取代码之前,先把 Python 环境准备好。你不需要特别高的版本,3.10 以上就完全够用。如果你还没装 Python,直接去官网下载稳定版,安装的时候切记勾选“Add Python to PATH”,不然后面在命令行里敲pip会提示找不到命令,这是很多人第一个卡住的坑。

数据分析主要用到三个库:

  • pandas:做表格化处理和聚合统计
  • matplotlib:基础绘图
  • seaborn:在 matplotlib 基础上做更美观的统计图表

一条命令装完:

pip install pandas matplotlib seaborn

如果你用的是 Anaconda 环境,也可以换成conda install pandas matplotlib seaborn,效果一样。

3.2 批量读取多个 StreamingHistory 文件的完整代码

这里是最容易出现操作失误的地方,我单独说一下。StreamingHistory0.json虽然叫 JSON,但实际打开看会发现它是个数组,里面每个元素是一条播放记录。批量读取的思路是用glob匹配所有StreamingHistory*.json文件,然后逐个用json模块解析,再把解析结果合并成一个大的 pandas 表格。

这是完整的读取代码:

import glob import json import pandas as pd files = glob.glob('StreamingHistory*.json') print(f"发现 {len(files)} 个播放历史文件") df_list = [] for file in files: with open(file, 'r', encoding='utf-8') as f: data = json.load(f) df_list.append(pd.DataFrame(data)) df = pd.concat(df_list, ignore_index=True) print(f"合并完成,共 {len(df)} 条播放记录") print(df.head())

跑完这段,你会得到一个表格,每一行是一条播放记录。如果输出的行数和你印象中的年度播放量差不多,说明文件读对了。

4. 数据清洗与字段处理的三个关键细节

4.1 时间字段的解析与时区陷阱

StreamingHistory里的每条记录包含五个字段,我直接列出来:

  • endTime:播放结束的时间,字符串格式,例如2024-03-15 22:34:00
  • artistName:艺人/乐队名称
  • trackName:歌曲名称
  • msPlayed:本条记录实际播放了多少毫秒
  • spotify_track_uri:歌曲的 Spotify 唯一标识

endTime是最需要留神的字段。Spotify 官方文档没有明确说明它使用的是 UTC 还是账户所在时区,不同区域的用户实际导出的数据可能都不一样。我的做法是先不急着转换成时间戳,保留原始字符串,然后随机抽几条记录对照自己真实听歌的时间,确定它记录的是本地时间还是 UTC 时间。

如果发现差了几个小时,就需要统一做时区偏移。比如我的账户导出的数据经核对后发现贴近本地时间,所以直接用pd.to_datetime转成datetime类型就能用。如果你发现是 UTC,而你在东八区,就需要加 8 个小时:

df['endTime'] = pd.to_datetime(df['endTime']) # 如果是 UTC 需要改成本地时间,下面这行按需取消注释 # df['endTime'] = df['endTime'] + pd.Timedelta(hours=8)

这个坑特别隐蔽,如果你不加核对直接做按小时分析,最终画出来的听歌高峰图可能和真实感受完全对不上,到时候找原因找半天。

4.2 msPlayed 单位与“跳过”判断

msPlayed的单位是毫秒,想要换算成分钟必须除以 60000。很多教程会忽略这一步,直接拿原始数值去算总时长,算出来的结果凭空大了一千倍,没有任何参考意义,所以换算单位后还要注意保留精度。

我建议额外加一列“分钟数”:

df['minutes_played'] = df['msPlayed'] / 60000

关于播放行为,有一个非常实用的判断方法:如果一条记录的msPlayed小于 30000 毫秒(也就是 30 秒),基本可以认为这是一次快速跳过。我用这个阈值来标记“疑似跳过”的行为:

df['is_skipped'] = df['msPlayed'] < 30000

这样就能统计你整体的切歌率是多少,以及哪些歌播放时长中位数特别短——那一类歌大概率是被你反复点开又关掉的。

5. 核心分析:听歌时长、时段习惯与 Top 榜

5.1 累计播放时长与年度趋势

数据清洗完之后,分析就变得很快了。先看总播放时长,这是最直观的指标:

total_minutes = df['minutes_played'].sum() total_hours = total_minutes / 60 print(f"累计播放时长:{total_hours:.1f} 小时")

如果想看月度趋势,可以把endTime提取出月份,再按月份聚合:

df['month'] = df['endTime'].dt.to_period('M') monthly = df.groupby('month').agg( 播放时长=('minutes_played', 'sum'), 播放次数=('trackName', 'count') ) print(monthly)

我分析自己的数据时发现,某个月播放时长断崖式下跌,往前一查,那段时间我几乎从没用 Spotify 听过歌,原因竟然是换了工作地点,通勤方式从地铁变成了开车,而这辆车只支持蓝牙连接,那阵子我一直用另一个平台。换句话说,这份数据不仅反映音乐偏好,还会真实记录你生活状态的变化,这是官方年度总结里完全看不到的信息。

5.2 一天中的听歌高峰:按小时热力分布

听歌时段是我这次分析里最有趣的维度。把endTime的小时提取出来,统计每个小时有多少次播放:

df['hour'] = df['endTime'].dt.hour hourly_counts = df.groupby('hour')['trackName'].count()

更精细一点,可以按“星期几 + 小时”两个维度交叉,画一张热力图。这会揭示工作日和周末完全不同的听歌节奏,比如工作日的听歌高峰集中在早高峰通勤和下午三点到五点,周末则集中在晚上十点之后。

这里我要特别提一个反直觉的发现:我原本以为自己全天听歌最集中是“通勤路上”,但热力图显示我的播放峰值其实是在深夜十一点到凌晨一点,说明那段时间情绪波动比较大,需要靠音乐填满房间里的安静。你要是做了同样的分析,大概率也会发现一些自己平时没意识到的小习惯。

5.3 Top 艺人为什么不能只看播放次数

统计 Top 艺人的时候,很多人第一步想到的是“播放次数最多”,但只看次数会忽略一个重要因素:单曲时长。比如一首十分钟的后摇被完整播放一次,和一首三分钟的口水歌被完整播放一次,背后代表的使用时长完全不同。

所以我在统计 Top 艺人的时候同时算了两个指标:播放次数和累计播放时长。实际操作时可以这样写:

artist_stats = df.groupby('artistName').agg( 播放次数=('trackName', 'count'), 累计播放分钟=('minutes_played', 'sum') ).sort_values('播放次数', ascending=False) artist_stats['累计播放小时'] = artist_stats['累计播放分钟'] / 60 print(artist_stats.head(20))

结果会让排序发生明显变化。某些乐队的播放次数不是最高,但因为单曲长度普遍在六分钟以上,累计时长反而拿到了第一。这也提醒我:播放次数代表“点开的频率”,播放时长代表“真正沉浸的时间”,两个指标要结合起来看才更接近真实喜好。

6. 可视化呈现与意外发现

6.1 用热力图和柱状图把结果画出来

到这一步,表格里的数字已经能说明问题,但可视化之后会直观得多,也适合分享给朋友看。

我核心画了两张图。一张是“星期几 x 小时”播放量热力图,用 seaborn 的 heatmap:

import seaborn as sns import matplotlib.pyplot as plt df['weekday'] = df['endTime'].dt.dayofweek # 0=周一 heatmap_data = df.pivot_table( index='weekday', columns='hour', values='trackName', aggfunc='count', fill_value=0 ) plt.figure(figsize=(12, 6)) sns.heatmap(heatmap_data, cmap='YlOrRd', linewidths=0.5) plt.title('播放量热力图: 星期 x 小时') plt.xlabel('小时') plt.ylabel('星期') plt.show()

另一张是 Top 15 艺人的累计播放时长柱状图,横向排列更易读:

top_artists = artist_stats.nlargest(15, '累计播放分钟') plt.figure(figsize=(10, 8)) sns.barplot( data=top_artists, x='累计播放分钟', y=top_artists.index, palette='viridis' ) plt.xlabel('累计播放分钟') plt.title('Top 15 艺人 (按播放时长)') plt.tight_layout() plt.show()

画完之后建议把图直接存下来:

plt.savefig('spotify_analysis.png', dpi=200, bbox_inches='tight')

这样后续自己看或者写年度总结的时候,可以直接用这份图。

6.2 我在这份数据里发现的“反直觉”规律

分析完自己的数据后,有几个意外发现,这里挑三个有代表性的说出来,算是给大家一个参考思路。

第一个发现是“高跳过率的歌反而暴露真实偏好”。我统计了一下,播放次数前三十的歌曲里,有一首几乎每次点开都是前二十秒就切掉,按理说这种歌应该早点删掉,但恰恰说明它经常出现在我不喜欢的随机播放里,或者说我对它有某种“点开确认一下是不是不想听”的执念。这种歌只靠年度总结是绝对看不出来的。

第二个发现是“深夜单曲循环现象”。我的数据里有一首歌单日播放了九次,全部集中在凌晨一点到两点。不用查记忆我也知道,那段时间我压力特别大,反复听这首歌其实是自我安抚。如果你也做了这个分析,不要错过类似的数据点,它们往往是情绪变化的投影。

第三个发现和数据断档有关。前面提到的那个播放量断档月份,如果只看官方年度总结,它只会告诉我全年总播放量,根本不会暴露这段空窗期。而拿到原始数据之后,你会看到生活节奏变化如何真实地反映在听歌行为里。

这些规律放到一起,让我对这些年的听歌行为有了比任何年度总结都立体的理解。数据本身不骗人,但前提是你得知道怎么把它从一堆 JSON 里刨出来。

一些实操经验总结

最后分享几个我做完这个项目之后的实在体会。

第一,申请数据导出要趁早。官方说的 30 天虽然通常用不了,但如果你等着做分析,提前申请总没错。下载链接是有时效的,收到邮件后尽快下载并备份到本地,避免想分析的时候才发现过期了要重新等。

第二,文件解压之后不要乱动目录结构。市面上有人会写专门的解析工具,会假设文件在特定路径下,你手动改过文件名再批量读取时很容易踩到路径报错。建议解压到一个独立文件夹,并且解压后别再移动里面的 JSON 文件,所有代码都在该文件夹下运行。

第三,保留原始数据,不要直接在原 DataFrame 上反复修改。我的习惯是每次清洗都新生成一列或新建一个副本,这样分析过程中发现问题,随时能回头检查原始记录,而不是从头再导一遍数据。

第四,如果数据量特别大,比如你听了五六年,几十个 JSON 文件合并后可能有几十万行。这种规模用 pandas 完全没问题,但建议在做groupby聚合时留意一下内存使用,关掉其他占内存较多的软件,避免中途卡死。

具体到如何把这份分析扩展出去,我觉得还有不少可以玩的方向:比如把播放记录和歌曲特征(BPM、调性、能量值)关联起来,判断自己的情绪偏好;又比如把早年和近年的数据切片对比,看看音乐口味是怎么演变的。Spotify 官方 API 也能拿到一部分歌曲音频特征数据,后续可以结合播放历史做更深的挖掘。不过那又是另一个话题了,等我把这套流程跑得更顺了再单独写一篇。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询