☰
Python实战:从Spotify API拉取听歌数据并做可视化分析
2026/10/10 12:57:28 网站建设 项目流程

每年的Spotify Wrapped都是社交平台上的固定节目,大家乐此不疲地晒出自己的年度歌单、TOP歌手、播放时长。但说实话,那个报告一年只出一次,能看的维度也就固定几样,满足不了真正的数据控。作为一个天天写Python的人,我早就想绕过Wrapped的限制,随时随地把自己的听歌数据拿下来,想怎么分析就怎么分析。这篇文章就完整讲讲我折腾这件事的整个过程:从Spotify开发者权限申请、Python环境准备,到OAuth授权、数据拉取、清洗和分析可视化,所有代码都是跑通验证过的,你可以直接照抄改改就能用。

1. 项目拆解:分析Spotify数据到底能玩出什么花

1.1 这个项目能帮你搞清楚什么问题

先说个最直接的问题:你觉得你了解自己的听歌偏好,但数据会告诉你完全不一样的答案。比如我拉完数据之后发现自己循环播放最多的歌手,根本不是我最常手动点开的那位,而是那些我"顺手点了下一首就从没关掉"的歌。这就是数据分析的魅力:把你的印象和直觉全部推翻,用数据重构一个更真实的行为画像。

具体来说,这个项目能帮你回答这么几类问题:

  • 你最近一个月、一周、甚至一天的听歌行为有什么规律?是通勤时段听得多,还是深夜凌晨才是你的音乐主场?
  • 你常听的歌曲有哪些共同特征?是BPM(速度)普遍偏快,还是能量值普遍偏低?
  • 你的音乐口味是不是真的"杂食"?用聚类算法一跑,会发现你的喜好其实就集中在两三类风格上。
  • 你循环最多的歌手和歌曲,和你的主观认知一致吗?

这些问题有了数据支撑之后,你对自己音乐偏好的理解会上升一个维度。而且说实话,整个过程本身就是一次很好的Python实践,从API调用、JSON解析到pandas清洗、可视化,覆盖了数据分析的完整流程。

1.2 技术选型为什么这么定

选型这件事我在动手前认真纠结过一轮。当时摆在我面前的有两条路:

一:用现成的Spotify数据导出工具,比如一些第三方网站能帮你导出播放记录。优点是省事,缺点是数据格式受限,很多字段拿不到,而且把个人听歌数据交给第三方,隐私上总觉得不太放心。

二:直接用Spotify官方Web API。优点是可以拿到完整数据,包括艺术家、专辑、音轨ID、播放时间戳,甚至每首歌的音频特征(情绪值、能量值、舞蹈性、BPM等),这些信息是官方导出的数据里没有的。缺点是需要自己处理OAuth认证,技术上麻烦一点。

我最后选了官方API,理由很朴素:既然要玩数据分析,就要拿到最原始、最丰富的字段。而且官方API的音频特征分析功能是Spotify独家的,其他平台根本拿不到这种级别的数据。这几年我用下来,说实话这条路是最值得的,虽然初期配置麻烦点,但后面所有分析维度都是建立在这些丰富数据上的。

2. 拿到钥匙:注册API与搭建环境

2.1 在Spotify开发者后台创建应用

这一步是整个项目的起点,也是很多新手第一次卡住的地方。你需要先有一个Spotify账号(免费的就行),然后访问Spotify for Developers仪表盘,登录后点击"Create App"创建一个新应用。

创建应用时它会让你填两项信息:

  • App name:应用名称,随便取,比如 "My Music Analyzer"
  • App description:应用描述,简单写一句就行

创建完成之后,你会进入应用管理页面,这里有几个关键信息你需要记下来:

字段说明存放位置
Client ID一串32位长度的十六进制字符串,相当于你的应用ID应用主页面上就能看到
Client Secret一串64位长度的字符串,相当于你的应用密钥需要点"Show Client Secret"才能看到

这两个值就是你的API钥匙,整篇文章后面所有的接口调用都要用到它们。

2.2 配置Redirect URI

创建好应用之后,还有一步关键配置:设置Redirect URI(重定向地址)。这是OAuth认证流程里的一个环节,Spotify会在用户授权完成后,把授权码发送到你设置的这个地址上。

在应用设置页面找到"Redirect URIs"那一栏,点击"Add"添加地址。本地开发的话,我建议直接填:

http://localhost:8888/callback

之所以用8888端口,只是我习惯了,你用其他端口完全没问题,关键是:你填的地址必须和你代码里写的完全一致,一个字符都不能差。我在这一步被坑过,当时填了localhost和127.0.0.1两个看起来差不多的地址,结果代码里用127.0.0.1,Dashboard上填的是localhost,授权的时候直接报错,排查了好久才发现是地址不匹配。

2.3 本地Python环境准备

接下来是Python环境。我强烈建议你用虚拟环境,别把依赖直接装到系统Python里,不然过段时间环境一乱,你都不知道是哪个包在打架。

创建虚拟环境并激活:

python3 -m venv spotify_env source spotify_env/bin/activate # macOS/Linux # Windows下是 spotify_env\Scripts\activate

然后安装依赖。这个项目的核心依赖其实不多:

pip install pandas matplotlib seaborn requests scikit-learn

如果你打算用现成的Spotify封装库,再加一个:

pip install spotipy

spotipy是社区维护的Spotify API Python封装库,把OAuth流程封装得很简洁,能省去不少样板代码。但我个人建议第一遍跑的时候自己用requests手写一遍OAuth,把流程彻底搞懂,之后再用spotipy就很快了。这就像你先学手动挡再开自动挡,以后遇到问题不会两眼一抹黑。

3. 核心实现:手把手把数据抓下来

3.1 自己实现OAuth授权流程

Spotify的OAuth授权流程,我们按步骤拆开来看。

第一步:引导用户打开授权页面。用户访问你构造的这个URL,看到Spotify的授权确认页,点"同意"之后,Spotify会把用户引导到你在Dashboard里配置的Redirect URI,并在地址后面带上一个?code=xxxx参数。

import requests import webbrowser import urllib.parse CLIENT_ID = "你的CLIENT_ID" REDIRECT_URI = "http://localhost:8888/callback" SCOPE = "user-read-recently-played user-top-read" params = { "client_id": CLIENT_ID, "response_type": "code", "redirect_uri": REDIRECT_URI, "scope": SCOPE, } url = "https://accounts.spotify.com/authorize?" + urllib.parse.urlencode(params) webbrowser.open(url) # 自动打开浏览器让用户授权

第二步:启动本地服务器接收回调。这里需要一个本地HTTP服务来监听Redirect URI。我用Python内置的http.server模块就能搞定,不用额外装框架。核心逻辑是:启动一个临时web服务器,监听在8888端口,一旦收到回调请求,就从中取出code参数,然后立刻关闭服务器。

from http.server import HTTPServer, BaseHTTPRequestHandler class CallbackHandler(BaseHTTPRequestHandler): code = None def do_GET(self): # 从URL中提取code参数 parsed = urllib.parse.urlparse(self.path) query = urllib.parse.parse_qs(parsed.query) if "code" in query: CallbackHandler.code = query["code"][0] print("已捕获到授权码:", CallbackHandler.code[:20] + "...") self.send_response(200) self.send_header("Content-Type", "text/html; charset=utf-8") self.end_headers() self.wfile.write("授权成功,可以关闭本页面。".encode("utf-8")) else: self.send_response(400) self.end_headers() # 关闭服务器 server.shutdown() server = HTTPServer(("localhost", 8888), CallbackHandler) server.handle_request() # 只处理一次请求就退出

第三步:用授权码换取Access Token。这个POST请求是OAuth流程中最关键的一次交换。

auth_url = "https://accounts.spotify.com/api/token" payload = { "grant_type": "authorization_code", "code": CallbackHandler.code, "redirect_uri": REDIRECT_URI, "client_id": CLIENT_ID, "client_secret": CLIENT_SECRET, } resp = requests.post(auth_url, data=payload) token_data = resp.json() access_token = token_data["access_token"] refresh_token = token_data["refresh_token"] expires_in = token_data["expires_in"] # 通常为3600秒

这里拿到的access_token有效期只有1小时。好在Spotify支持refresh token,过期之后我们可以用它重新换一个新的access_token,不用再让用户重复授权。

3.2 拉取最近播放记录

拿到的access_token之后,就可以通过Authorization Header调用Spotify的API了。获取最近播放记录的接口长这样:

headers = { "Authorization": f"Bearer {access_token}" } # 获取最近播放的50条记录 url = "https://api.spotify.com/v1/me/player/recently-played?limit=50" resp = requests.get(url, headers=headers) data = resp.json()

这个API有一个限制:最多只能返回最近50条播放记录,而且时间范围大概是最近30天左右。想要更长的历史数据,单靠这个接口是不行的,需要定时定期抓取积累,或者用Spotify官方的账号数据导出功能(那个才包含完整的播放历史文件)。

拿到数据之后,关键是提取出有用的字段。每条播放记录长这样(我做了一些字段省略):

items = data["items"] records = [] for item in items: track = item["track"] played_at = item["played_at"] artist_name = track["artists"][0]["name"] track_name = track["name"] track_id = track["id"] duration_ms = track["duration_ms"] popularity = track["popularity"] records.append({ "track_id": track_id, "track_name": track_name, "artist_name": artist_name, "played_at": played_at, "duration_ms": duration_ms, "popularity": popularity, })

3.3 批量获取每首歌的音频特征

这是我觉得整个项目里最有"技术含量"的一步。Spotify的API能返回每首歌的音频特征数据,包括:

  • danceability:舞蹈性,0.0到1.0,值越高越适合跳舞
  • energy:能量,0.0到1.0,值越高表示歌曲越激烈、越热闹
  • valence:情感积极程度,0.0到1.0,值越高表示越欢快、越积极
  • tempo:BPM(每分钟节拍数)
  • acousticness:原声程度,值越高表示越接近纯原声乐器
  • instrumentalness:器乐程度,值越高表示越接近纯音乐

这三个指标组合起来,基本就能勾勒出一首歌的"听觉气质"。我拿到播放记录中的track_id之后,用/v1/audio-features/{track_id}这个接口逐首歌去查询:

def get_audio_features(track_id, headers): url = f"https://api.spotify.com/v1/audio-features/{track_id}" resp = requests.get(url, headers=headers) if resp.status_code == 200: feat = resp.json() return { "track_id": track_id, "danceability": feat["danceability"], "energy": feat["energy"], "valence": feat["valence"], "tempo": feat["tempo"], "acousticness": feat["acousticness"], "instrumentalness": feat["instrumentalness"], } else: return None

如果你一次性要拉的歌曲数量很多,建议每首歌之间加一个短暂延时,后面我会专门讲限流的问题。我当时拉100首歌大概花了一分多钟,完全可接受。

4. 深度分析:让数据替你说话

4.1 数据处理与热门歌手排行

拿到原始播放记录之后,先把数据整理成DataFrame:

import pandas as pd df = pd.DataFrame(records) df["played_at"] = pd.to_datetime(df["played_at"]) df["hour"] = df["played_at"].dt.hour df["weekday"] = df["played_at"].dt.day_name() print(df.head())

接着算谁是你真正的"本命歌手"。这里我用两个维度:播放次数和累计播放时长。

# 歌手播放次数排行 top_artists_by_count = df["artist_name"].value_counts().head(10) # 歌手累计播放时长排行 df["duration_min"] = df["duration_ms"] / 60000 top_artists_by_duration = df.groupby("artist_name")["duration_min"].sum().sort_values(ascending=False).head(10)

有意思的是,这两个排行结果往往不一样。有些歌手会频繁出现在你的播放历史里但每首歌都听不完,有些歌手虽然出现的次数少,但每次都能让你循环很久。

4.2 你的听歌习惯隐藏规律

接下来看时间维度的分析。我分析了自己每天的听歌时段分布,用pivot_table做了个二维表:

hour_weekday = pd.crosstab(df["weekday"], df["hour"])

再画成热力图之后,规律的显现特别直观。我自己最明显的发现是:工作日早上8-9点和晚上18-19点各有一个明显的播放高峰,这是通勤时段;晚上的高峰一直到凌晨1点才消停。周末的分布则是另一番景象,下午两点开始持续走高,深夜比工作日还要活跃,说明周末基本是夜猫子模式。

这种"听歌生物学作息表"你用肉眼是感知不到的,只有把时间戳拆开细细看,才能发现自己的音乐生活节奏原来如此规律。

4.3 音频特征:你的口味画像

把播放记录和音频特征数据合并之后,就可以画出你的音乐口味画像了。我画了一张按曲目分布的箱线图:

merged = df.merge(features_df, on="track_id", how="inner") fig, axes = plt.subplots(1, 3, figsize=(12, 4)) metrics = ["energy", "valence", "danceability"] for ax, metric in zip(axes, metrics): merged[metric].plot(kind="box", ax=ax) ax.set_title(metric) plt.tight_layout() plt.show()

看这三个指标的中位数,能快速判断你的总体口味:是偏向高能量快节奏的"鸡血型",还是偏向低能量慢节奏的"解压型"。我当时测出来的结果很惊人,energy的中位数是0.68,valence的中位数是0.42,说明我虽然平时听起来好像很爱嗨歌,实际上整体偏好是节奏感强但情绪不算特别高昂的歌曲。

4.4 用聚类算法看你的音乐世界分几派

接下来这步是我最喜欢的一步:用KMeans聚类算法,把所有歌曲按音频特征分成若干类别,看看你的音乐世界到底分几派。

from sklearn.preprocessing import StandardScaler from sklearn.cluster import KMeans features = merged[["energy", "valence", "danceability", "tempo", "acousticness"]] scaler = StandardScaler() features_scaled = scaler.fit_transform(features) # 用肘部法则大致确定K值 inertias = [] for k in range(2, 9): km = KMeans(n_clusters=k, random_state=42, n_init=10) km.fit(features_scaled) inertias.append(km.inertia_) # 我试下来K=3效果最好,能明显区分出三类歌曲 km = KMeans(n_clusters=3, random_state=42, n_init=10) merged["cluster"] = km.fit_predict(features_scaled)

聚类结果出来之后,我给它定义了三个类型分派:

  • Cluster 0:高能量、高舞蹈性、高情绪值,这是"跑步时候听的歌"
  • Cluster 1:高原声、低能量、低节奏,这是"深夜安眠曲"
  • Cluster 2:各项指标都在中间,属于"日常循环不腻的歌"

用散点图可视化聚类效果:

plt.figure(figsize=(8, 6)) scatter = plt.scatter(merged["energy"], merged["valence"], c=merged["cluster"], cmap="Set2", alpha=0.7) plt.xlabel("energy") plt.ylabel("valence") plt.title("Songs colored by cluster") plt.colorbar(scatter) plt.show()

你会发现散点图上的分界特别清楚,三个类别几乎没有交叉重叠。这说明Spotify的音频特征算法对歌曲气质的刻画是非常有效的,也说明我的听歌偏好虽然看起来杂,实际上暗含清晰的流派偏好。

5. 踩坑记录:这些问题我全遇到过

5.1 OAuth授权过程中的各种报错

这是整个项目里新手最频繁卡住的地方,我列几个我遇到过的:

报错 "invalid_client":Client ID或Client Secret填错了。去Dashboard重新复制一遍,注意别多复制了空格。推荐的做法是存在环境变量里,别硬编码在代码中:

export SPOTIPY_CLIENT_ID="你的ID" export SPOTIPY_CLIENT_SECRET="你的密钥"

报错 "redirect_uri_mismatch":代码里的Redirect URI和Dashboard里配置的完全不一致。注意大小写、端口、路径都要一字节不差。

授权页面能打开但回调不触发:检查一下回调地址的端口是否被占用,以及是不是被防火墙拦了。本地起的服务器改用127.0.0.1而不是localhost试试,有时候系统DNS解析会出幺蛾子。

5.2 API限流问题

Spotify API的限流策略是每30秒最多发多少请求,超出后会返回HTTP 429状态码,响应头里会有Retry-After字段告诉你需要等待多少秒。

之前我一次性拉100首歌,用了个简单循环,结果每拉几首就被限流了。后来我在代码里加了个简单的延时配合退避机制:

import time def get_features_with_retry(track_id, headers, max_retries=3): for i in range(max_retries): resp = requests.get(f"https://api.spotify.com/v1/audio-features/{track_id}", headers=headers) if resp.status_code == 200: return resp.json() elif resp.status_code == 429: retry_after = int(resp.headers.get("Retry-After", 1)) time.sleep(retry_after + 1) else: time.sleep(0.5) return None

加了重试逻辑之后,整个流程稳了很多。

5.3 数据遗漏与偏差

这里有个特别重要的认知:recently-played这个API只能返回最近50条记录,所以如果你的听歌量比较大,这个数据代表的是你最近几小时的偏好,不是你长期的完整画像。

想拉更长时间跨度的数据,有几个备选思路:

  • 写个定时任务,每半小时拉一次数据存进SQLite,积累几周之后数据量就很可观了
  • 用Spotify官方的"扩展播放历史"导出(Privacy Settings里申请导出数据,Spotify会以邮件方式发给你CSV/JSON文件)
  • 用/v1/me/top/artists和/v1/me/top/tracks接口获取长期Top统计,这个接口有time_range参数,支持short_term(约1个月)、medium_term(约6个月)、long_term(数年)三个范围

顺便提醒一个细节:那个长期Top接口的输出是按Spotify算法加权过的,不是简单的播放次数累加,所以结果和你自己统计的播放次数TOP可能会不太一样,两边交叉对比着看才好玩。

注意:你的数据只跑在本地,不要把它传到任何第三方平台上。音乐口味虽然是小事,但数据隐私的习惯要从每一件小事养起。

写在末尾

最后分享几个我在实际操作之后的体会。第一,分析自己听的歌这件事,最大的乐趣反而不是那些图表多好看,而是你终于能跳出来用第三人称视角观察自己的行为习惯。我以前一直以为自己听歌很杂,结果聚类跑完发现其实就两大派,还发现工作日中午12点的听歌量几乎为零,可能是那时候都在开会吧。

第二,这个项目是很典型的一鱼多吃:拿自己的数据练手不会觉得枯燥,学习的效率远高于看一百遍教程。爬数据练的是requests和API调试,分析练的是pandas和数据分析思维,可视化练的是matplotlib/seaborn,聚类练的是sklearn。一个项目把Python数据分析的整个链路都过了一遍。

如果你想继续扩展这个项目,我提供几个思路:一是把长期定时采集的数据积累到数据库里,做一个真正意义上的"个人听歌年鉴";二是把音频特征和情绪标签关联起来,训练一个分类模型,分析未来听的新歌更接近你的哪种偏好。这些方向都不难,但玩起来会越来越有意思。数据这个东西,积少成多之后总会给你惊喜的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询