
1. 为什么要自己动手分析Spotify听歌数据每年年底大家都喜欢晒那个Spotify年度回顾但那毕竟是平台给你的总结能看到的维度太固定了。真正把你的听歌数据拿下来用自己的代码慢慢拆解你会发现很多有趣的东西你每天几点最沉迷音乐、哪些歌是被你突然听腻的、某个风格的歌到底占了你多少播放量。这个项目就是用Python把Spotify的听歌数据拉下来结合pandas做清洗和分析再用matplotlib或seaborn画出你能看得懂的图表。我能理解大家看到“分析数据”第一反应是“是不是很难”。其实上手难度比想象中低很多你只需要掌握一点Python基础语法知道DataFrame的基本操作再照着本文把代码跑通一遍就能拿到完整分析结果。如果你完全没接触过Python也没关系本文会从环境配置一路讲到可视化每一段代码都有解释跟着做就行。这个项目适合三类人想深入了解自己听歌习惯的音乐爱好者正在学Python数据分析、想找个真实数据集练手的学习者以及准备做个人数据项目、充实作品集的开发者。它能给你的不只是一堆图表而是一套“从数据获取到清洗再到可视化”的完整流程这套思路放到任何数据项目里都通用。我在做这个项目时最大的体会是难点从来不在写分析代码而在怎么把数据拿到手、怎么处理脏数据。所以下面文章会把数据获取的细节放在靠前的位置这部分坑最多也最容易被网上教程带偏。2. 环境准备Python与依赖库2.1 Python环境与虚拟环境创建做这个项目我推荐直接用Python 3.9以上的版本太老的环境容易在安装依赖时踩坑。如果你电脑上还没装Python去官网下载对应系统的安装包安装时记得勾选“Add Python to PATH”否则后面命令行里会找不到python命令。装好Python之后强烈建议建一个虚拟环境。很多人图省事直接全局安装依赖结果不同项目之间互相打架今天装这个库把另一个搞坏了明天又要卸载重来。用虚拟环境隔离每个项目的包是非常好的习惯。我习惯用Python自带的venv不需要额外安装别的工具mkdir spotify-analysis cd spotify-analysis python -m venv venv激活环境这一步在不同系统上写法不同macOS和Linux执行source venv/bin/activateWindows用户在命令行里执行venv\Scripts\activate激活后你会看到命令行前面多了个“(venv)”的标记这说明你已经在这个独立环境里了。后续装任何库都不会影响系统全局环境就算把环境删了重来也毫无负担。2.2 安装所需依赖库这个项目核心依赖只有四个pandas负责数据处理matplotlib和seaborn负责可视化spotipy负责调用Spotify官方接口。如果你选择走“官方数据导出”路线spotipy其实不是必需的但想用Web API补全额外特征数据时spotipy会非常省事。pip install pandas matplotlib seaborn spotipy安装过程中如果遇到网络慢的问题可以换成国内镜像源比如清华的pypi镜像速度会有明显提升。我装完后习惯先验证一下版本确保依赖正常import pandas as pd import matplotlib.pyplot as plt import seaborn as sns import spotipy print(pd.__version__) print(spotipy.__version__)能正常打印出版本号说明环境就绪了。这里有个小建议如果你用的是Jupyter Notebook来做分析建议额外安装一下ipykernel把当前虚拟环境注册进Jupyter这样在Notebook里也能用到刚才装好的环境体验会顺畅很多。3. 数据获取的两种主流路线3.1 路线一官方数据导出零门槛最省心Spotify提供了一个隐私设置里的“下载我的数据”功能你可以在账户页面申请导出完整数据。Spotify官方数据导出通常需要几天时间准备数据准备好后会以账号邮箱通知。导出的数据包里包含你的播放历史记录文件格式是JSON里面记录了从某个时间点起每一首歌的播放时间、曲名、艺术家、专辑等信息。拿到数据包后你会在文件夹里看到多个JSON文件听歌历史通常在一个叫StreamingHistory的文件里文件名可能带着数字编号。打开看你会发现它的结构非常简单每条记录包含四个字段{ endTime: 2024-05-12 14:30:12, artistName: 某位歌手, trackName: 某首歌名, msPlayed: 180000 }字段含义很直白endTime是这首歌播放结束的时间artistName是歌手名trackName是歌曲名msPlayed是这首歌实际播放了多少毫秒。这个路线的最大优点是零门槛不需要注册开发者账号不需要拿API密钥也不需要懂任何接口调用。你的历史记录甚至不用担心被平台判为异常访问。缺点是数据有延迟导出申请后要等待几天而且拿到的只有播放记录拿不到每首歌的音频特征数据比如能量值、节奏、声学度这些。想分析更深层的音乐特征就需要走第二条路线。3.2 路线二Spotipy调用Web API补充高价值特征Spotify有一个Web API里面包含了非常丰富的音乐元数据和音频分析指标。比如每首歌的danceability舞曲感、energy能量、acousticness原声感、valence积极度等维度这些数据是官方的“音乐基因”可以用来研究你为什么喜欢某些歌。调用API需要在Spotify开发者后台创建一个应用拿到Client ID和Client Secret。创建过程非常简单登录后点创建应用填写应用名称和描述就能拿到两个字符串。要注意的是如果你只想用“音频特征”这个接口可以通过Client Credentials流程直接获取token不需要用户授权代码写起来简单不少。用spotipy实现这个流程非常直接import spotipy from spotipy.oauth2 import SpotifyClientCredentials client_id 你的Client ID client_secret 你的Client Secret auth_manager SpotifyClientCredentials(client_idclient_id, client_secretclient_secret) sp spotipy.Spotify(auth_managerauth_manager)拿到sp对象之后你可以通过曲目的Spotify ID来查询音频特征。问题是你自己导出的播放历史里没有Spotify ID只有歌曲名和歌手名。这时候就要用搜索接口把名字转成ID再把ID批量喂给音频特征接口。我实际操作时发现直接搜歌名经常遇到同名歌、不同版本的问题单纯用trackName去搜很容易匹配错。更稳的方法是拿“trackName 空格 artistName”作为关键词搜索再取搜索结果的第一条。即便这样仍然会有极少数匹配不上因为Spotify上同一个歌手可能有不同专辑版本、Live版等。我的处理办法是匹配不上的先留空等到后续清洗时再决定是否删除千万不要为了凑数据瞎匹配。3.3 两条路线怎么配合使用单纯用导出数据你能做播放量排行、时段分析、歌手排名这些基础分析。单纯用API你拿不到完整的历史播放记录。所以一个完整的项目通常是两条路配合先用导出数据拿到播放历史再根据歌曲名和歌手名去API请求音频特征最后把两张数据表按歌曲维度拼接起来。这条路听起来完美实操时有个很现实的问题API有速率限制免费额度下每分钟请求次数有限如果你的播放历史里有几千首不重复的歌一个一个搜ID再加一个一个查特征可能要跑很久。我第一次跑全量数据时因为没做缓存跑到一半触发了限流整个脚本挂掉之前查过的结果全丢了。后来我把查询过的结果实时存到本地JSON里断点续跑才把几千首歌完整跑完。后面“常见问题”章节我会详细说缓存技巧。4. 从原始数据到可用数据集的清洗加工4.1 处理播放历史的核心清洗逻辑拿到原始JSON后第一步是把数据加载进pandas这是后续所有分析的基础。不同年份的导出文件可能是多个StreamingHistory文件读取时可以做一个巧妙合并import json import pandas as pd records [] # 假设所有StreamingHistory文件在同一个文件夹 for filename in [StreamingHistory0.json, StreamingHistory1.json]: with open(filename, encodingutf-8) as f: data json.load(f) records.extend(data) df pd.DataFrame(records) print(df.shape) print(df.head())load到的DataFrame里每行是一次播放记录但这里有几个很常见的问题一是播放时间不足30秒的记录有很多可能是切歌或误触播放这类数据会污染分析结果二是同一首歌可能被反复播放很多次但播放时长差异巨大三是歌曲名里可能存在空格、大小写不一致等导致统计时同一首歌被分成多条。我清洗时的策略是先删掉msPlayed小于30000的记录也就是播放少于30秒的不算有效播放。然后对trackName和artistName做规整化处理统一转成小写并去除多余的空白字符。最后再根据歌手名与歌名组合生成一个“歌曲唯一标识”用于后续去重和统计。df df[df[msPlayed] 30000].copy() df[track_clean] df[trackName].str.strip().str.lower() df[artist_clean] df[artistName].str.strip().str.lower() df[song_key] df[track_clean] - df[artist_clean]这一步看起来简单但它决定了后面统计的准确性。如果你跳过清洗直接做统计榜单里会出现一堆因为大小写不同而重复的同一首歌数据会很难看。4.2 时间字段与时区处理的细节Spotify导出的endTime字段是UTC时间。很多人做时段分析时忽略了这一点导致画的“24小时听歌分布”永远是歪的因为你的作息时间和UTC时间差了好几个小时。处理方式是先把endTime解析成datetime对象再转换成本地时区。如果你在中国需要在原时间上加上8小时很直接df[end_time] pd.to_datetime(df[endTime]) df[local_time] df[end_time] pd.Timedelta(hours8) df[hour] df[local_time].dt.hour df[weekday] df[local_time].dt.dayofweek df[date] df[local_time].dt.date做完时区处理后再提取小时、星期几、日期三个字段。之所以要提取这么多时间维度是因为后续能画出各种各样的图按小时可以看到你的凌晨和晚上听歌习惯按星期几可以看到工作日和周末的差异按日期可以看到一年之中哪些天你听歌特别多。这里有个小坑我一开始没注意某个播放记录如果发生在凌晨0点30分它的endTime可能是当天的0点30分但播放开始实际上可能是前一天深夜。这意味着跨天时午夜前后的歌曲会被分到不同日期里但按小时统计不受影响。4.3 特征工程构造分析维度原始数据里只有歌手、歌名、时长三个维度想要看出更有洞察力的结果你需要自己制造特征。我这里说的“特征工程”不是机器学习里那种复杂过程而是从现有字段中造出更有分析价值的字段。最常用的两个衍生字段是播放次数和有效播放时长。把同一个song_key分组统计次数就能得到“反复播放的歌曲”排行。把msPlayed累加再除以3600000换算成小时就知道某位歌手占了你多少小时的收听时间。另一个更有意思的维度是“收听趋势变化”。你可以按周或按月份把播放记录聚合起来算出每周播放总时长从而观察自己听歌热情在一年中的波动。比如考试周、出差季、心情低落的那段时间听歌时长往往会有明显变化这些规律被数字呈现出来时你会对自己的生活有新的理解。还有一点值得尝试把“第一次听到某首歌的时间”也算出来。这个字段很简单就是同一首歌在播放记录里的最小endTime。拿到它之后你可以分析一首歌从第一次听到最后一次播放之间隔了多久配合播放次数就能判断哪些歌是反复上头回味的哪些是听过一次就再也不碰的。5. 听歌行为的可视化分析5.1 24小时听歌节奏与日历热力数据清洗完成后第一个值得画的就是24小时听歌分布图。这个图能直观反映出你一天中的音乐活跃时间。用matplotlib画一个简单的柱状图就能说明问题hour_counts df[hour].value_counts().sort_index() plt.figure(figsize(10, 5)) plt.bar(hour_counts.index, hour_counts.values, color#1DB954) plt.title(24小时听歌次数分布) plt.xlabel(小时) plt.ylabel(播放次数) plt.show()绿色是Spotify的品牌色我用它来保持风格统一。画出来后你大概率会看到两个明显的波峰一个是通勤路上一个是晚间休息。如果你早上没有波峰说明你的听歌习惯更多集中在夜间这个数据其实挺诚实的。如果你想把颗粒度做得更细可以用日历热力图。横轴是星期几纵轴是一年的第几周格子的颜色深浅代表该时段听歌次数的多少。这种图看起来复杂但用pivot_table加seaborn的heatmap就能搞定pivot df.pivot_table(indexweek, columnsweekday, valuesmsPlayed, aggfuncsum) sns.heatmap(pivot, cmapYlGnBu)这个视图能让你一眼看到哪一周特别能听歌哪几天完全空掉。比如节假日出去玩的那几天通常是深色的空白非常有意思。5.2 艺术家与流派排行按艺术家做聚合是最直观的排行榜。先按artistName分组计算播放次数和总时长再取前10名画横向条形图。横向条形图在排行场景下比竖向图好读得多因为歌手名字往往较长竖向图的x轴标签会挤成一团。artist_stats df.groupby(artist_clean).agg( 播放次数(msPlayed, count), 播放时长(msPlayed, sum) ).reset_index() artist_stats[播放时长小时] artist_stats[播放时长] / 3600000 top_artists artist_stats.sort_values(播放次数, ascendingFalse).head(10) plt.figure(figsize(10, 6)) sns.barplot(datatop_artists, x播放次数, yartist_clean, paletteviridis) plt.title(播放次数最多的前10位艺术家) plt.xlabel(播放次数) plt.ylabel() plt.show()只看播放次数有个问题某位歌手的歌都很短比如有些说唱歌手歌曲只有两分钟你会频繁切歌导致播放次数虚高。而另一些歌手的歌都是长曲播一次就顶别人两次。所以榜单我习惯同时看两个维度播放次数代表你主动切歌的频率播放总时长代表这位歌手真正占据了你耳朵的时间。两者完全可能给出不同答案这种矛盾本身就是值得留意的地方。流派分析需要你在前面搜索歌曲ID时额外获取一个信息艺术家对应的流派标签。Spotify API里的artist对象带有一个genres字段里面是该歌手的风格标签。虽然歌手可能有多个标签但取最主流的一个或者前两个基本够用。5.3 音频特征的多维分析音频特征是最能体现“官方数据比你自己的感觉更细腻”的部分。Spotify的音频特征里danceability舞曲感描述歌曲适合跳舞的程度energy能量描述歌曲的强度valence积极度描述歌曲传达的情绪积极程度。把这些特征和你自己的播放数据进行结合可以直接给你自己的听歌口味“做体检”。比如把你播放次数最多的前50首歌的特征均值和你全部播放记录的特征均值做对比如果前50首歌的energy明显高于整体说明你偏好在日常选择中寻找更刺激、更有活力的歌曲。如果valence偏低说明总有一天你其实在靠伤感歌曲渡过的。画这类对比图我用的是分组箱线图一组是“高频歌曲”一组是“全部歌曲”比较它们的energy、valence和acousticness等指标分布。箱线图能看到中位数和离散程度对于听歌口味这种主观但又有统计规律的数据特别合适。还有个我特别推荐的做法是做一个歌曲特征散点图横轴是energy纵轴是valence每个点代表一首歌点的大小表示你的播放次数。这样你可以直观看到自己反复播放的歌集中在哪个情绪象限。我做完自己的图之后发现高频歌曲集中在“高能量、中高积极度”区域说明我每天听歌更多是为了激活状态而不是寻求安慰这确实符合我对自己的认知。6. 实操中常见的问题与排查经验6.1 API认证与权限问题spotipy调用Web API时最容易出错的是认证范围配置。如果你用的是ClientCredentials流程权限只能访问公开数据和音频特征无法读取你个人的播放历史这是设计如此。这也是为什么我前面强调历史播放数据要走导出路线千万不要试图直接用API拉个人历史记录。如果你后续想扩展项目比如做“最近一个月播放排行”需要调用用户私人播放记录功能那就必须走Authorization Code流程让用户授权。这个流程比ClientCredentials复杂很多要用到Spotify OAuth的授权页面拿到授权码后再换token。如果你遇到HTTP 401错误先检查是不是token过期了。Access Token的有效期通常只有一小时过期后需要刷新如果没做刷新逻辑跑长任务一定会中途挂掉。6.2 数据缺失与时区错乱官方导出数据偶尔会缺字段。我遇到过trackName和artistName都是正常的但msPlayed字段显示为0的情况。这种记录通常是用户手动跳过的直接删掉即可。还有一种情况是导出的JSON里有多余的空白字符或者不可见字符导致字符串匹配失败。时区问题我在前面提过一次但这里必须再强调一次endTime字段是UTC时间如果你的本地时区不是UTC一定要先换算再提取小时字段。我早期做分析时忘了这一步画出来的凌晨波峰出现在下午整个结论都是错的。这个错的结论我居然还保存了几天直到和实际感受对比时才反应过来。先验证数据流程再信任分析结果是所有数据项目的通用教训。6.3 关于数据量的几个实话Spotify官方数据导出默认给的时长范围可能不包含全部历史不同地区、不同账户类型的可导出范围略有差异。如果你发现导出数据里最早日期不是你注册日期别惊讶这是平台策略问题不是你的代码出错了。另一个现实问题是你播放过几百首不重复的歌但API请求有每分钟上限如果逐首查询音频特征整个过程可能非常漫长。我的解决办法是加一个本地缓存机制每查完一首歌就把结果存进字典然后序列化到JSON文件。下次运行时如果歌曲ID已经在缓存文件里就直接读取不再请求API。这样重跑一次脚本只需处理新增的歌曲不浪费请求次数。import os import json cache_file audio_features_cache.json cache {} if os.path.exists(cache_file): with open(cache_file, r, encodingutf-8) as f: cache json.load(f) for song_id in song_ids: if song_id in cache: continue try: features sp.audio_features(song_id) cache[song_id] features[0] if features else None except Exception as e: print(f查询失败: {song_id}, 错误: {e}) continue # 每查10首保存一次防止中途崩溃丢失进度 if len(cache) % 10 0: with open(cache_file, w, encodingutf-8) as f: json.dump(cache, f)这个脚本里最值得注意的一点是“每查10首保存一次”。不要等全部查完再保存因为一旦中间网络波动或限流导致脚本崩溃之前的劳动成果全部白费。分批写盘是最稳的办法。还有一个经验是搜索歌曲时要做好心理准备中文歌名和英文歌名在API搜索时表现不太一样。英文歌名通常直接加artistName就能精准匹配但中文歌曲常出现不同版本混在搜索结果里的情况有时候会把“现场版”或者“伴奏版”匹配进来。如果你发现某首歌的音频特征明显异常优先怀疑是匹配到了错误版本。我个人玩下来最大的体会是分析自己数据的过程本身就像一次自我观察它不同于看一个平台给你准备好的报告你需要自己清理一次数据自己处理一次时区自己排一次抱怨最后才会发现那些数字背后的生活轨迹。这套流程中你会经历所有数据分析项目都会遇到的真问题但因为你分析的是自己的数据好奇心会成为你最好的动力来源。如果你对这个项目有兴趣可以先拿自己导出的数据跑一遍最基础的清洗和统计等确认流程没问题了再一步步加入API请求、音频特征分析和可视化。这个项目扩展空间其实很大你可以基于同样的代码加入更多分析维度比如按播放时间快速看不同年代的歌手占比或者做一首歌的忠实程度排名也可以把分析结果部署成一个简单的本地网页报告。数据在那里问题在那里剩下的就看你什么时候开始了。