用Python分析Spotify听歌数据:从API授权到可视化完整实战 年底各大平台都会给你端出年度报告卡片做得一年比一年精致可数字背后到底是怎么算出来的平台基本不会告诉你也不会把原始数据交给你。作为一个把耳机当器官的人我对Spotify这些年攒下的听歌数据一直充满好奇深夜单曲循环的到底是哪几首过去一年里哪个艺人占据了我的耳朵排行榜第一名听歌时段分布和心情波动有没有规律带着这一堆疑问我干脆用Python写了一套自己的分析脚本把Spotify听歌数据完整拉出来、清洗、统计、可视化顺便把从Python安装、申请API到出图的整条链路都摸了一遍。这篇文章就把这套流程完整拆开不光是教你怎么调接口还会把每一行代码背后的设计逻辑讲清楚适合两类人——想看看平台年度报告之外真实数据的Spotify重度用户和正缺一个有真实业务场景练手的Python数据分析初学者。1. 项目思路与整体设计你想从听歌数据里看到什么1.1 平台年度报告之外API能给你什么Spotify自己的年度总结做得越来越好但它的本质是平台用自家算法挑选出来的“宣传页”而不是一份可供你自由分析的原始报表。比如年度报告中会告诉你“你今年听了300小时音乐”却不会告诉你这300小时分布在哪一天、哪个时段、哪一首歌被你循环了多少遍。如果你只想看结论平台报告完全够用但如果你想回答“我到底在什么情绪下听什么歌”“我的音乐品味是不是真的偏向某个风格”就必须回到数据源头。Spotify Web API恰好提供了这个入口。通过API你能拿到播放时间戳、曲目与艺人信息、流派标签以及一组被叫做“音频特征”的量化字段。把这些数据拉下来之后你可以去回答的平台报告里根本不会出现的问题一周七天里我哪天听歌最多每天哪个小时是耳机最忙的时间我的播放列表里是否有重复率极高的“耳朵单曲循环区”按播放次数排序和按累计时长排序结果差异有多大我喜欢的歌在danceability、energy、valence这些音频特征上是平均值还是偏科这些问题的答案组合起来就是你个人的“音乐肖像”。我第一次跑完脚本时最大感受是平台觉得我是某某流派的乐迷但从数据看我的情绪波动远比一个标签复杂。这是API比年度报告更有价值的地方——它不替你讲故事只把原材料摆在你面前。1.2 整体技术链从HTTP请求到图表只需四个工具这个项目并不需要搭一个什么庞大的数据处理平台相反我把技术栈刻意控制在一套很轻的组合里Python作为主语言spotipy负责封装Spotify API的授权与请求pandas负责清洗和聚合matplotlib负责画图。如果中途想调试分段数据也可以顺手装一个Jupyter Notebook跑交互代码但完全不是必需项。为什么选这套组合因为“用Python分析听歌数据”这个项目的核心目标不是炫技而是让我能清楚看到从“拉取数据”到“生成图表”的每一个环节。spotipy比直接用requests调接口简单得多它内部把OAuth授权、请求头、参数拼接和异常处理都做掉了省去大量重复代码pandas是表格数据处理的标准工具groupby、pivot_table这类操作几行就能完成matplotlib虽然画出的图不是最花哨的但胜在可控性强、依赖少适合做个人分析项目。如果你是非技术背景、刚开始接触Python我也建议用这套组合。图省事可以去找现成的听歌统计网站但自己走一遍流程之后你对“数据是怎么变成结论”的理解是完全不一样的。整条链路跑通一次你等于同时实践了API调用、数据清洗、聚合统计、可视化四件事这比单纯刷语法题有用得多。2. 环境准备与Spotify API接入从Python安装到OAuth授权一次跑通2.1 先搭开发环境Python版本与虚拟环境选择项目的第一个实际门槛是Python安装。目前这个分析脚本对Python版本要求不苛刻3.9到3.12之间都能跑我建议直接装当前较新的3.11或3.12版本。在官网下载安装包时有一个细节很多人会忽略安装向导第一步务必勾选“Add Python to PATH”否则后面在命令行敲python会提示找不到命令还得回头手动改系统环境变量非常折腾。Python装好之后我强烈建议为这个项目单独建一个虚拟环境而不是直接往全局环境里塞包。虚拟环境相当于给项目隔出了一个独立的小房间里面装的库版本不会和其他项目互相干扰。命令行里执行python -m venv .venv # macOS / Linux 激活方式 source .venv/bin/activate # Windows 激活方式 .venv\Scripts\activate激活后命令行提示符前面会出现(.venv)字样这就代表你已经进入虚拟环境了。接着安装项目依赖pip install spotipy pandas matplotlib seabornspotipy是Spotify的Python SDKpandas负责数据处理matplotlib和seaborn负责可视化。如果你打算用Jupyter Notebook边写边调可以再执行一句pip install jupyter。这里顺便说一句有些同学的电脑里可能已经装过Anaconda那环境问题就更好解决直接用conda创建新环境即可思路完全一致。这个项目不是大型工程环境准备阶段控制在十分钟以内比较合理如果卡在这一步太久后面容易泄气。2.2 在Spotify开发者平台创建应用拿到两个关键凭证要用Spotify API前提是在开发者平台创建一个应用。整个流程不复杂大致是登录Spotify账号进入开发者平台点击Create App填写应用名称和描述在Redirect URI一栏填上http://localhost:8888/callback然后创建。创建完成后进入应用详情页你会看到一串Client ID以及一个默认隐藏、需要点击“Show client secret”才会展开的Client Secret。这两个字符串就是后面访问API的钥匙。Client ID相当于你的应用标识Client Secret相当于密码它们会通过OAuth授权流程用来换取访问令牌。有个容易踩坑的地方是Redirect URI。Spotify只允许回调地址与你登记在开发者平台上的地址一致如果两边差一个符号授权时就会报错。我统一用http://localhost:8888/callback是因为本地回调比较灵活不需要一个真实部署的服务器地址。理解这一点后你后面看到OAuth报错时也就知道往哪个方向排查了。这两个凭证最好不要直接硬编码在脚本里。常见做法是通过环境变量读取避免脚本被分享出去时把秘密一起泄露。比如在命令行里先设置SPOTIFY_CLIENT_ID和SPOTIFY_CLIENT_SECRET两个变量然后在Python代码里用os.getenv读取。2.3 用spotipy完成授权OAuth流程背后发生了什么Spotify API使用的是OAuth授权流程对新手来说第一次遇到可能会觉得绕但把它拆开其实很简单。你可以把一个桌面应用想象成一个餐厅服务员他不能直接拿你的银行卡密码去结账必须先经过你本人的授权餐厅才能从你账户里扣钱。OAuth就是这套授权机制你的Spotify账号相当于银行Python脚本相当于餐厅回调地址相当于结账时服务员递过来的小票。spotipy把大部分细节都封装好了。我习惯用手动粘贴回调URL的模式因为这种模式对你理解整个流程最有帮助而且不依赖本机额外开启端口。代码如下import os import spotipy from spotipy.oauth2 import SpotifyOAuth scope user-top-read user-read-recently-played sp_oauth SpotifyOAuth( client_idos.getenv(SPOTIFY_CLIENT_ID), client_secretos.getenv(SPOTIFY_CLIENT_SECRET), redirect_urihttp://localhost:8888/callback, scopescope, cache_path.spotify_token, ) auth_url sp_oauth.get_authorize_url() print(请打开下面网址完成授权, auth_url) callback_url input(授权完成后粘贴浏览器地址栏里的完整URL) code sp_oauth.parse_response_code(callback_url) token_info sp_oauth.get_access_token(code) sp spotipy.Spotify(auth_managersp_oauth) print(授权成功当前用户, sp.me()[display_name])这里scope参数很重要它决定了脚本能读取你哪些数据。我通常只申请user-top-read和user-read-recently-played因为这两个权限足够拿到热门曲目、热门艺人和最近播放记录。如果需要读取收藏再额外加一个user-library-read。原则是权限能少则少自己的数据也不要在授权页面上大放送。第一次运行脚本时浏览器会打开Spotify授权页面点击同意后地址栏会跳转到一个长URL把那串完整URL复制回来粘贴进终端即可。spotipy拿到授权码后会自动换出access token和refresh token并且通过cache_path参数把token信息缓存到本地文件.spotify_token里。这样第二次运行时你不需要重新授权脚本会自动读取缓存只有当token真正过期时才会提示重新走一遍流程。3. 数据抓取与清洗把API返回变成可以分析的表格3.1 抓取顶部曲目、艺人、最近播放记录授权成功后就可以开始抓数据了。Spotify API里的常用接口有三个当前用户的热门艺人current_user_top_artists、热门曲目current_user_top_tracks、最近播放记录current_user_recently_played。前两个接口支持一个有意思的时间范围参数time_range可选short_term、medium_term、long_term分别对应近一个月、近半年、长期以来的收听习惯。我用一个函数封装最近播放记录的获取逻辑直接把结果转换成pandas的DataFrameimport pandas as pd def get_recent_tracks(sp, limit50): results sp.current_user_recently_played(limitlimit) records [] for item in results[items]: track item[track] records.append({ song: track[name], artist: track[artists][0][name], album: track[album][name], song_id: track[id], played_at: item[played_at], duration_ms: track[duration_ms], }) return pd.DataFrame(records) recent_df get_recent_tracks(sp, limit50) print(recent_df.head())如果感觉50条不够可以在函数外部循环翻页把多页数据合并起来。翻页机制在后面的分析里同样会用到我一般写成循环加sleep的结构避免一口气请求太多接口触发限流。获取热门艺人和热门曲目的代码也类似top_artists sp.current_user_top_artists(limit50, time_rangelong_term) top_tracks sp.current_user_top_tracks(limit50, time_rangelong_term) for idx, artist in enumerate(top_artists[items]): print(idx 1, artist[name], artist[genres])第一次拉数据的时候你会发现返回值是一个嵌套很深的JSON结构曲目的artists字段还包含了一个列表。这里有个新手很容易犯的错我一开始只取track[artists][0][name]遇到合作曲目时就会丢失第二、第三个艺人信息。后来我改成把所有艺人名用逗号拼起来分析结果才准确。3.2 音频特征是什么danceability、energy、valence等关键词播放记录只能告诉你“听了什么、什么时候听的”却不能直接告诉你“你为什么会喜欢这类歌”。要回答后者就得靠音频特征字段。Spotify给每首歌都标了一组0到1之间的数值你可以把它理解成平台算法对歌曲气质做的量化描述。我常用的几个指标是danceability表示歌曲适合跟着律动的程度数值越高节奏感越强适合跳舞energy是活跃度越高越让人觉得充满爆发力valence是情绪积极性高valence的歌听起来明亮、快乐低valence的歌往往带着悲伤或压抑的情绪acousticness是原声程度越高代表越偏向木吉他、钢琴之类的原声录音strumentalness表示无人声的比例越高说明越偏纯音乐speechiness则表示歌词像说话而非唱的比例说唱和脱口秀类内容的数值会偏高。你可以把这些字段当成人格测试的维度。有人听歌偏好高能量低valence可能说明日常更依赖音乐来释放压力有人acousticness常年偏高可能更享受安静的场景音乐。我第一次看到自己的平均数值时发现自己听歌的valence常年偏低但energy不低这才意识到自己总在情绪复杂时选择那些有劲儿却略带拧巴的歌。获取这些字段很简单拿到曲目ID列表后调用audio_features接口即可ids [track[song_id] for track in top_tracks[items][:50]] features sp.audio_features(ids) for song, f in zip([t[name] for t in top_tracks[items][:50]], features): if f: print(song, f[danceability], f[energy], f[valence])需要注意接口一次最多传50个ID所以我用切片取前50个。如果歌单长度超过50需要分批调用后再合并。3.3 清洗和结构化从嵌套JSON到干净的DataFrameAPI返回的数据并不直接适合分析缺值、重复、乱序都是常态。我拿到数据后会先做三件事时间标准化、去重、补充衍生字段。第一步把played_at字符串转成pandas的时间类型然后从中拆出小时、星期几这些分析维度。有一个坑必须提醒Spotify返回的时间戳是UTC时区的不是你的本地时间。如果你的使用场景在非UTC时区直接按dt.hour统计会得到完全不一样的结果。我通常先tz_convert到自己所在时区再提取小时字段。recent_df[played_at] pd.to_datetime(recent_df[played_at]) recent_df[played_at] recent_df[played_at].dt.tz_convert(Asia/Shanghai) recent_df[hour] recent_df[played_at].dt.hour recent_df[weekday] recent_df[played_at].dt.dayofweek第二步去重。最近播放记录里可能出现同一首歌在几秒内被重复记录或同一分钟出现两条相同记录的情况所以我会用drop_duplicates去掉完全相同的行。如果你的目标是统计播放次数而不是播放事件数这一步要格外注意因为按artist聚合时重复数据会直接影响排名。第三步把音频特征字段加入主DataFrame。我一般用曲目ID作为关联键把audio_features返回的结果merge进来。最终形成的表大致长这样每一行是一次播放事件包含歌名、艺人、播放时间、时长以及这首歌的danceability、energy、valence等音频特征。这个表就是后续所有分析的地基。4. 核心分析从不同维度拆解你的听歌习惯4.1 按小时拆解你的耳机在一天里哪个时段最忙拿到干净的DataFrame后第一件让我觉得有意思的分析是按播放时间分组。把“小时”作为分组字段统计每个小时内的播放次数能很清楚看到自己的收听节奏。hourly recent_df.groupby(hour)[song].count().reset_index() hourly.columns [hour, play_count] top_hour hourly.sort_values(play_count, ascendingFalse).head(5) print(top_hour)我自己的分析结果显示晚上22点到23点是我播放量最高的时段工作日早上9点到10点还有一个明显的小高峰。对照一下自己的生活规律其实完全对得上上午一般戴着耳机写文档晚上则是放松时间音乐当背景音。数据最大的魅力就在这里它不是告诉我“你爱听什么”而是告诉我“你在什么状态下更依赖音乐”。如果你想看得更细可以进一步把工作日和周末分开分别统计每小时播放量。做法是在groupby之前先增加一列is_weekend然后按is_weekend和hour两个字段一起分组。这样能对比出通勤日的音乐选择和休息日有什么区别分析维度一下就丰富起来了。4.2 Top N分析用播放次数和总时长双维度排座次听歌排行是所有人都想看的榜单但这里有一个容易忽略的细节按歌曲出现次数排名和按艺人出现次数排名得到的结果并不一样。我习惯把两套榜单一起算因为它们回答的问题完全不同。次数榜单计算的是“谁出现的频率最高”artist_play_count recent_df.groupby(artist)[song].count().sort_values(ascendingFalse).head(10)时长榜单计算的是“谁占据了我耳朵的时间最多”artist_duration recent_df.groupby(artist)[duration_ms].sum() / 60000 top_duration artist_duration.sort_values(ascendingFalse).head(10)对比这两份榜单你会发现一些有趣的错位。比如某个歌手的歌平均只有两分钟但因为无限循环所以次数排名第一另一个歌手单曲动辄七八分钟播放次数没那么高累计时长却悄无声息地爬到了前面。这种错位恰恰能反映你的收听习惯你是在找“短平快的刺激”还是愿意花一整张专辑的时间沉浸在长叙事里。同样的逻辑也适用于歌曲本身。你可以统计单曲播放次数和单曲累计播放时长两个指标再分别取Top 10。如果一份榜单和另一份榜单有半壁江山重合说明你的听歌习惯相当稳定如果差异很大说明你既有高频切换的一面也有长期沉浸的一面。4.3 音乐人格画像用均值看看你的口味偏好把音频特征汇总成平均值可以得到一张抽象的“口味偏好画像”。我用下面这段代码把几个关键特征的平均值拉出来feature_cols [danceability, energy, valence, acousticness, instrumentalness, speechiness] feature_means recent_df[feature_cols].mean().round(3) print(feature_means)光看绝对值可能不好理解更好的做法是做对比。一种对比方法是用平台平均数据或好友的数据做参照另一种是把自己最近一个月的均值short_term和长期均值long_term放在一起比较看看音乐口味是否正在漂移。我就发现自己的short_term energy长期高于long_term说明最近几个月我听的歌越来越“吵”很可能是写稿压力变大了。如果你喜欢更直观的方式可以把自己的均值落在0到1的坐标轴上直接看哪个维度特别突出。拿我自己举例instrumentalness和acousticness一直偏低说明我的听歌口味整体偏向人声和录音室作品而speechiness适中说明纯说唱和纯音乐都不是我的主力区。这个结果和我在音乐平台上的标签基本吻合但多了一层量化解释顿时觉得自己懂点数据就是不一样。5. 可视化结果把表格里单调的数字变成音乐画像5.1 用条形图呈现年度Top歌手分析做到这里数据已经足够丰富了但一堆数字还是不如一张图直观。我做的第一个可视化是横向条形图用来展示Top 10艺人的播放次数。import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] # 如果系统没有SimHei换你电脑里的中文字体名 plt.rcParams[axes.unicode_minus] False top10 artist_play_count.head(10).sort_values() font_size 10 top10.plot(kindbarh, figsize(8, 6), color#1DB954) plt.title(播放次数 Top 10 艺人) plt.xlabel(播放次数) plt.tight_layout() plt.show()画图时最容易翻车的是中文显示。Windows下我用SimHei一般没问题macOS可以改成PingFang SC或者Arial Unicode MSLinux则可能需要安装额外的中文字体。如果第一次出图时中文全部变成方块别慌先检查当前系统有哪些中文字体然后把plt.rcParams[font.sans-serif]换成对应字体名就行。还有一个小细节负号是ASCII字符不设置axes.unicode_minus为False的话横轴上的负数可能变成乱码。5.2 用热力图看清一周听歌规律如果说柱状图适合看排名那么热力图更适合看二维规律。想知道“一周七天、每天24小时我的播放热度是怎么分布的”用pivot_table加imshow就能画出来。pivot recent_df.pivot_table( indexweekday, columnshour, valuessong, aggfunccount ).fillna(0) fig, ax plt.subplots(figsize(12, 5)) im ax.imshow(pivot, cmapviridis, aspectauto) ax.set_xticks(range(24)) ax.set_yticks(range(7)) ax.set_yticklabels([周一, 周二, 周三, 周四, 周五, 周六, 周日]) plt.colorbar(im) plt.title(一周播放热度按小时) plt.show()这张图的信息密度非常高。一眼看过去哪个色块最深就是你一周里最依赖耳机的时间段。我自己的热力图在周五深夜和周六上午各有一块亮斑这解释了一件事我周末起得并不早但醒来的第一反应很可能是放歌当背景音。热力图的可贵之处在于它不会美化你的行为它只诚实地记录你在什么时候反复打开播放器。5.3 用雷达图画一张“音乐人格”最后一个可视化是雷达图把前面算好的几个音频特征均值画成多边形的形状。做法是让每个坐标轴代表一个特征多边形的“胖瘦”直接反映你的口味倾向。import numpy as np labels [danceability, energy, valence, acousticness, instrumentalness, speechiness] values [recent_df[label].mean() for label in labels] values values[:1] angles np.linspace(0, 2 * np.pi, len(labels), endpointFalse).tolist() angles angles[:1] fig, ax plt.subplots(figsize(6, 6), subplot_kw{projection: polar}) ax.plot(angles, values, markero, linewidth2) ax.fill(angles, values, alpha0.25) ax.set_xticks(angles[:-1]) ax.set_xticklabels(labels) plt.title(我的音乐人格画像) plt.show()雷达图第一次画出来的时候我盯着看了一会儿感觉像是拿到了一份关于自己耳朵的体检报告。energy和danceability拉出去一大块acousticness缩在角落里感觉和我平时偏好的电子、摇滚风格是对得上的。如果你觉得自己的雷达图太扁基本说明你的口味过于集中在某几个类型后续反而可以单独拉出那些分布外的歌曲看看没准能找到一些被你忽略的宝藏。可视化环节的通用经验是不要一口气画五张图堆一起看而是每张图只回答“一个问题”。条形图回答“谁最多”热力图回答“什么时候听”雷达图回答“口味长什么样”。图少一点思考的时间反而多一点这个原则在个人数据分析项目里特别受用。6. 常见问题与避坑指南我踩过的坑全在这6.1 按错误信息分类从OAuth到HttpError整个项目最让人头秃的部分大概率集中在授权和请求阶段。我把遇到过的问题整理成一张表方便你报错时直接对照问题现象常见原因解决办法授权页跳转后报“invalid_client”Client Secret填错或没有设置环境变量检查开发者平台的应用设置是否和你代码里的环境变量一致授权成功后第二次运行又要求重新授权cache_path没设置或缓存文件权限有问题增加cache_path参数确认缓存文件未被删除调用接口报403当前scope权限不足或token失效检查scope是否包含对应接口权限删除旧缓存重新授权调用接口报404曲目ID或艺人ID有误或API地址拼错打印请求链接和参数确认ID确实存在中文标签在图中变成方块系统缺少对应中文字体改用系统中已安装的中文字体名并设置rcParams返回的数据量明显偏少API单次返回有上限且未做分页处理循环翻页把多页结果合并played_at时间与本地时间不一致API返回的是UTC时间用tz_convert转到本地时区再提取小时字段我特别强调一下scope权限问题。Spotify API的错误提示不算友好403错误并不总会在文案里写清楚到底是权限不足还是token过期。排查时先检查是不是权限问题再检查缓存token最后才去怀疑代码逻辑。这个排查顺序能省下很多时间。6.2 配额、Token过期与数据分页Spotify API对普通应用有请求频率限制虽然个人项目的量级通常很难触发但一旦写了一个循环去抓几千条历史记录就可能在某个瞬间收到429状态码。429的意思是请求太频繁服务器让你慢一点。解决方式很朴素在循环里加time.sleep(0.5)让每次请求之间间隔半秒实在不行就加退避逻辑遇到429后指数退避再重试。分页是另一个必然遇到的坑。current_user_top_artists和current_user_recently_played单次请求最多返回50条超出部分需要通过offset参数偏移。写一个循环翻页并不会让代码复杂很多但会让数据量从50变成500分析结果自然也稳定得多。Token过期问题也不少见。spotipy正常情况下会用refresh token自动续期但如果你把cache文件删了或者换了一台电脑运行脚本就需要重新授权。我的习惯是把.spotify_token文件纳入项目目录但不提交到公开仓库这样既避免重复授权也避免了token泄露。6.3 几个实用技巧缓存Token、控制请求频率、别只取第一位艺人关于缓存Token这里有个小技巧值得多说一句。当你拿到access token后spotipy会把token信息写到cache_path指定的文件里。第二次运行脚本时它会优先尝试刷新而不是重新弹授权页面。这看起来是默认行为但前提是你每次创建SpotifyOAuth时都传入同一个cache_path。如果你图省事不传默认会生成.cache文件乱用文件名的后果是每个缓存文件里的token源不一致会频繁触发重新授权。关于控制请求频率我自己的脚本里会额外记录“上次请求时间”如果两次请求间隔太短就自动sleep。直接一点的做法是写一个简单的装饰器或工具函数统一包住所有API调用。这样既不痛不痒地把接口调用频率限制住了后续想调整间隔也只要改一个地方。还有一个容易被忽略的字段是track[artists]。这是一个列表因为歌曲可能是多人合作。如果只取[0]任何合唱曲目都会被记到第一位艺人名下这会让Top艺人榜单产生误差。我之前就是这么干的结果某支组合的分身歌手莫名被统计成了单飞歌手。后来统一改成“把所有艺人名用逗号连接”并将“完整艺人组合”作为分组依据统计结果才恢复正常。最后再分享一个我自己习惯的做法每次跑完分析我会把最终生成的DataFrame保存成CSV文件放本地。这样做的好处是后续想换一种角度重新分析时不需要再发起API请求直接用本地数据跑既快又不消耗配额。你还可以在同一张CSV里叠加上月份的数据累积足够时长后做成更长期的趋势分析。我第一次跑完这套分析时最意外的发现不是某个歌手的排名而是自己一天内听歌时段的分布比想象中更规律。那之后我经常会把新拉下来的数据和旧数据做对比观察自己的音乐口味是不是正在慢慢变化。如果你也对这类问题感兴趣可以从最简单的一步开始装好Python创建完开发者应用用脚本拉下最近50条播放记录然后试着统计一下你最常听的5个歌手。做完这一步你离一张属于自己的音乐人格雷达图其实也就只剩两三个代码块的距离。