ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

用Python分析Spotify听歌数据:从JSON解析到可视化报告

用Python分析Spotify听歌数据:从JSON解析到可视化报告 前几天我在 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, encodingutf-8) as f: data json.load(f) df_list.append(pd.DataFrame(data)) df pd.concat(df_list, ignore_indexTrue) print(f合并完成共 {len(df)} 条播放记录) print(df.head())跑完这段你会得到一个表格每一行是一条播放记录。如果输出的行数和你印象中的年度播放量差不多说明文件读对了。4. 数据清洗与字段处理的三个关键细节4.1 时间字段的解析与时区陷阱StreamingHistory里的每条记录包含五个字段我直接列出来endTime播放结束的时间字符串格式例如2024-03-15 22:34:00artistName艺人/乐队名称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(hours8)这个坑特别隐蔽如果你不加核对直接做按小时分析最终画出来的听歌高峰图可能和真实感受完全对不上到时候找原因找半天。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(播放次数, ascendingFalse) artist_stats[累计播放小时] artist_stats[累计播放分钟] / 60 print(artist_stats.head(20))结果会让排序发生明显变化。某些乐队的播放次数不是最高但因为单曲长度普遍在六分钟以上累计时长反而拿到了第一。这也提醒我播放次数代表“点开的频率”播放时长代表“真正沉浸的时间”两个指标要结合起来看才更接近真实喜好。6. 可视化呈现与意外发现6.1 用热力图和柱状图把结果画出来到这一步表格里的数字已经能说明问题但可视化之后会直观得多也适合分享给朋友看。我核心画了两张图。一张是“星期几 x 小时”播放量热力图用 seaborn 的 heatmapimport seaborn as sns import matplotlib.pyplot as plt df[weekday] df[endTime].dt.dayofweek # 0周一 heatmap_data df.pivot_table( indexweekday, columnshour, valuestrackName, aggfunccount, fill_value0 ) plt.figure(figsize(12, 6)) sns.heatmap(heatmap_data, cmapYlOrRd, linewidths0.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( datatop_artists, x累计播放分钟, ytop_artists.index, paletteviridis ) plt.xlabel(累计播放分钟) plt.title(Top 15 艺人 (按播放时长)) plt.tight_layout() plt.show()画完之后建议把图直接存下来plt.savefig(spotify_analysis.png, dpi200, bbox_inchestight)这样后续自己看或者写年度总结的时候可以直接用这份图。6.2 我在这份数据里发现的“反直觉”规律分析完自己的数据后有几个意外发现这里挑三个有代表性的说出来算是给大家一个参考思路。第一个发现是“高跳过率的歌反而暴露真实偏好”。我统计了一下播放次数前三十的歌曲里有一首几乎每次点开都是前二十秒就切掉按理说这种歌应该早点删掉但恰恰说明它经常出现在我不喜欢的随机播放里或者说我对它有某种“点开确认一下是不是不想听”的执念。这种歌只靠年度总结是绝对看不出来的。第二个发现是“深夜单曲循环现象”。我的数据里有一首歌单日播放了九次全部集中在凌晨一点到两点。不用查记忆我也知道那段时间我压力特别大反复听这首歌其实是自我安抚。如果你也做了这个分析不要错过类似的数据点它们往往是情绪变化的投影。第三个发现和数据断档有关。前面提到的那个播放量断档月份如果只看官方年度总结它只会告诉我全年总播放量根本不会暴露这段空窗期。而拿到原始数据之后你会看到生活节奏变化如何真实地反映在听歌行为里。这些规律放到一起让我对这些年的听歌行为有了比任何年度总结都立体的理解。数据本身不骗人但前提是你得知道怎么把它从一堆 JSON 里刨出来。一些实操经验总结最后分享几个我做完这个项目之后的实在体会。第一申请数据导出要趁早。官方说的 30 天虽然通常用不了但如果你等着做分析提前申请总没错。下载链接是有时效的收到邮件后尽快下载并备份到本地避免想分析的时候才发现过期了要重新等。第二文件解压之后不要乱动目录结构。市面上有人会写专门的解析工具会假设文件在特定路径下你手动改过文件名再批量读取时很容易踩到路径报错。建议解压到一个独立文件夹并且解压后别再移动里面的 JSON 文件所有代码都在该文件夹下运行。第三保留原始数据不要直接在原 DataFrame 上反复修改。我的习惯是每次清洗都新生成一列或新建一个副本这样分析过程中发现问题随时能回头检查原始记录而不是从头再导一遍数据。第四如果数据量特别大比如你听了五六年几十个 JSON 文件合并后可能有几十万行。这种规模用 pandas 完全没问题但建议在做groupby聚合时留意一下内存使用关掉其他占内存较多的软件避免中途卡死。具体到如何把这份分析扩展出去我觉得还有不少可以玩的方向比如把播放记录和歌曲特征BPM、调性、能量值关联起来判断自己的情绪偏好又比如把早年和近年的数据切片对比看看音乐口味是怎么演变的。Spotify 官方 API 也能拿到一部分歌曲音频特征数据后续可以结合播放历史做更深的挖掘。不过那又是另一个话题了等我把这套流程跑得更顺了再单独写一篇。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进