资讯动态

用Python分析Spotify播放记录:从数据清洗到可视化全复盘

发布时间:2026/10/9 12:46:29 来源:尧图企业网站定制
大概是从三年前开始重度使用Spotify今年初我闲着没事翻了翻后台的导出数据发现里面躺着两万多次播放记录。当时脑子里冒出来一堆问题我到底花在音乐上的时间有多少每天深夜那一个小时在听什么我的“本命歌手”是真爱还是只是通勤时顺手点开于是直接写了一轮Python脚本把这些Spotify听歌数据完整分析了一遍。整个过程不算复杂但中间踩了不少文档里不会写的坑。这篇文章就是完整的复盘记录按“抓数——清洗——算指标——画图——避坑”的顺序来适合两类人看一是单纯好奇自己听歌习惯的人二是想找一份真实数据分析项目练手的Python新手。不需要你会爬虫能装上环境、把代码跑通就够了。1. 你的Spotify听歌记录里藏着哪些意想不到的习惯1.1 为什么我会盯上自己的播放历史每年年底音乐平台都会给用户生成一份年度报告看起来信息量很大实际上只有几个封面数字总播放时长、前几名歌手、前几名歌曲。可数据一旦到了自己手里维度完全不一样。举个例子我导出数据后发现周末上午十点的播放量比工作日高出三倍但周末晚上十点反而比工作日低。这说明我工作日晚上靠音乐提神周末上午则习惯把音乐当背景音。而年度报告永远不会告诉你这种节奏。更细的还有“平台分布”我在电脑上听歌占比其实不到8%绝大部分播放来自手机而手机里又分为主动选歌和系统续播。把这些拆开才算真正“看懂”了自己的听歌行为。1.2 先列出想回答的问题清单动手写代码之前我建议你先花十分钟把问题写下来。我当时列了这样一份清单总播放次数和总播放时长到底是多少相当于多少天连续播放哪些歌手/专辑/歌曲占据了我80%的播放时长一天24小时里我的收听高峰在几点周内和周末差多少这三年我的音乐偏好有没有发生漂移——从后摇转变到电子还是越来越杂食有多少次播放是我压根没听完就切走的切歌行为集中在什么场景为什么要先列清单因为数据字段是固定的但同样一张表算均值、算分布、算占比回答的是完全不同的问题。没有清单就去分析很容易陷入“先把所有图画出来看看”的陷阱最后产出几十张图却说不清结论。我的习惯是每个问题对应一段独立脚本这样跑起来清爽排错也方便。2. 拿数据的两条路官方导出与Spotify API怎么选2.1 官方导出的完整流程与等待时间Spotify允许用户从账户后台导出完整的历史数据。打开账户页面进入“隐私设置”找到“下载你的数据”相关的入口申请时会让你选择数据范围我建议直接勾选最完整的订阅选项尤其是带“扩展播放记录”的那一类。普通播放列表导出只有歌单信息真正有用的是扩展流媒体历史里面记录了每一次播放的精确时间、歌曲名、歌手名、专辑名、播放设备、播放时长等字段。申请提交之后官方会发一封邮件到注册邮箱里面给一个下载链接链接有效期一般有一周左右。等待时间这个事得看运气我遇到过最快的半小时最慢的等了快一周。别反复提交申请每提交一次都会重新排队只申请一次是最稳的做法。下载下来的不是单文件而是一个压缩包解压后能看到多个StreamingHistory开头的JSON文件。注意如果你的数据量比较大还可能有一个专门存放音频文件的目录那个我们分析中用不到不用管。核心就是这些JSON文件里记录的每一笔“播放事件”。2.2 API方案的适用场景与基本配置如果觉得官方导出太慢或者你想分析的是“未来一个月我的收听变化”那就得用API。Spotify官方提供了一个Python库叫Spotipy需要在开发者后台注册一个应用拿到Client ID和Client Secret再配置一个回调地址。注册流程很快填个应用名称和用途就行。拿到凭据后用Client Credentials模式就可以直接请求那些不需要用户授权的接口比如按歌手名搜索、查询歌手流派、查询专辑信息和音频特征。但是如果你要拿“当前用户最近播放记录”就必须走用户授权流程让用户点一个授权链接然后拿到访问令牌。我自己的最终方案是“两条腿走路”历史分析用官方导出的JSON因为它覆盖全量、不丢记录流派和音频特征这类官方导出里没有的数据用API补充。用导出文件分析全量习惯用API给歌曲打标签这个分工在后面的章节里非常有用。2.3 官方导出字段表先认识你的数据长什么样拿到JSON后我用pandas读进来做了个快速预览发现每条记录包含的字段比想象中多得多。我整理个表格方便对照字段名含义我的用途ts播放启动时间ISO 8601UTC时区转成本地时间分析作息规律ms_played这次播放持续了多少毫秒统计有效时长切歌检测master_metadata_track_name歌曲名歌曲排行master_metadata_album_artist_name歌手名歌手排行master_metadata_album_album_name专辑名专辑维度分析spotify_track_uri歌曲唯一标识去重、补全API信息reason_start播放启动原因比如点击、自动播放区分主动听歌/被动听歌reason_end播放结束原因比如播完、手动切歌分析切歌行为platform播放平台设备偏好分析字段里还有两个一眼看上去很另类的叫ip_addr_decrypted和user_agent_decrypted翻译过来是IP地址和用户代理。它们本意是方便你回忆“当时在哪个城市听的”但涉及隐私我后面会专门讲怎么处理这里先不展开。3. 环境准备与数据加载pandas、plotly与那堆JSON文件3.1 需要安装哪几个Python库整个分析依赖的库不多我实际装的就这几个pip install pandas plotly spotipy python-dateutilpandas数据处理的核心读JSON、分组聚合、时间序列全靠它plotly用来画交互式图表生成HTML文件可以分享给朋友spotipy可选安装只在需要调API补全流派和音频特征时用python-dateutil处理带时区的ISO时间字符串时更稳很多教程会让你直接装matplotlib但我个人更推荐plotly。原因是数据分析过程中你通常要先探索再下结论matplotlib画完图得逐张保存查看而plotly生成的是交互式HTML鼠标悬停就能看数值翻图表像翻网页一样方便。探索效率高一个量级。3.2 把分散在多个JSON里的StreamingHistory读取进来官方导出的文件往往不止一个比如我那次解压后分别看到StreamingHistory0.json、StreamingHistory1.json这样命名的一串文件。一次性把所有文件都读进来再拼接是这个场景最简单的解法import json import glob import pandas as pd frame_list [] for path in glob.glob(./Spotify Extended Streaming History/StreamingHistory*.json): with open(path, r, encodingutf-8) as f: data json.load(f) frame_list.append(pd.DataFrame(data)) df pd.concat(frame_list, ignore_indexTrue) print(df.shape) print(df.head())这里有几个细节值得说。一是encodingutf-8必须写不然遇到某些非英文歌曲名会直接报编码错误。二是glob.glob直接匹配文件名前缀批量读取不用手动数文件个数。三是ignore_indexTrue很重要否则拼接后索引全是重复的后面groupby虽然不影响但loc、iloc取数时会很别扭。读取完成后看一眼df.shape如果记录数和你心理预期的播放量级差很远回头检查是否漏了文件名。我见过不止一次新用户只读了StreamingHistory0.json最后统计结果少了一大截。3.3 时间戳字段与时区转换UTC和本地时间的爱恨情仇这个坑是我第一次分析时踩得最深的。导出数据里的ts字段是ISO格式的UTC时间字符串比如2024-11-03T12:30:05Z。如果你不去管时区直接提取小时那所有时间点都偏了8小时深夜听的歌会被算到下午整个作息分析全部错乱。正确做法是先把字符串转成pandas的时间类型再转换时区df[ts] pd.to_datetime(df[ts]) df[ts_local] df[ts].dt.tz_convert(Asia/Shanghai)注意tz_convert要求时间对象本身带时区信息所以一定要用pd.to_datetime先解析成带UTC的datetime再转换。如果你的Python环境默认时区本来就不是UTC解析出来的时间可能没带时区后缀这时候先用tz_localize(UTC)补上时区再执行tz_convert。转换之后后面所有按小时、按星期、按月份的分析都基于ts_local本地习惯才不会被时区搞乱。我当时光是修正这个点就推翻了一半的图表结论。4. 核心指标怎么算播放时长、本命歌手与流派偏好4.1 播了多少分钟别把切歌误算成完整播放第一条指标是总播放时长听起来简单但ms_played字段存的是“本次播放实际持续了多久”单位是毫秒它不是歌曲长度。你可能点击了一首歌但5秒后就切走了这条记录的ms_played就是5000左右。如果直接用全部记录求均值你的平均播放时长会被大量手滑记录拉低。我的统计口径分两层。先算原始总量包括所有播放行为df[hours_raw] df[ms_played] / 3600000 total_hours round(df[hours_raw].sum(), 1) print(f包含所有播放行为累计 {total_hours} 小时)再算“有效播放”也就是至少听了30秒才算一次真正意义上的播放。这个阈值不是官方标准是我结合实际听歌习惯定的。我自己实测30秒以下的记录大多是手滑、试听、切歌误触。你可以根据自己的习惯调整但这个步骤不能省否则播放次数会虚高一截。effective_df df[df[ms_played] 30000].copy() effective_df[hours] effective_df[ms_played] / 3600000我当时算下来有效播放总时长约312小时相当于13天不眠不休连续听歌。这个数字比年度总结里报的那个数字低很多我后来意识到年度总结算的是“有的播放行为”而我算的是“认真听的时间”口径不同没有对错但分析时必须二选一并保持统一。4.2 本命歌手鉴定播放量、完成率与“遗珠歌手”有了有效播放数据集第二个问题就是谁是真正的本命歌手。这里不能用“播放次数”因为它区分不了“听完整首歌”和“听30秒就切”的差别。按有效时长排更接近真实偏好artist_play ( effective_df.groupby(master_metadata_album_artist_name)[ms_played] .sum() .sort_values(ascendingFalse) .reset_index() ) artist_play.columns [artist, total_ms] artist_play[hours] artist_play[total_ms] / 3600000 print(artist_play.head(10))这样做完之后有个意外发现我播放次数最多的歌手有效总时长只排第三。原因是我经常随机播放他的热门单曲但每首都听不了多久就切了。反而有一个相对小众的后摇乐队播放次数只有几十次但每次都能从头听到尾有效时长一路爬升到前五。这种歌手我起了个外号叫“遗珠歌手”——它可能不是你的年度报告头牌却是你真正会安静听完的声音。为了识别这类歌手我加了一个“完成率”指标用ms_played除以歌曲的标准时长。先把每首歌的时长通过spotify_track_uri去API查出来再做聚合。完成率高的歌手才是你在不赶时间、愿意沉浸在音乐里的时候会选的。4.3 流派偏好把细粒度标签映射成听得懂的大类官方导出里没有流派字段需要自己补。最直接的办法是遍历有效播放中出现过的歌手用Spotipy调API拿艺术家的genres列表import spotipy from spotipy.oauth2 import SpotifyClientCredentials sp spotipy.Spotify( client_credentials_managerSpotifyClientCredentials( client_id你的Client_ID, client_secret你的Client_Secret ) ) artist_cache {} def get_artist_genres(artist_name): if artist_name in artist_cache: return artist_cache[artist_name] try: results sp.search(qfartist:{artist_name}, typeartist, limit1) items results[artists][items] genres items[0][genres] if items else [] except Exception: genres [] artist_cache[artist_name] genres return genres这里强调一点一定要加artist_cache缓存。否则每个歌手都发一次HTTP请求几百个歌手就会让脚本慢到怀疑人生。加了缓存之后整个批量补全秒级完成。流派拿到手是细粒度的英文标签比如bedroom pop、deep house、post-rock。这些标签很有信息量但直接统计会让你看到几十上百种流派没法得出结论。我做了个简化的映射表把它们归拢到几个通俗大类独立摇滚、流行、电子、嘻哈、民谣、古典、其他。归完之后再看占比你的口味特征就非常清楚了。5. 时间维度挖习惯通勤歌单与深夜emo时刻5.1 按小时切片我的一天怎么被音乐划分时间分析是我这次做下来最有乐趣的部分。先把每条记录按本地时间的小时归组再统计每个小时的播放次数和播放时长effective_df[hour] effective_df[ts_local].dt.hour hourly effective_df.groupby(hour)[hours].sum().reset_index() hourly[count] effective_df.groupby(hour).size().reset_index(dropTrue) print(hourly.head())画出来之后结论非常像“音乐作息心电图”。我的一天有两个明显高峰早上七点到九点晚上九点到十一点。早高峰对应通勤地铁晚高峰对应写代码和睡前放松。最让我意外的是凌晨两点到三点还有一个小突起回看数据发现那段时间我在循环播放同一个沉寂类的歌单明显是失眠。这些规律普通用户平时根本意识不到因为听歌动作太“顺手”了。但数据会说话当你把一年里几千次播放压缩到24小时坐标轴上你的情绪周期和生活节奏都在里面。5.2 周内节奏工作日和周末的听歌差异同样一张表换成按星期几切片又能看到新的结构。我引入了星期字段effective_df[weekday] effective_df[ts_local].dt.dayofweek结果周一到周四的播放量曲线很平稳周五开始上升周六到达顶峰周日晚间又回落到接近工作日的水平。更细节的一个洞察是工作日午休时段12点到14点的听歌量很低因为午休我基本在补觉。而周末下午14点到17点的播放量突然拉高那是做家务、看书的时候把音乐当背景音。如果你愿意再叠一层“主动播放/自动续播”的区分还能看出周末更多是主动选歌工作日更多是让推荐算法接管。这两个状态对应完全不同的听歌体验看得越多越觉得有意思。5.3 长期口味漂移用月度数据看浅层迁移最后我在时间轴上做了一个更宏观的维度按自然月统计流派占比的变化。流程是取月度、取每个月的流派字段、算各流派的播放时长占比然后画成堆叠面积图。因为我跨度有三年所以能清楚看到自己的口味漂移轨迹。我的数据里非常明显第一年以独立摇滚和后摇为主第二年电子音乐占比迅速爬升第三年嘻哈和流行掺了进来整体变得杂食。这种变化往往对应生活阶段的变化——换城市、换工作、新认识了朋友。音乐口味不是瞬间改变而是按月慢慢漂移的你回头看每一年的Top歌手发现的旧爱可能连你自己都快忘了。6. 可视化那些能直接发朋友圈的图表怎么做6.1 周时间热力图一张图看懂全部作息我特别推荐做一张“星期-小时”热力图横轴是24小时纵轴是周一到周日颜色越深表示播放时长越长。这样一整周的听歌习惯压缩在一张图里观感很直观也很容易在社交媒体上引起讨论。用plotly表达式可以这样画import plotly.express as px heat_data ( effective_df.groupby([weekday, hour], as_indexFalse)[hours] .sum() ) fig px.density_heatmap( heat_data, xhour, yweekday, zhours, color_continuous_scaleViridis, ) fig.write_html(weekly_heatmap.html)拿到weekly_heatmap.html就可以直接分享。我这边跑完第一版直观看到周三晚上的色块异常偏大追查回去发现那天固定要开周会开完会习惯性用音乐“自我疗愈”这个洞察是列表视图里根本看不出来的。6.2 歌手排行、累计播放曲线与榜单效应第二类图表是歌手排行。我会同时画两张一张是按有效时长排序的Top15歌手条形图一张是累计播放时长随时间变化的曲线。后者特别能说明问题斜率越大说明那段时间听得越猛斜率变平说明忙碌到没空听歌。累计曲线直接用cumsum就能实现effective_df effective_df.sort_values(ts_local) effective_df[cum_hours] effective_df[hours].cumsum() fig2 px.line(effective_df, xts_local, ycum_hours)有一种“榜单效应”也值得一提。我拖动累计曲线的局部区域发现某张专辑发布的首周曲线几乎是垂直上升的听歌时长猛增两周热度过去曲线恢复平缓。年轻的时候会觉得这是“热爱”但数据告诉你这更像是对新内容的正常消费行为。6.3 导出交互HTML从分析到分享的最后一公里plotly的图表默认就是交互式的只要用write_html导出整个页面自带缩放、悬停显示数值的功能不需要额外部署服务端。这是我最满意的一点做好了发给朋友没人需要装浏览器插件点开就能玩。分享时有个习惯要养成图表的标题、悬停文本和坐标轴标签尽量改成中文或通俗的描述。直接扔一个hours上去朋友看不懂这字段什么意思。我一般会把数据列改名再画顺手给颜色标度加上单位这样图表一出来就是成品而不是“分析过程中间产物”。7. 踩坑实录短播放、重复记录与隐私边界7.1 短播放记录不要无脑过滤先想清楚你要回答什么问题前面我提到底线是30秒但这个过滤并非万能。我第一次跑通分析时把小于30秒的记录全部删掉结果整个数据集的播放次数少了近四成当时觉得“太夸张了”后来才发现我的播放列表里混着大量电台节目片头用户语音片段这种本来就短的东西。所以正确做法是分场景决定是否过滤。分析“有效听歌时长”时可以过滤分析“我的切歌行为是否频繁”时千万不能过滤——切歌本身就是你行为的核心数据。我后来把原始表和有效表拆成两个DataFrame一份做全量统计一份做偏好统计再也没纠结过。7.2 重复记录、空歌曲名与播客内容怎么处理官方导出的JSON基本不会重复记录但当你用API补全流派时很容易产生重复合并的问题同一歌手的不同拼写、同一首歌在单曲和专辑里的URI不同去重必须依据spotify_track_uri不能依据歌曲名。示例代码effective_df effective_df.drop_duplicates(subsetspotify_track_uri, keeplast)另外播放记录里可能混入播客Podcast剧集。这类记录的master_metadata_track_name字段通常为空但episode_name字段有值。如果你的分析目标是“音乐偏好”必须把这些记录剔除否则一个半小时的播客会被当成一次巨长播放时长占比严重失真。判断逻辑很简单master_metadata_track_name.isna()严格脑子说明它不是常规歌曲。7.3 就算只分析自己也有些隐私边界不能碰最后必须泼一盆冷水。官方导出的数据里带有ip_addr_decrypted和user_agent_decrypted这类字段记录了你每次播放联网时的IP和设备信息。你分析自己的数据时看到这些字段没问题但如果把处理后的表格、图表、甚至随手分享的截图发到公开平台这些信息很可能泄露你的地理位置、家庭宽带运营商和常用设备型号。我的建议是导入原始数据后立刻删掉这两个字段只留后面分析需要的列df df.drop(columns[ip_addr_decrypted, user_agent_decrypted], errorsignore)再进一步所有需要补全流派和音频特征的歌曲标识只保留spotify_track_uri不要保留含有个人特征的字段。这是我自己做数据项目时的底线也是对整个分析习惯的基本尊重。结实地跑完这一整套我最大的感受是技术只解决了一半的问题另一半问题靠的是你对自己的了解。数据把“我以为我喜欢听什么”和“我实际上在听什么”之间的差距完完整整地暴露了出来。分析完自己的听歌数据之后我专门建了三个新歌单一个给工作日早起通勤一个给深夜安静时段还有一个专门留给那些完成率特别高的“遗珠歌手”。如果你也准备动手我建议你把这篇文章读到的代码先在自己导出数据上完整跑一遍再做任何修改。第一步永远是拿到数据先看看那些JSON文件里到底躺着哪些小秘密。

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

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

免费获取报价 →
↑