资讯动态

用Python分析Spotify听歌数据:从JSON导出到可视化全流程

发布时间:2026/10/22 13:47:02 来源:尧图企业网站定制
1. 听歌数据的价值在哪里从“歌单收藏”到“行为轨迹”入坑 Spotify 久了你会发现收藏歌单和真正听过的歌完全是两回事。有些歌你根本没点过有些歌手你以为早就脱粉了结果年度报告一出来发现他霸占了你一整年的播放时长。我当初做“使用Python分析你的Spotify听歌数据”这个小项目就是源于一次好奇流媒体平台到底记录了我多少听歌行为这些数据能不能导出导出来之后又能分析出什么先说结论能导出而且格式很规整分析价值远超你预期。Spotify 的隐私政策允许用户申请导出完整的账号数据包含播放历史、搜索记录、关注列表、设备信息等。其中最核心的就是 StreamingHistory 系列文件记录了每一首歌的播放时间、曲目名、歌手名、播放时长以及播放时是否跳过。也就是说你能精确到“某天凌晨三点我循环了哪首歌”“哪首歌我只听了 12 秒就切掉了”。这份数据特别适合三类人第一想做个人年度听歌盘点但不想等平台出报告的音乐爱好者第二数据分析和 Python 可视化方向的新手这套数据干净、结构简单、字段少比爬小说、爬电商练手友好得多第三想了解自己真实听歌习惯、想优化歌单的普通用户。我最终做出来的分析结果包含按小时分布的听歌活跃度热力图、按月的播放量趋势、播放时长 Top50 曲目榜、歌手播放量排名、跳过率最高的曲目清单。整个项目用 Jupyter Notebook 就能跑完依赖库只有 pandas 和 matplotlib对新手没有门槛。下面从数据导出开始完整过一遍整个流程。2. 数据准备导出流程与文件格式拆解2.1 如何向 Spotify 申请导出个人数据这一步很多人卡在找不到入口。实际上路径非常简单登录 Spotify 网页端进入账户设置找到“隐私设置”区域里面有一项“下载您的数据”。点击后会弹出一个确认框要求你选择要导出的数据范围。提醒一句默认选项通常已经包含了“播放历史记录”相关的全部字段建议全选因为后续分析需要的 StreamingHistory 就藏在这里面。提交申请后平台不会立刻给你文件而是会发一封确认邮件到你的注册邮箱需要点击邮件里的链接完成验证。之后就是等待打包。打包时间不稳定我实测过两次一次大约两天一次等了五天。文件生成后平台会再次发邮件附上一个下载链接打开后会得到一个 ZIP 压缩包。解压后目录结构大致是MyData/ ├── AccountData/ ├── Audiobooks/ ├── Identifiers/ ├── Payments/ ├── Playlist1.json ├── Playlist2.json ├── SearchQueries.json ├── StreamingHistory0.json ├── StreamingHistory1.json ├── ... └── Userdata.json这里重点说明StreamingHistory 是分文件的如果你的历史足够长会出现 StreamingHistory0、StreamingHistory1、StreamingHistory2……多个 JSON 文件。这倒不是平台故意拆分而是导出机制对单个文件大小做了限制所以拆成了多个分片。提示申请导出的是完整历史数据不是最近 90 天。我见过的数据最早可以追到刚注册账号那会儿哪怕你那时候注册的是免费账号只要有播放行为就会有记录。2.2 四种关键 JSON 文件的含义与用途解压之后的文件看着有点多但真正用于听歌分析的核心文件就两个StreamingHistory播放历史和 Userdata用户信息。其他文件像 SearchQueries、Playlist 文件属于锦上添花本文先不展开。先说 StreamingHistory。这是整个项目的核心数据源每条记录结构如下{ ts: 2023-11-15T08:23:11Z, username: your_account, platform: iOS, ms_played: 180234, conn_country: CN, ip_addr_decrypt: x.x.x.x, user_agent_decrypt: Mozilla/5.0..., master_metadata_track_name: Bad Blood, master_metadata_album_artist_name: Taylor Swift, master_metadata_album_album_name: 1989 (Deluxe), spotify_track_uri: spotify:track:xxxx, episode_name: null, episode_show_name: null, reason_start: clickrow, reason_end: trackdone, shuffle: false, offline: false, offline_timestamp: , incognito_mode: false }一眼扫过去字段很多但真正要用的是这几个ts播放时间UTC 时区、ms_played播放时长单位毫秒、master_metadata_track_name曲名、master_metadata_album_artist_name歌手名、master_metadata_album_album_name专辑名、reason_end结束原因、shuffle是否随机播放、offline是否离线播放。其中 reason_end 这个字段是后续分析跳过率的关键。它的取值常见的有 trackdone完整听完、endplay主动结束、fwdskip点击下一首跳过、backbtn返回上一首每个值对应不同的行为含义后面会专门展开。Userdata.json 文件包含的是账号维度的信息比如用户名、国家、创建时间等。它的作用更多是辅助确认数据归属比如你有多个账号可以用它来滤掉非自己账号的历史。注意不要直接拿线上工具去解析这些 JSON。这些文件结构本身很规整用 pandas 直接加载即可没必要绕弯子。3. 环境准备与数据加载手把手搭好分析基础3.1 Python 环境与依赖库安装开发环境方面我用的是 Anaconda 自带的 Jupyter Notebook省去了很多环境配置的烦恼。如果你机器上还没装 Python 科学计算环境最简单的安装路径是去官网下载 Anaconda 安装包一路默认安装完Jupyter 就带好了。依赖库其实只需要两个pip install pandas matplotlib如果之后要做交互式的图表可以再加一个 plotly。不过本文的核心分析pandas 和 matplotlib 完全够用。顺便说下为什么选 pandas 而不是纯 Python 处理StreamingHistory 文件动辄几万行纯 Python 逐条读取再统计代码长而且效率差。pandas 对 JSON 文件的读取有原生支持一行pd.read_json()就能把整个文件载入 DataFrame后面做时间序列、聚合统计都是现成的 API效率高得多。3.2 合并多个 JSON 分片文件的正确姿势前面说过StreamingHistory 可能拆成多个文件所以第一步是把它合并成一个完整的 DataFrame。代码很简单import pandas as pd import glob # 读取所有 StreamingHistory 文件 history_files glob.glob(MyData/StreamingHistory*.json) df_list [pd.read_json(f) for f in history_files] df pd.concat(df_list, ignore_indexTrue) print(f共合并 {len(history_files)} 个文件总记录数 {len(df)} 条)输出的效果大概是共合并 3 个文件总记录数 184935 条18 万条记录这就是约两年半的听歌轨迹。看到这个数字的时候我第一反应是平台记数据记得太细了连我在地铁上掐掉一首歌的半秒操作都留下了痕迹。合并过程有个细节concat 之后建议再检查一下是否有完全重复的行。由于数据导出机制通常不会重复但保险起见可以执行一次df df.drop_duplicates()接下来做两件基础的清理工作过滤非音乐类型的记录以及删除空曲名记录。# 只保留曲目不为空的记录去掉播客等非音乐内容 df df[df[master_metadata_track_name].notna()] # 重置索引 df df.reset_index(dropTrue)这里为什么要过滤因为 StreamingHistory 里其实混入了 podcast播客的播放记录它们存在于 episode_name 字段中而 master_metadata_track_name 是空的。如果不加过滤后续按歌曲聚合统计时会出现一堆空值干扰。当然如果你本身想分析播客那又另说。3.3 时间字段的解析与时区处理的坑原始数据的 ts 字段是 ISO 8601 格式的字符串形如2023-11-15T08:23:11Z末尾的 Z 代表 UTC 零时区。如果你的账号注册地在中国或者你在国内使用 Spotify大部分播放行为发生在东八区。这里就涉及到整个项目里我认为最容易被忽略、但影响最大的一个操作时区转换。不转换会怎么样假设你凌晨 2 点在听歌记录的是前一天的 18:00 UTC。后续做“按小时活跃度分析”时你的凌晨听歌行为会被划到前一天下午整个分析直接失真。转换方法很干净# 转为 datetime 时区感知类型 df[ts] pd.to_datetime(df[ts], utcTrue) # 转换到东八区 df[ts_local] df[ts].dt.tz_convert(Asia/Shanghai)转换完成后后续所有时间维度的分析小时、星期、月都从 ts_local 字段提取。如果你不在东八区改成自己的 IANA 时区名称即可比如 America/New_York、Europe/Berlinpandas 的 tz_convert 支持全球标准时区名。同时这里可以顺手把 ts_local 里的年、月、日、小时、星期几提取成独立列后面会大量用到df[year] df[ts_local].dt.year df[month] df[ts_local].dt.month df[day] df[ts_local].dt.day df[hour] df[ts_local].dt.hour df[weekday] df[ts_local].dt.dayofweek # 0周一, 6周日实操心得如果是多账号合并分析记得在合并前先给每个账号打一个来源标签不然后面没法区分数据归属。我因为只有一个账号这一步省了但代码结构上建议保留这个思路。4. 探索性分析从总播放量到隐藏的听歌习惯4.1 整体规模总播放次数与时长统计拿到完整数据后的第一感觉应该先回答几个直观的问题我总共听了几首歌累计播放时长有多少平均每天听歌多久这几个指标是后续一切分析的地基。计算代码total_plays len(df) total_ms df[ms_played].sum() total_hours total_ms / (1000 * 60 * 60) print(f总播放次数{total_plays}) print(f累计播放时长约{total_hours:.1f} 小时)我这边得到的结果是累计播放时长约 1 万小时出头折算成连续播放将近 417 天。看到数字那一刻还是挺震撼的。在我们还听磁带、CD 的年代我们无法想象留下这么精确的听觉足迹。这里提醒一个单位问题ms_played 单位是毫秒不是秒。很多人会在这里踩坑把毫秒当成秒做聚合然后算出来的总时长离谱得吓人。千万记住除以 1000 才是秒。4.2 最常听的歌曲与歌手 Top 榜基础统计做完了接下来直接上硬货播放次数最多的歌曲 Top 10以及播放总时长最多的歌手 Top 10。播放次数排行track_counts df[master_metadata_track_name].value_counts() print(track_counts.head(10))这个结果只反映了“被点开”的次数但歌曲有长有短点开次数多不等于听的时间长。更合理的指标是累计播放时长。对于一首 4 分钟的歌完整播放一次的时长是 240 秒但对于一首 15 分钟的电子长曲完整播放一次的体量完全不同。所以歌手榜我建议用累计播放时长来排名artist_playtime df.groupby(master_metadata_album_artist_name)[ms_played].sum().sort_values(ascendingFalse) artist_playtime_hours artist_playtime / (1000 * 60 * 60) print(artist_playtime_hours.head(10))我记得那次跑出来的结果里排名第一的歌手有将近 300 小时的累计播放量远超第二名。从个人口味来说这并不意外但数字从年度报告式的“百分比”变成了精确到小时的时间投入观感完全不同。这里还可以加一个“平均单曲播放时长”的维度某个歌手的歌曲的总播放时长除以播放次数可以判断你是在老老实实完整听还是总快进。这个指标后面在用 reason_end 字段分析跳过行为时可以结合起来看能发现一些很有意思的特征。4.3 跳过率与播放完成度的真实计算原始数据里的 reason_end 字段记录了一首歌结束播放时的原因这个字段是分析听歌行为深度的关键。取值含义对照如下reason_end含义行为判断trackdone歌曲自然播完完整收听endplay播放列表/专辑自然播完完整收听完最后一个fwdskip点击下一首跳过主动跳过backbtn点击返回上一首主动返回logout登出停止播放中断remote其他设备控制停止外部中断做跳过行为分析最简洁的方式是构造一个新的布尔字段# 跳过 非正常结束 df[skipped] ~df[reason_end].isin([trackdone, endplay]) skip_rate df[skipped].mean() print(f整体跳过率{skip_rate:.2%})整体跳过率是一个非常个人的指标。我从数据里算出来自己的跳过率在 45% 左右也就是将近一半的曲目没听完就切掉了。这其实符合流媒体时代的典型行为特征选择成本低耐心有限10 秒钟决定一首歌的生死。如果想更严格地定义“试听即弃”的歌可以结合 ms_played 和歌曲总时长来算相对完成度。比如播放了不到整首歌 30% 就切换的曲目算作高跳过曲目。找出这些歌的共性——是同一个歌手还是同一种风格比单纯看跳过率有趣得多。5. 时间维度分析窥见生活习惯的窗口5.1 按 24 小时分布你是“通勤型”还是“夜猫型”时间字段处理好了按小时聚合太轻松了hour_activity df.groupby(hour).size()得到的结果是一个 24 维的计数反映的是全天每个小时你开始播放歌曲的次数。把结果画出来会看到一个非常鲜明的波峰波谷形态。我第一个明显的高峰在早上 7~9 点这是通勤时间第二个更大的高峰在晚上 18~23 点之间是下班之后到睡前的时间。凌晨 4 点的播放量几乎为 0说明那段时间确实在睡觉。这种数据形态很直观地复现了“工作日-周末”的生活节奏差异。更进一步可以按“工作日 vs 周末”分开统计。方法也不复杂前面已经提取了 weekday 字段0~4 算工作日5~6 算周末df[is_weekend] df[weekday].isin([5, 6]) grouped df.groupby([is_weekend, hour]).size().unstack(0)工作日的听歌高峰和通勤时段强绑定周末的分布更平滑且凌晨时段有明显抬升。做完这个分析你对自己“几点最闲”会有超乎直觉的认知。5.2 按月播放趋势发现你的“歌荒期”和“上瘾期”按月的播放量趋势是最容易一眼看出长期变化的图表monthly df.groupby([year, month]).size() monthly.plot(kindline)那一年的数据里12 月和 1 月是绝对高峰其他月份相对平稳。这大概率不是巧合年底的 Holiday Season出行、聚会、独处的时间都会变多听歌的场景源相自然被放大。值得说的是月级趋势还能帮助你发现数据中的“异常时段”。比如某个月播放量骤降往下钻取日级数据看是不是某几天的记录为空排查是不是设备出了故障、账号被挤下线或者是某段时间切换了另一个账号听歌。这类数据质量问题在时间维度上会暴露得特别明显。5.3 日维度热力图可视化“一周的节奏”把小时和星期两个维度交叉起来用热力图呈现是整个项目里视觉效果最好的一张图。pandas 透视表加 Matplotlib 的 imshow 或 seaborn 的 heatmap 都可以实现import seaborn as sns pivot df.pivot_table(indexweekday, columnshour, valuesms_played, aggfuncsum, fill_value0) sns.heatmap(pivot / (1000 * 60 * 60), cmapYlGnBu)热力图的横轴是 0~23 小时纵轴是周一至周日颜色越深代表累计听歌时长越长。做出来之后你会发现周一到周五的色块形态明显不同于周六周日工作日早上有深色竖条周末的颜色集中在深夜和下午。这种“一周节奏”图哪怕不做任何进一步分析发出来也特别有张力。实操心得做热力图时注意pivot_table的 aggfunc 用 sum 而不是 count。因为 0~1 秒的“误触播放”也是完整的记录用 count 会把很短的不完整播放当作真实听歌行为导致颜色失真。用 sum 的播放毫秒数更合理。6. 可视化呈现让数据会说话6.1 可视化工具选型为什么用 Matplotlib 足够听歌分析常用的可视化库有三个Matplotlib、Seaborn、Plotly。我的建议是静态图和热力图用 Seaborn Matplotlib如果想要网页交互效果再加 Plotly但初期完全没必要。Matplotlib 的优势不在好看而在于稳定和生态。它和 pandas 的集成是最成熟的DataFrame 可以直接调用.plot()方法自定义程度高。Seaborn 是 Matplotlib 的高级封装做统计图形尤其是热力图非常漂亮底层还是 Matplotlib。如果你后续想做更炫酷的交互式探索比如在浏览器里悬浮显示歌曲信息可以用 Plotly。但那会牵扯到更多 HTML/JS 的知识这个项目阶段保持简单更有意义。6.2 解决中文显示乱码的两个方案Matplotlib 画图最常见的坑是中文乱码。默认字体没有中文字形图表标题、刻度里的中文标签会显示成方块。方案有两套任选方案一全局设置中文字体import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] # Windows 用黑体 plt.rcParams[axes.unicode_minus] FalsemacOS 可以换成[Arial Unicode MS]。方案二注册自己的字体文件如果你需要完全可控可以下载一个开源中文字体文件如思源黑体用 FontProperties 手动加载from matplotlib.font_manager import FontProperties font FontProperties(fnameSourceHanSansSC-Regular.otf) plt.title(播放量月度趋势, fontpropertiesfont)方案二更稳因为不依赖系统是否预装了相应字体。6.3 核心图表的使用场景这次项目里我把图表分成三个层次按使用场景区分第一层趋势图。最典型的是按月、按周播放量折线图适合回答“我的听歌量如何随时间变化”。这类图用plot(kindline)直接生成配上网格线就足够。第二层分布图。包括 Top 歌手横向条形图、播放时长分布直方图。Top 歌手榜用条形图最直观一眼看到歌手之间的时长差距。播放时长直方图适合观察“短播放”和“完整播放”的边界在哪。第三层关系图。热力图是最典型代表。它把两个维度叠加在一个二维色块矩阵上空间利用率高信息密度大。凌晨时间段的深色块和通勤时段的亮色竖条比任何文字都更有说服力。实操心得不要在一张图里堆太多内容。条形图就放 10 条不要堆 50 条折线图就画两年不要从账号注册第一天画到昨天。图表的作用是快速传递结论而不是数据倾倒。7. 进阶分析与扩展思路这只是一个起点7.1 歌曲档位分析你听歌的“耐心阈值”如果你把每次播放的 ms_played 除以歌曲总时长就能算出一个“完成度”百分比。对所有歌曲的完成度分布做直方图会看到一个非常明显的 U 型分布在非常低的完成度区域有大量记录说明许多歌 15 秒之内就被切了在接近 100% 的区域也有大量记录说明被完整听完的歌也不少。这个 U 型中间的最低点可以理解为你的“耐心阈值”超过这个时间还在听大概率会听完没超过这个时间下一首。这组数据比跳过率有意思多了。7.2 设备与场景维度分析StreamingHistory 里的 platform 字段记录了播放设备可能的值包括 iOS、Android、Windows、web player 等。按设备统计播放量能发现你在什么场景下听歌最多是在地铁用手机还是在电脑前写代码时挂着网页播放器。再结合 offline 字段是否离线播放可以进一步分析离线播放的比例有多高说明你为“没网环境下的听歌需求”专门做了下载。这些数据放在一起其实就是在描绘一个立体的人。7.3 结合歌曲元数据做风格聚类StreamingHistory 本身不含曲目风格、BPM、调性这些音乐特征但 Spotify 为每个曲目提供了 track URI格式是spotify:track:xxx。你可以拿这些 URI 配合 Spotify Web API 去请求更多歌曲特征比如 danceability舞曲感、energy能量值、valence情感效价。把 18 万条播放记录映射到这些音乐特征上再做箱线图对比就能回答“我的听歌心情画像”这类问题。例如你的播放记录里是低能量歌曲居多还是高能量歌曲居多凌晨时段听的能量值是否显著高于白天都是很有挖掘空间的课题。但这个课题需要申请 API 授权单条请求有配额限制如果曲目量太大需要做批量爬取和缓存工作量会比听歌数据本身大得多。我们项目组当时只处理了 Top 500 曲目量级可控。7.4 从数据分析过渡到推荐系统思路如果还想再进一步可以把你的听歌历史当成一个“隐式反馈数据集”按用户—曲目—播放时长构造交互矩阵借用协同过滤的思路来发现自己歌单里的“盲区”。具体做法是先只保留完整听完的记录构建一个用户—曲目矩阵但这个矩阵只有你自己一行没法直接算相似度。替代方案是降维攻击统计每个歌手的播放量权重再结合歌手的高频共现关系看有没有“你高频率听 A 歌手、但 B 歌手已经完全覆盖了 A 的空缺位置”的迹象。这算是一个最小化的“聆听完整度-偏好滑坡”分析真正要落地推荐系统需要更多用户数据但思路是相通的。8. 常见问题与避坑指南8.1 时间戳偏早 8 小时如果你发现分析结果里凌晨 1 点的播放量异常高、下午 2 点却几乎为 0先别怀疑自己的作息检查一下时区有没有转换。所有 ts 字段的原始值都是 UTC必须先dt.tz_convert(Asia/Shanghai)再提取小时。我最初做的时候忽略了这个细节导致“深夜听歌”假象持续了两版分析。8.2 合并文件和过滤播客记录的先后顺序先合并再过滤会比逐文件过滤更稳。因为 concat 之后数据总量大pandas 的过滤操作本身是向量化的性能上不存在瓶颈而且先合并再做统一过滤逻辑更清晰。反过来的问题是如果某个 StreamingHistory 分片里恰好全是播客记录逐文件过滤会漏掉其他文件里的非空曲目容易出错。8.3 ms_played 和 playtime_ms 的混淆部分平台上 Spotify 相关 API 的字段名叫做 playtime_ms而导出文件里面叫 ms_played语义完全一样。两者的单位都是毫秒。但有一个容易忽视的问题如果歌曲播放时间小于 1000 毫秒即不到 1 秒那它到底算不算“听过”我的处理方式把 ms_played 3000 的记录单独拎出来统计不算进总播放时长。因为大概率是滑动列表时的误触几百毫秒就切了。过滤掉这些记录后总时长和平均播放时长的数值会更接近真实听歌体验。8.4 文件编码问题Windows 下用 Excel 看导出的 JSON 文件有时候会乱码但 pandas 的 read_json 内部用的是 UTF-8一般不会出现读取乱码。真正容易出问题的是输出 CSV 后用 Excel 打开中文乱码解决方法是写出的时候带 BOMdf.to_csv(spotify_history.csv, indexFalse, encodingutf-8-sig)这个细节我吃过亏第一次用默认 utf-8 导出的 CSV 在 Excel 里中文全乱折腾了半天才意识到是 Excel 对 UTF-8 无 BOM 的支持不好。8.5 隐私与数据安全提醒最后务必强调隐私。导出的数据包含你的 IP 地址ip_addr_decrypt 字段、设备信息、精确到秒的播放时间线。这些数据放在本地分析没问题但不要随意上传到公开仓库、不要放到网盘共享链接里。如果你打算把分析过程写成博客发布建议做两步处理一是删除 IP 和 UserAgent 等敏感字段二是将时间戳做粗化处理只保留年月日小时去掉分钟秒。这不仅是保护自己也是对读者负责。多年以后回头看那些年度报告动画虽然炫目但真正能留住记忆的其实是精确到毫秒的原始数据。每一首切掉的歌、每一个凌晨的播放记录都在 JSON 里沉默地躺着等待某一天被重新唤醒。我强烈建议你也导出一份自己的听歌数据存进本地不仅是为了跑一遍本文的分析更是给未来的自己留一个可以回溯的声音坐标。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑