
直接从一个程序员兼重度音乐用户的角度聊这个事。先放下虚的用Python分析你自己的Spotify听歌数据是我做过性价比最高的一次个人数据项目没有之一。它不需要你懂什么高深的算法也不需要你花钱买服务器一台普通电脑、一个Spotify账号、几行Python代码就能把你过去一年甚至几年的听歌轨迹翻个底朝天你凌晨三点还在循环哪首歌哪个乐队被你冷落了大半年又突然回春通勤路上和深夜码代码时听的歌到底差多远这些都能看得清清楚楚。这篇分享不是我抄官方文档是我自己从零开始跑通的完整过程包括中间踩进去又爬出来的坑。文章会覆盖三个方面为什么这个项目比想象中有价值、具体怎么一步步拿到数据并做分析、以及我在实操中遇到的最典型问题和解决办法。我默认你已经装了Python但如果你只是刚接触Python也没关系我会在关键位置把依赖安装和基础用法一并说清楚。目标只有一个你照着做也能得到一份属于你自己的听歌行为画像。1. 整体设计与思路拆解为什么选API而不是别的方式很多人听到“分析Spotify数据”第一反应是去网上找那种导出的CSV文件或者用第三方网站生成的年度总结。这个思路不能说错但有两个硬伤一是隐私二是时效。Spotify官方从来没有开放过让第三方直接下载用户完整历史数据的入口你在第三方网站拿到的所谓报告本质上都是基于它当时截获的数据做的快照有的甚至需要你把账号授权给不明来历的服务风险不小。正确做法是走Spotify官方Web API。它是Spotify自己提供的一套RESTful接口注册一个开发者应用后通过OAuth授权拿到属于你自己的访问令牌然后就能拉取真实的、实时的、有完整元数据的播放记录。路径是Python → Spotipy库 → Spotify Web API → 用户最近播放记录 → 结构化整理 → 统计分析 → 可视化。Spotipy是这个流程里最关键的胶水层它是Spotify官方API的Python第三方封装把那些麻烦的认证握手、请求头构造、返回JSON解析全部简化成了几行函数调用。我选择Spotipy而不是自己用requests硬写原因有三个。第一OAuth流程极其繁琐手写容易出莫名其妙的token过期问题Spotipy把token自动刷新做了封装第二API的返回结构是嵌套很深的JSON新手光解析字段就得花半天Spotipy能直接以Python对象返回省掉大量时间第三Spotipy社区活跃遇到问题搜索一下基本都有答案。整套方案的依赖就四个核心库spotipy负责API对接pandas负责数据清洗和处理matplotlib和seaborn负责画图。如果你愿意再加一个wordcloud还能做出很有视觉冲击力的艺术家词云。这个项目的本质是“API数据获取 数据清洗 探索性数据分析”。你不需要机器学习不需要建模预测只需要掌握三个能力怎么发请求、怎么把脏数据变干净、怎么用图表把规律找出来。这三个能力放在任何一个数据岗位上都通用所以这个项目的学习杠杆极高做的过程顺便就把数据分析的完整工作流走了一遍。2. 准备工作与核心依赖配置先说环境。我建议直接用Anaconda或Miniconda管理Python环境如果只是分析数据装完就能用省得折腾系统级Python和依赖冲突。我自己的操作是新建了一个干净的conda环境Python版本用的3.10这个版本对pandas、spotipy这些库的兼容性都非常稳定。conda create -n spotify_analysis python3.10 conda activate spotify_analysis pip install spotipy pandas matplotlib seaborn wordcloud如果你不是用conda直接用原生的Python环境也没问题只要保证pip install能正常执行就行。这里有一个经验一定不要全局安装一定要为这个项目单独建虚拟环境。我刚开始入坑的时候图省事直接pip装到系统环境后来跑其他项目时发现某个库的版本被顶掉了排查了半天才意识到是全局依赖打架。做数据分析类项目虚拟环境是底线不是可选项。安装完之后建议先验证一下环境是否正常防止后面把认证问题和环境问题混在一起排查。打开Python交互式终端输入下面几行命令import spotipy import pandas as pd print(spotipy.__version__) print(pd.__version__)只要能看到版本号输出依赖层面就是健康的。接下来进入重头戏注册Spotify开发者应用并完成认证。2.1 注册开发者应用并获取密钥打开Spotify for Developers官网登录你的Spotify账号点击Dashboard然后创建新应用。名字随便填比如my-music-analysis关键设置在重定向URI这一步——这里必须填一个本地地址因为我们是本机脚本做OAuth回调常用的写法是http://localhost:8080。创建完成后在应用的Basic Information页面就能看到两个关键值Client ID和Client Secret这两个就是后续跟API握手通信的凭证一定不要泄露也不要传到公开的Git仓库里。我强烈建议把这两个值保存到环境变量而不是直接写死在代码里。你的代码可能会分享给朋友看或者上传到GitHub做备份如果把密钥明码写在脚本里就等于是把大门钥匙交出去了。Windows下用setx命令macOS/Linux下用export命令或者更简单一点在项目根目录创建一个.env文件然后用python-dotenv来加载。后面给出的代码示例里默认从环境变量读取export SPOTIPY_CLIENT_ID你的client_id export SPOTIPY_CLIENT_SECRET你的client_secret export SPOTIPY_REDIRECT_URIhttp://localhost:80802.2 OAuth认证scope设计Spotify API的每种数据权限都对应一个scope授权范围。我们这次只需要读取用户最近播放记录对应的scope是user-read-recently-played。这个scope只能读取当前授权用户自己的播放历史拿不到别人的数据是官方允许的最小必要权限。你在实际授权时可能会发现Spotify跳出的页面只显示了一个很小的权限请求这就是很多第三方总结网站拿到你历史记录的真相——它们请求的scope更多更宽。使用Spotipy做OAuth认证有两种模式SpotifyClientCredentials和SpotifyOAuth。前者适合访问公开数据比如热门榜单、专辑信息后者适合访问用户私有数据也就是我们需要的播放历史。一定要选后者使用SpotifyOAuth时程序会打开浏览器跳到Spotify的授权页你点同意后它会跳转到你设置的本地地址读取回调URL中的授权码自动完成token获取和缓存。整个体验很顺滑唯一需要注意的是本机不能占用8080端口否则回调会失败。from spotipy.oauth2 import SpotifyOAuth import spotipy sp spotipy.Spotify(auth_managerSpotifyOAuth( scopeuser-read-recently-played, client_id你的client_id, client_secret你的client_secret, redirect_urihttp://localhost:8080, cache_path.spotify_cache ))代码执行到这里如果弹出浏览器授权页面登录后看到Authentication successful的提示说明认证已经通过。之后Spotipy会在本地生成一个.spotify_cache文件后续再运行脚本就不用重复授权了。这个细节很不起眼但极其省事千万不要把缓存文件加到Git里否则容易泄露refresh token。3. 数据拉取与清洗把原始JSON变成干净表格认证搞定之后数据拉取本身反而简单了。Spotify的recently-played接口一次最多返回50条记录而我们想要的是足够长时间跨度的历史数据所以必须做分页拉取。这个地方藏着一个大坑这个接口不支持传统的offset翻页而是使用before或after时间戳做游标翻页。什么叫游标翻页简单理解就是每次请求都带着上一批最后一条记录的时间点让API返回这个时间点之前或之后的下一批数据。我们顺着时间向前翻就是一直用before参数带上当前批次最早一条播放记录的毫秒级时间戳直到返回的记录数小于50或直接为0说明已经翻到最早的历史记录。我把拉取逻辑写成一个循环函数循环的同时做了三件事累计总条数、打印进度条信息、控制请求频率。在请求之间加一个sleep(0.5)非常必要不是因为API会骂人而是因为Spotify的API有严格的速率限制。官方文档写的限制是按令牌计算的实际体验下来如果你不加控制地狂发请求很快会撞上HTTP 429错误。import time from datetime import datetime def fetch_all_history(sp, limit50): all_records [] last_before None while True: if last_before: results sp.current_user_recently_played(limitlimit, beforelast_before) else: results sp.current_user_recently_played(limitlimit) items results.get(items, []) if not items: break all_records.extend(items) earliest_played_at items[-1][played_at] last_before int(datetime.fromisoformat(earliest_played_at.replace(Z, 00:00)).timestamp() * 1000) print(f已拉取 {len(all_records)} 条记录最早时间 {earliest_played_at}) time.sleep(0.5) if len(items) limit: break return all_records history fetch_all_history(sp)跑完这个函数之后你手里会得到一个原始记录列表每条记录长这样包含了track对象里面有歌曲名、专辑名、艺术家列表、时长、流行度、ID等、played_at字符串UTC时区的ISO8601格式时间戳、context播放来源比如歌单、专辑、艺术家电台。为了好分析必须把它拍扁成一张干净的表格。拍扁flatten是数据分析里最基础也最容易出错的动作。尤其要注意track对象里有嵌套的artists列表一首歌可能有多位艺术家如果直接取列表会得到一长串Python对象存入DataFrame时会变得极其丑陋。我的建议是取artists字段时只保留第一位主要艺术家的名字同时把完整艺术家列表用/连接成字符串存到另一列。这样既保留了完整信息又让主分析字段保持干净。import pandas as pd def flatten_records(records): rows [] for r in records: track r.get(track, {}) artists_full , .join([a[name] for a in track.get(artists, [])]) rows.append({ played_at: r.get(played_at), track_name: track.get(name), album_name: track.get(album, {}).get(name), artist: track.get(artists, [{}])[0].get(name) if track.get(artists) else None, artists_full: artists_full, duration_ms: track.get(duration_ms), popularity: track.get(popularity), track_id: track.get(id), explicit: track.get(explicit), }) return pd.DataFrame(rows) df flatten_records(history) df[played_at] pd.to_datetime(df[played_at]) df df.sort_values(played_at).reset_index(dropTrue) print(df.head())数据清洗到这里并没有结束还有三件小事必须处理否则后面分析会失真。第一时区转换。played_at字段是UTC时区直接转成本地时间才能正确反映你的作息规律。如果你在中国就得加8小时。第二去重。Spotify的播放记录接口偶尔会把同一首歌的连续播放记成多条记录虽然这是少数情况但做精确统计前可以先按track_id played_at去重。第三缺失值处理。有的老歌在API里没有popularity值有的独立音乐人作品没有完整专辑信息遇到这种情况不要删行用fillna填充或保留空值后续分析时单独处理。时间字段转成本地时间后还能扩展出多个衍生列星期几、小时、日期、年月这些字段是后续所有时间维度分析的基石。一个小技巧是直接用.dt访问器批量生成local_tz Asia/Shanghai df[played_local] df[played_at].dt.tz_convert(local_tz) df[hour] df[played_local].dt.hour df[weekday] df[played_local].dt.dayofweek df[date] df[played_local].dt.date df[month] df[played_local].dt.to_period(M)4. 核心分析维度与实操可视化数据表格就绪后就到了最有意思的部分从不同维度解剖你的听歌行为。这个环节没有标准答案我分享几个我做下来觉得信息量最大、也最容易出效果的分析方向你可以按自己的兴趣做取舍。4.1 时间维度作息和听歌习惯的映射时间维度的分析价值在于它直接映射了你的生活节奏。把数据按hour分组统计每个小时段播放了多少首歌就能画出一张24小时听歌分布图。我自己的数据做出来之后发现两个明显的波峰早上8点到9点通勤路上和晚上21点到23点夜间放松或加班。如果你经常熬夜凌晨的曲线还可能有第三个峰这个峰的出现时间基本就是你作息不规律的直接证据。再往细一层把weekday和hour交叉起来做一个热力图能看得更精细工作日和周末的听歌习惯差异会在热力图上表现得极其直观周末听歌高峰往晚上和下午移动工作日的早高峰则非常陡峭。这里用seaborn的heatmap画图最合适。import seaborn as sns import matplotlib.pyplot as plt hourly_counts df.groupby([weekday, hour]).size().unstack(fill_value0) plt.figure(figsize(12, 6)) sns.heatmap(hourly_counts, cmapYlGnBu, linewidths0.5) plt.xlabel(小时) plt.ylabel(星期) plt.title(一周听歌时间热力图) plt.show()如果你愿意多走一步还可以把时长也纳入统计——不是听了几首而是听了多少分钟。duration_ms字段存的是每首歌的总时长不是你的实际收听时长所以只能粗略估算。用df[duration_ms].sum() / 60000算出总分钟数后再按小时拆分就能看到你每天在音乐上“挂”了多久。注意这个数值会偏高因为你可能只听了半首歌就切歌但统计层面的形态分析已经足够可信。4.2 艺术家与歌曲偏好找出真正的本命这块是最好玩也最容易出“社交传播图”的分析。按artist分组统计播放次数取前15位画横向条形图就能得到你的艺术家榜单。做这个分析时我有一个心得播放次数高不代表你最喜欢只能代表你播放得频繁。有的人排名靠前是因为他的歌多、专辑多、适合当背景音有的歌手排名不高但几乎所有歌曲你都听过那才是真正的本命。所以我会同时统计每个艺术家的唯一歌曲数用nunique算出来它反映的是你对这个歌手作品涉猎的广度。两个指标交叉起来看一个代表陪伴时长一个代表探索深度组合起来才是一个完整的画像。歌曲维度上统计单曲播放次数取播放最多的歌。这里有个技术细节同一首歌在不同专辑里可能有不同ID比如单曲版和专辑版统计时会算成两首歌。我的解决办法是直接用歌名track_name分组而不是用track_id虽然粗暴但更符合人的直觉。再结合popularity字段还能玩出一个小洞察把你自己播放最多的歌和它的官方流行度对比哪些歌是你私藏的小众宝藏哪些歌是你跟大众品味重合的热门金曲一目了然。top_artists df.groupby(artist).agg( 播放次数(track_name, count), 歌曲数(track_id, nunique) ).sort_values(播放次数, ascendingFalse).head(15) top_tracks df.groupby(track_name).agg( 播放次数(track_name, count), 艺术家(artist, first) ).sort_values(播放次数, ascendingFalse).head(20)4.3 播放来源与歌曲属性理解你的使用场景Spotify的API还提供了一个很容易被忽略的字段context它记录了这首歌是通过什么渠道播放的来自歌单、专辑、单曲循环还是艺人电台。这个字段对理解使用场景很有帮助。比如我自己的数据里大量播放来源于“专辑”说明我有完整听专辑的习惯而如果你的数据里大部分来源是“歌单”说明你更倾向用歌单作为主要消费形态。更进阶的一个玩法是抓取歌曲的音频特征audio features。Spotify API对每首歌都有对应的valence情感积极度、energy能量、danceability舞动性、tempo节拍这些数值。可以批量对高频出现的歌曲ID调用sp.audio_features(track_ids)然后把这些数值merge回原始表这样你就能挖掘更深层的行为规律比如你深夜听的歌是不是明显比白天的更加舒缓、你专注工作的时候偏好的能量值区间是多少。我做过一次这个分析凌晨1点到3点的平均valence比我白天的平均值低了差不多两成数据一出来自己都有点被惊到。批量获取音频特征的代码需要注意API限制一次最多传100个track_id所以我写了一个分块函数def get_audio_features_batch(sp, track_ids, batch_size100): features [] for i in range(0, len(track_ids), batch_size): batch sp.audio_features(track_ids[i:ibatch_size]) features.extend(batch) time.sleep(0.2) return features拿到结果之后过滤掉其中的None有些冷门歌曲可能查不到特征转成DataFrame后按id字段与原表合并即可。如果这个过程中某首歌因ID匹配不上被过滤了不影响大局不用太纠结。5. 更长周期的数据获取思路与速率控制我知道看到这里你可能会问一个问题我的听歌历史远不止最近几个月但接口最多只能拿最近50条、翻页翻到某一天就到底了这怎么解决这是所有做这个项目的人都会碰到的天花板。Spotify Web API的recently-played接口公开的说明里从未承诺返回全部历史数据我实测下来通常能回溯到几个月甚至半年前但更早的记录就拿不到了。想获得更长的历史核心思路是主动积累。设计一个定时运行的脚本比如每隔几个小时通过API拉一次最近播放记录把结果增量存储到本地的SQLite或CSV里。这样坚持跑几个月你手上的时间跨度就会变得越来越长最终会得到一份完全属于你自己的长期听歌档案。我自己的做法是部署在家庭小主机上配合cron任务每小时跑一次每次拉取最新的50条记录存入SQLite。数据量不大但长期积累会非常可观。增量拉取时要注意去重策略。最简单的做法是给记录建一个以played_at为主键的唯一索引插入时使用INSERT OR IGNORE这样同一时刻的同一首播放记录永远只会保留一条。由于played_at精确到毫秒级别不同歌曲极少会重复到相同的时间戳这个方案非常稳。import sqlite3 conn sqlite3.connect(spotify_history.db) df.to_sql(play_history, conn, if_existsappend, indexFalse) conn.execute( CREATE UNIQUE INDEX IF NOT EXISTS idx_unique_play ON play_history (played_at); ) conn.commit()同样地如果你想分析某个特定时间段的数据也可以通过after参数精准拉取。比如你只想知道2024年12月你听了什么把12月1日零点的时间戳转成毫秒传给after把12月31日23:59的时间戳转成毫秒传给before即可。双参数组合使用后API会严格遵守这个时间窗口精度很高。6. 常见问题与排查经验实录这个项目从注册到出图每个环节都可能遇到问题我把自己实战中踩过或者是身边朋友踩过的问题集中列出来做成一个速查表看到相同报错能省下不少排查时间。问题现象根本原因解决办法授权时浏览器报错“localhost无法连接”重定向URI没有填或填错端口被占用确认开发者后台的Redirect URI与代码一致换用其他空闲端口运行时报No token或401 Unauthorized缓存文件过期或缺失scope不对删除本地.spotify_cache重新授权确认scope包含user-read-recently-played返回HTTP 429 Too Many Requests请求频率超过API限制在请求之间增加sleep整体控制请求速率played_at解析报错字符串是带Z的UTC格式直接pd.to_datetime在某些pandas版本会警告先替换Z为00:00再解析或者使用formatISO8601参数只有50条记录无法往前翻忽略了这个接口不支持offset翻页的特性使用before时间戳游标翻页直到拿到最老记录歌曲/艺术家名出现乱码或无法显示部分非英文歌曲字符集问题检查DataFrame读写时的编码统一使用utf-8重复记录过多同一首歌被连续多次播放或拉取任务重复执行用played_at去重或者增加track_id维度去重分析日期只有最近几天要明白接口回溯范围有限长期增量存储同时换用afterbefore精确窗口拉取除了表格里这些还有几个容易被忽略的经验。不要在生产环境反复测试OAuth授权每一步授权都会在Spotify后台留下应用记录虽然不影响什么但会拖慢后续调试节奏。建议固定一个本地工作目录和一个虚拟环境尽量让环境问题不出现在第一现场。缓存文件不要删除太勤每次重新授权都要走一遍浏览器流程浪费时间不说心情也会变差。有一个比较隐蔽的问题是current_user_recently_played返回的记录里如果是某些播客节目或本地文件导入的记录可能缺少完整的track元数据。遇到这类数据行时我的建议是直接在清洗阶段过滤掉因为我们的分析主题是音乐这些杂音会让艺术家榜单和歌曲榜单出现莫名其妙的条目。7. 进阶玩法给分析报告加点故事感基础的统计图表做完如果你还觉得不过瘾可以试着把分析结果组合成一份有叙事感的个人音乐报告。我自己的第二版项目就是这么做的生成了周报式的Markdown报告每次运行脚本后自动输出本周的听歌总时长、Top艺术家、新发现歌曲、深夜歌曲清单等模块。这样看数据就像在翻阅自己的音乐日记比单张图更有连续性和代入感。这类报告的产出思路其实就是自动化的“数据叙事”。把前面做的所有统计指标集中到一个JSON字典里然后用Python的f-string模板生成一段段文字最后拼接成一份完整的Markdown文件。用wordcloud生成的艺术家词云可以作为封面图很抓眼球。词云的生成代码非常简单把艺术家名称和播放次数做成一个字典传入即可from wordcloud import WordCloud import matplotlib.pyplot as plt artist_freq df.groupby(artist).size().to_dict() wc WordCloud(width1600, height800, background_colorwhite, font_path你的中文字体路径).generate_from_frequencies(artist_freq) plt.figure(figsize(16, 8)) plt.imshow(wc, interpolationbilinear) plt.axis(off) plt.show()这里提醒一句生成词云时如果艺术家名字包含中文必须指定一个支持中文的字体文件路径否则会出现满屏的方块别问我怎么知道的。推荐直接用系统自带的黑体或微软雅黑。如果想再进一步还可以把时间序列上的播放量做成折线图观察长期趋势。比如按月统计播放量看看你哪个月特别沉迷音乐、哪个月几乎消失。把这个趋势和你的生活事件对照起来往往会发现很有意思的巧合——搬家、考试、换工作、项目上线这些波动全都写在你的听歌数据里了。分布式存储那一步我后来也做了优化。CSV在数据量超过10万行后会变得又大又慢SQLite则完全没有这个压力。如果你只是在本地做探索性分析SQLite是性价比最高的存储方案不需要额外搭建数据库服务也能执行复杂的SQL聚合查询。我个人的体会是数据的魅力在于它不说话但从来不说谎。用Python分析完自己的Spotify数据之后我再听歌时的感受发生了微妙的变化——我知道这些旋律从哪里来又会把我带回哪段记忆里。最后分享一个实用的小技巧做完整套分析后把那些排在你播放榜首但你自己都没意识到的歌挑出来手动建一个歌单过一个月再听你会惊讶于当时的自己为什么会在那个时间点疯狂循环它。这种和过去的自己相遇的感觉是这个项目给到我最意外的回报。