ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python实战:用pandas清洗Spotify听歌数据,可视化你的音乐画像

Python实战:用pandas清洗Spotify听歌数据,可视化你的音乐画像 如果你也是Spotify的重度用户一定有过这种体验每年年底的音乐总结里你听了xx首歌这是你的年度歌手这些卡片看着很漂亮但总觉得不够解渴。真正想看清楚自己到底怎么听歌——哪个时间段最沉迷、那首歌被你偷偷循环了多少次、某位歌手究竟在列表里占了多大比重——我们需要自己去碰那份听歌记录。几个月前我花了点时间从Spotify官方导出个人数据然后用Python写了套脚本把这批数据拆开揉碎做了一遍分析结果发现了不少令我自己都意外的事实。这篇文章就是把整个过程完整拆解一遍从数据导出的路径、文件长什么样到用Python(pandas)清洗、聚合最后用matplotlib画出几张不太一样但足够有信息量的图表。内容适合已经会一点Python、想动手做个数据分析小项目的读者也适合那些完全没写过分析脚本、但想拿真实数据练手的同学。我尽量把每一步的原理和坑都讲清楚你照着抄基本就能跑通。1. 数据从哪来先搞清楚Spotify给你导出了什么1.1 为什么说官方数据导出是效率最高的路线聊分析之前得先解决一个绕不开的问题数据怎么拿到手不少人第一反应是去扒Spotify的接口或者写个爬虫去模拟播放记录。我的建议是别折腾这些。Spotify官方本来就有完整的个人数据导出功能你在账号设置里的Privacy页面找到Download your data相关的选项点一下申请导出Spotify会在几天内官方说法是可能长达30天把一份存档打包发到你的邮箱里。这份存档就是你账号的完整数据快照包含全部听歌记录、搜索历史、你的个人信息、付费记录等。对用来分析听歌行为来说这比任何接口都全而且它是官方给的合规数据不用提心吊胆地考虑什么使用条款问题。实际操作中导出申请一般有几种可选范围有些地区可能让你选择是导出完整数据还是部分数据。做一个音乐分析项目的话记得尽量选完整数据或者至少包含流媒体播放历史的那一档。我自己的经验是从点击申请到收到下载链接大概等了两三天不同账号可能有差异提前规划好时间就行。1.2 收到的压缩包里到底装了哪些文件解压之后你会看到一个文件夹里面的内容各家账号可能略有差异但几个核心文件基本是固定的。最常见的有StreamingHistory0.json、StreamingHistory1.json...这是整个分析的核心每一行记录就是你某一次播放动作。Identity.json账号的基础信息比如用户名、国家、账号创建时间。Payments.json付费历史一般分析用不上。SearchQueries.json你的搜索关键词记录做行为洞察时可能有点意思。Playlist*.json如果你导出的是完整数据可能还会包含播放列表信息。真正值得我们研究的就是那批StreamingHistory*.json。打开文件看一条记录结构大概是这样的{ endTime: 2024-03-15 08:32:17, artistName: The Beatles, trackName: Here Comes The Sun, msPlayed: 183240 }字段含义很清楚endTime是这首歌播放结束的时间artistName是歌手名trackName是歌曲名msPlayed是这次播放听了多少毫秒。注意不同账号、不同时间点导出的文件名可能不一样。我见过有人的压缩包直接是StreamingHistory0.json也见过带国家/日期前缀的写法。最稳妥的办法是解压后先看一眼目录结构凡是文件名里带StreamingHistory或者streamhistory字样的JSON基本都是同一类播放记录文件拿到脚本里一起处理就好。这个字段设计其实是很标准的时序事件格式每条记录都是一次播放事件。后面我们的整个分析思路就是围绕这张事件表做聚合。2. 动手前先想清楚分析脚本该怎么搭2.1 装好一套不折腾的Python环境代码层面我们需要的东西其实很少核心就是三个库pandas负责数据处理matplotlib负责画图glob负责找到那些JSON文件。如果你用的是Anaconda环境pandas和matplotlib应该都是自带的了。如果是纯净版Python环境一条命令就够pip install pandas matplotlib版本方面Python 3.9以上基本没问题pandas版本新一点更好。整个分析脚本只依赖这两个第三方库其余都用标准库这样不管你是跑在macOS、Windows还是Linux上侧重点都在代码逻辑本身不会被环境问题带偏。2.2 分析框架从原始播放记录到信息量足够的图表拿到一堆JSON文件以后不能上来就写画图代码。我在做数据分析项目时习惯先想清楚整个流水线从原始数据到最终结论中间要经过哪些处理阶段。这次的听歌数据分析我拆成四步加载把多个StreamingHistory*.json文件读进来合并成一张完整的DataFrame。清洗把毫秒转成分钟可读的时长把时间字符串处理成真正的日期时间类型过滤掉明显无效的记录。聚合按歌手、歌曲、小时、月份等维度分组统计算出占比、次数、时长这些指标。可视化把聚合结果用图表的形式展示出来。这个顺序看起来平平无奇但它能保证每一步都只做一件事后续排查问题会轻松很多。清洗和聚合是最容易出细节bug的地方视觉化反而简单画图只是把已经算好的数字映射成图形。明确了流水线后面写代码的时候思路会非常清晰。3. 核心代码一步一步拆开讲3.1 把所有StreamingHistory文件读进同一张表先处理数据加载。因为数据可能分布在很多个JSON文件里我习惯用glob把符合规则的文件路径一次性抓出来然后逐个读取再拼接。import glob import pandas as pd files glob.glob(StreamingHistory*.json) print(f找到 {len(files)} 个播放记录文件) df_list [] for f in files: chunk pd.read_json(f) df_list.append(chunk) df pd.concat(df_list, ignore_indexTrue) print(df.shape)这里有个小细节pd.read_json对StreamingHistory这种默认的JSON数组格式支持得很好一般不用额外参数。但如果你的文件路径不在当前工作目录下记得在glob里写完整路径前缀比如glob.glob(../MyData/StreamingHistory*.json)。跑完之后看一眼列名和行数。以我自己几年的播放记录为例通常能读到几万行甚至十几万行这个体量对pandas来说压力很小秒级处理。如果发现行数和你预期偏差太大先回看文件结构看是不是漏掉了某个子目录里的JSON。3.2 时间戳清洗毫秒、字符串和时区那些事接下来是清洗环节这一步最容易出问题。先处理msPlayed。这个字段单位是毫秒很多人第一次直接拿它出来当秒算结果发现自己单曲循环某首歌时长的数字小得离谱。正确做法是转成分钟保留两位小数df[minutes_played] df[msPlayed] / 1000 / 60 df[minutes_played] df[minutes_played].round(2)然后处理endTime。这个字段是字符串要转成真正的datetime类型才能按小时、月份做聚合df[endTime] pd.to_datetime(df[endTime])这里有个容易踩的坑endTime字符串本身不带时区信息但Spotify导出时记录的是当地时间还是UTC不同账号情况可能不一样。我的经验是先看看最早一条记录的endTime是几点如果和你印象中的深夜/凌晨习惯明显对不上可能就是UTC时间需要加一个偏移# 如果确认数据是UTC想转成UTC8之类的本地时间 df[endTime] df[endTime].dt.tz_localize(UTC).dt.tz_convert(Asia/Shanghai)tz_localize是给这列时间戴上UTC的帽子tz_convert是把这个时区的时间换算成目标时区。不要搞反顺序顺序反了会报错或者得到毫无意义的时间。我自己就因为这个时区转换吃过亏当时画出来的24小时分布图整片往前面平移了8个小时折腾了很久才发现是时区没转。清洗完顺手加几个辅助字段小时、月份、年份、星期几后面聚合的时候直接用df[hour] df[endTime].dt.hour df[month] df[endTime].dt.month df[year] df[endTime].dt.year df[weekday] df[endTime].dt.dayofweek还有个常见情况msPlayed非常短的记录比如1秒钟就跳到下一首这多半是切歌或自动播放的干扰数据保留反而会影响时长统计的准确性。我建议加一步过滤df df[df[msPlayed] 10000] # 只保留听了10秒以上的记录要不要过滤取决于你的分析目标。如果是统计播放次数可能想把所有记录都算上如果是统计实际听歌时长过滤掉短时间跳过的记录更合理。我在做年度趋势分析时用的是过滤后的版本因为时长连续性更有意义。3.3 基础统计你的听歌总量到底是什么量级清洗完成之后数据已经变成了干净的事件表。这时候可以先做一波全局统计给自己一个总览total_songs len(df) total_minutes df[minutes_played].sum() unique_tracks df[trackName].nunique() unique_artists df[artistName].nunique() print(f累计播放 {total_songs} 次) print(f累计收听时长约 {total_minutes / 60:.1f} 小时) print(f涉及 {unique_tracks} 首不同歌曲) print(f涉及 {unique_artists} 位不同歌手)这几个数字一出你的听歌画像的大盘子就出来了。我当时算完发现累计时长比自己预想的多很多原因是我经常一边做别的项目一边挂着Spotify导致后台播放的记录也算进去了。记住一个点这里的时长是你真实产生播放记录的总和不代表你一定专心在听。3.4 画图用几张图表把听歌习惯可视化出来有了聚合好的数据画图阶段反而是最轻松的。我用matplotlib画几张我认为最有信息量的图歌手歌单占比、一天24小时的听歌分布、每年的听歌时长趋势。先看歌手Top榜。聚合出每个歌手的累计播放时长取前15名import matplotlib.pyplot as plt # 设置中文字体不然图表里中文全是方块 plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei, PingFang SC] plt.rcParams[axes.unicode_minus] False top_artists ( df.groupby(artistName)[minutes_played] .sum() .sort_values(ascendingFalse) .head(15) ) plt.figure(figsize(10, 8)) top_artists.sort_values().plot(kindbarh, color#1DB954) # Spotify绿 plt.xlabel(累计播放时长分钟) plt.title(我的Spotify Top 15歌手按播放时长) plt.tight_layout() plt.savefig(top_artists.png, dpi150)用barh横向条形图而不是纵向是因为歌手名字一般偏长横向排列更易读。我习惯把累计时长而不是播放次数作为排序依据因为时长更能反映你在它身上花了多久。当然也可以补一张按次数的版本两者结合起来看更有意思——如果一个歌手播放次数很多但累计时长不高可能说明他的歌普遍偏短。接着看24小时分布。我深夜听歌比例一直很高所以这张图对我冲击力很大hourly df.groupby(hour)[minutes_played].sum() plt.figure(figsize(12, 5)) plt.bar(hourly.index, hourly.values, color#1DB954) plt.xticks(range(0, 24)) plt.xlabel(小时) plt.ylabel(累计播放时长分钟) plt.title(一天24小时听歌分布) plt.tight_layout() plt.savefig(hourly_distribution.png, dpi150)如果你想把趋势拉平一点可以加一个滚动平均。比如用hourly.rolling(3, centerTrue).mean()来平滑相邻小时的波动能看到更清楚的时间段偏好。这个技巧对同一批数据做概览型图表特别管用。年度趋势可以画个折线图按年份和月份聚合monthly ( df.groupby([df[endTime].dt.to_period(M)])[minutes_played] .sum() ) plt.figure(figsize(14, 5)) monthly.plot(markero, linewidth1.5, color#1DB954) plt.title(每月累计听歌时长趋势) plt.xlabel(月份) plt.ylabel(累计播放时长分钟) plt.grid(True, alpha0.3) plt.tight_layout() plt.savefig(monthly_trend.png, dpi150)如果你发现折线图横坐标挤成一团这个非常常见有两个立竿见影的办法一是用plt.xticks(rotation45)把日期斜着展示二是别把to_period(M)的结果直接当横轴刻度先生成字符串列表再每隔几个坐标轴落一个刻度。我一般用后者效果更干净。4. 那些最容易翻车的细节和排查技巧4.1 常见问题速查表照着对照就好数据分析项目里代码报错并不可怕怕的是报错信息看不懂。我把自己踩过的坑整理成了一张速查表你复现的时候如果遇到类似问题可以直接对照。现象可能原因解决办法pd.read_json解析失败或读到空表路径不对或文件不是StreamingHistory类JSON用glob打印完整路径确认文件和脚本的相对位置endTime解析出来全是NaT字符串格式和to_datetime默认格式不匹配先打印几行原始值必要时指定format%Y-%m-%d %H:%M:%S24小时分布图整体偏移时区未转换或转错方向确认导出数据是UTC还是本地时间用tz_localize配tz_convert图表里中文全部显示为方框matplotlib缺少中文字体设置plt.rcParams[font.sans-serif]为系统可用中文字体重启内核生效折线图横坐标密集得看不出趋势时间粒度太细改用字符串刻度或对序列做rolling平滑再旋转刻度标签总时长比想象中多出很多后台自动播放也算记录判断是否过滤低于10秒的记录以及是否只统计主动播放多个文件拼接后行数对不上部分文件在子目录中用glob.glob(**/StreamingHistory*.json, recursiveTrue)递归查找这张表基本覆盖了90%的日常问题。还有一个值得单独拎出来的点如果你编辑或处理原始JSON文件一定要小心不要用Excel直接打开保存。Excel对JSON的格式化处理会把原始结构改得乱七八糟之后脚本读出来的数据可能全是错位。我的习惯是代码里做任何分析都用只读方式不在Excel里折腾原始数据。4.2 我实操中踩过的五个坑写下来给后来人第一个坑是单位混淆。我第一次分析的时候天真的以为msPlayed是秒直接除以60就算成分钟了导致所有数字偏大60倍。毫秒和秒的换算这种事错起来特别隐蔽因为图表整体形状不变只是量级离谱。建议在清洗环节输出几行样例人工核对一条数据一首3分半的歌msPlayed应该大约在210000左右。先验证再往下走。第二个坑是过滤逻辑写得太激进。我一开始把msPlayed 30000的记录全删了结果发现某场直播回放的整轨歌曲因为技术原因只记录了十几秒被我误删了。数据清洗的粒度要跟着目标走而不是拍脑袋定阈值。第三个坑是画图前忘了看数据量级。有一次我画了Top 100歌手结果图上一大半都是只有一个短歌的单曲艺人信息量稀碎完全没法看。后来我加了两个约束条件累计播放时长超过x分钟、播放次数超过x次才画出真正有意义的榜单。做可视化之前一定要先看看分布再决定展示哪些数据。第四个坑是groupby之后忘了reset_index。pandas的groupby结果如果直接用于画图有时索引会是歌手名或时间图是能出但整个操作链再往后需要这些列时就会报KeyError。建议在聚合后统一reset_index()让列结构规整化。第五个坑是过度追求好看的图表。我一开始塞了很多颜色渐变、网格阴影、标签强调进去结果图又重又乱。数据可视化的目的是让数字自己说话不是堆视觉效果。后来我做了一套极简风格统一用Spotify那个绿浅色背景标题简短。信息反而清晰了。4.3 除了静态图表这批数据还能玩出什么花样当你完成了上面这些基础分析这批数据其实还远没有榨干。我后来在此基础上做过几个延伸方向都挺有意思音乐特征联动把清洗后的歌曲列表提交给Spotify官方Web API按trackId去查每首歌的energy、danceability、valence这些音频特征字段从而分析出我平时听的歌到底是偏沉重还是偏愉悦我做项目时听的歌是不是节奏更快这类结论。这个属于进阶玩法需要申请API权限但没有技术壁垒每一步都有官方文档可查。年度听歌摘要把year维度聚合出的结果包装成一份个性化的年度报告比如某年某月某日你集中听了哪张专辑深夜播放次数最多的歌Top 10。自动生成播放列表从历史高频歌曲里挑出你一个月没听的歌结合时长筛选生成重返记忆类的歌单。打包成exe如果你以后想在没装Python的电脑上给朋友展示效果用pyinstaller把脚本打包成可执行文件也是一种选择。这些方向都不需要太高深的技术栈本质上都是在清洗过的干净表上加不同的聚合和关联逻辑。数据分析项目最有魅力的地方就在这里——数据准备好了想象空间是无限的。我个人的体会是这类个人数据分析和写给别人用的系统很不一样。它不需要担心高并发不需要设计复杂的存储结构唯一重要的就是你对你自己的数据有多少好奇心。拿到Spotify导出文件的那一刻我本来只想看看Top歌手是谁结果最后在深夜一边改代码一边发现自己在凌晨两点听得最多的歌其实无比安静这种反馈特别真实。如果你也是那种喜欢琢磨数据的Spotify用户强烈建议找点时间导一份自己的数据试试。动手之后你会发现能讲出故事的往往不是那些最热门的歌而是你自己真正的习惯。
返回列表