每年年底朋友圈都会被Spotify Wrapped刷屏,那种漂亮的年度总结卡片确实是很好的社交货币。可对我来说,它一直有个遗憾:那张图只告诉我是哪些歌手陪我熬过了这一年,却从来没告诉我"我到底在什么时段最爱听歌""凌晨两点还在单曲循环的究竟是哪一首""我现在真实的口味是偏咖啡店背景音还是电音现场"。想要回答这些问题,光靠平台给的总结是不够的,得想办法把属于自己的原始听歌数据拿回来分析。
这篇文章就是记录我用Python分析自己Spotify听歌数据的完整过程。项目本身并不复杂,但涉及的知识点非常典型:OAuth授权、REST API调用、JSON解析、pandas数据清洗、可视化绘图,最后还能顺手玩一把KMeans聚类。如果你正在学Python、想找一个真实数据项目练手,或者你是Spotify深度用户、想真正看懂自己的听歌行为,这篇内容应该正好适合你。
1. 为什么拿自己的Spotify数据练手
1.1 一张Wrapped看不到的东西
Spotify Wrapped每年只给你几张设计精美的卡片:听歌总时长、年度歌手、年度歌曲、听歌风格标签。这些内容作为社交分享够用了,但作为数据分析的原材料远远不够。
最典型的问题有三个。第一,Wrapped只给总时长,不给时间分布,你不知道自己到底是通勤路上听得最多,还是深夜窝在沙发上听得最多。第二,Wrapped给的是"平台眼中的你",它用一套算法把你的口味压缩成三五个标签,但你自己真正反复循环的那些歌,未必能出现在标签里。第三,Wrapped没有音频特征数据,你不会知道自己常听的歌到底是高能量还是低能量、偏欢快还是偏忧郁。
而Spotify官方API把这些数据全部开放出来了。通过API可以拉取你自己的播放历史、曲目热度、艺人和专辑信息,甚至每首歌的舞曲性、能量值、情绪积极度这类音频特征。拿到这些数据后,完全可以自己做一套比Wrapped更细的年度报告。
1.2 这个项目能学到什么
我推荐这个项目的原因很简单:它是少有的、数据和你自己强相关的练手项目。很多教程用鸢尾花数据集、房价数据集,学完就忘,因为那些数据跟你没有情感连接。但自己的听歌数据不一样,分析出"我周五晚上听的歌能量值明显偏高"这种结论时,你会立刻去对照自己的真实生活,这种感觉是模拟数据给不了的。
技术层面,这个项目能覆盖以下技能点:
- OAuth授权流程:理解为什么第三方应用不能直接拿账号密码,而是要用授权码换取令牌。
- REST API调用:requests库的基础用法、请求头构造、参数传递、分页处理。
- JSON数据处理:把嵌套很深的API响应整理成结构化DataFrame。
- pandas清洗:时间戳解析、字段提取、去重、聚合。
- 数据可视化:matplotlib、seaborn、plotly画柱状图、折线图、雷达图、散点图。
- 机器学习入门:用KMeans对歌曲特征做聚类,发现自己的隐藏歌单。
这些技能几乎覆盖了数据分析岗位日常工作的核心链路:取数、清洗、分析、可视化、建模。
1.3 需要准备的工具和知识
在开始之前,先确认一下环境。这个项目对基础要求不高,我自己是在学完Python基础语法之后开始做的,连类都还没完全搞懂就上了,遇到不会的边查边写。
工具清单如下:
- Python 3.9及以上版本。
- 一个Spotify账号,免费账号也能用,后面会说明接口权限问题。
- 安装requests、pandas、matplotlib、seaborn、scikit-learn,建议用pip统一安装。
- 一个本地开发环境,我用的是VSCode,用Jupyter Notebook来做过程性探索会更舒服。
需要说明的是,全文所有涉及个人数据的示例,我都做了脱敏处理或者展示的是构造出来的示例数据。你自己在操作时也要注意,授权令牌绝对不要提交到公开代码仓库里。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 第一步:注册应用并搞定API认证
2.1 在Developer Dashboard创建应用
要调Spotify API,第一步不是写代码,而是去Spotify for Developers的Dashboard注册一个应用。这个步骤特别容易忽略,很多教程默认你已经做好了,但实际上一堆新手卡在这。
注册流程很简单:
- 打开developer.spotify.com/dashboard,用你的Spotify账号登录。
- 点击Create app创建一个新应用。
- 填写应用名称和应用描述,这里随便填,反正只有你自己能看到。
- 最关键的一步:填写Redirect URI,也就是回调地址。本地开发可以直接填
http://localhost:8888/callback,这个地址要记好,后面代码里要用到。
创建完成后,你会进入应用详情页,在Settings里能看到两个核心凭证:Client ID和Client Secret。Client ID相当于你的应用身份证号,Client Secret相当于密码,这两个东西一定不要泄露。
2.2 理解OAuth授权流程:为什么不能直接拿密码
很多没接触过认证流程的人,第一次都会问:为什么不直接用账号密码登录然后拿数据?
因为Spotify不能把用户密码交给第三方应用,这是基本的安全底线。它的做法是通过OAuth授权,让用户在一个受信任的页面上主动授权给第三方应用,然后应用拿到一个临时的授权码,再用这个授权码换访问令牌。
我用小区门禁来类比:Client Credentials相当于访客临时码,只能刷开大门,进不了业主的房间;Authorization Code Flow相当于业主在物业那里签了授权书,物业给你一张能进特定楼层的临时卡,过期作废。听歌历史属于个人私有数据,所以必须走Authorization Code Flow。
这个流程一共四步:
- 在代码里拼接一个授权链接,引导用户打开。
- 用户在Spotify登录页面确认授权。
- Spotify跳转回你填写的Redirect URI,并在URL参数里带上一个code。
- 用这个code去请求Token接口,换取access_token和refresh_token。
2.3 手写一次Token请求
为了理解原理,我建议你至少手动写一次完整的OAuth流程。虽然实际项目里会用官方库,但这一步能帮你搞清楚Token是从哪来的。
先拼接授权链接:
python复制import random
import string
import requests
CLIENT_ID = "你的Client_ID"
REDIRECT_URI = "http://localhost:8888/callback"
SCOPE = "user-read-recently-played"
# 生成一个随机的state参数,用来防止CSRF攻击,实际项目中建议保留
state = "".join(random.choices(string.ascii_letters + string.digits, k=16))
auth_url = (
"https://accounts.spotify.com/authorize?"
f"client_id={CLIENT_ID}"
f"&response_type=code"
f"&redirect_uri={REDIRECT_URI}"
f"&scope={SCOPE}"
f"&state={state}"
)
print("请在浏览器中打开以下链接并完成授权:")
print(auth_url)
浏览器打开后会要求登录Spotify账号并确认授权,确认后浏览器地址栏会变成类似http://localhost:8888/callback?code=一串字符&state=xxx的形式。把这串code复制出来,接下来用它换Token。
python复制import base64
CLIENT_SECRET = "你的Client_Secret"
AUTH_CODE = "上一步复制出来的code"
# 把 Client ID 和 Client Secret 拼起来做Base64编码
auth_header = base64.b64encode(
f"{CLIENT_ID}:{CLIENT_SECRET}".encode()
).decode()
resp = requests.post(
"https://accounts.spotify.com/api/token",
headers={"Authorization": f"Basic {auth_header}"},
data={
"grant_type": "authorization_code",
"code": AUTH_CODE,
"redirect_uri": REDIRECT_URI,
}
)
token_data = resp.json()
print(token_data["access_token"])
print(token_data["refresh_token"])
拿到access_token之后,调用API时在请求头里带上Authorization: Bearer 你的access_token就能访问数据了。注意access_token的有效期通常只有1小时,过期后要用refresh_token去刷新,手写这部分会比较繁琐。
2.4 使用spotipy简化认证
实际项目里,手动处理Token刷新太容易出错了,社区里已经有人把这个封装好了,也就是spotipy这个库。它能把上面一整套OAuth流程压缩成几行代码。
python复制import spotipy
from spotipy.oauth2 import SpotifyOAuth
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(
client_id=CLIENT_ID,
client_secret=CLIENT_SECRET,
redirect_uri="http://localhost:8888/callback",
scope="user-read-recently-played",
cache_path=".spotify_cache"
))
运行第一次时会弹出浏览器要求授权,之后Token会缓存到.spotify_cache文件里,过期自动刷新,不用你再管。
我对比一下两种方式的使用场景:
| 方式 | 优点 | 缺点 | 什么时候用 |
|---|---|---|---|
| 手写requests | 理解OAuth原理 | 代码量大,Token刷新要自己写 | 学习、面试、排查问题 |
| spotipy | 代码简洁、自动刷新 | 把原理藏起来了 | 实际项目、快速原型 |
我的建议是:第一次用spotipy把流程跑通,拿到数据之后,再回头手写一次认证。两条路都走一遍,你对OAuth的理解才算真正到位。
3. 拉取听歌历史:接口、参数与数据清洗
3.1 recently-played接口的基本用法
Spotify API中和听歌历史相关的接口是GET /v1/me/player/recently-played,它在文档里被归属于Player API,需要user-read-recently-played这个scope权限。
这个接口的基本用法非常简单:
python复制results = sp.current_user_recently_played(limit=50)
print(len(results["items"]))
print(results["items"][0]["played_at"])
print(results["items"][0]["track"]["name"])
用spotipy调用时返回值是一个嵌套很深的字典。我刚开始接触的时候看到那个数据结构是有点崩溃的,items列表里每个元素都有played_at和track两个大块,track里面又嵌套了artists、album、external_urls等一堆字段。
直接读这个嵌套结构很容易懵,建议先在Jupyter里跑一下print(json.dumps(results, indent=2, ensure_ascii=False)),把完整响应打印出来看一遍结构,再开始写提取逻辑,否则很容易写错字段名。
3.2 突破50条限制:利用before参数持续累积
这个接口有一个很关键的坑:它最多只返回最近50条播放记录,没有翻页参数。
我最初以为跟很多分页接口一样,用offset翻页就能拿到全部历史,结果反复试都只能拿到最后50首。翻了文档才发现,它的分页方式是通过before和after两个时间戳参数实现的:
limit:控制返回条数,最大50。before:返回在这个Unix毫秒时间戳之前的记录。after:返回在这个Unix毫秒时间戳之后的记录。
所以想要拿到更多历史数据,正确思路是:先正常拉一次,拿到这50条里最早一条的播放时间,然后把before设成这个时间戳,再拉一次,以此类推,直到拿不到数据为止。
但一次性往前挖也挖不了太远,这个接口本质上只保留最近一个时间段的数据。网上很多人都验证过,这个接口的实际数据窗口并不会特别长。要想做长期分析,正确做法是设置一个定时任务,每天或者每周跑一次,把结果累积保存下来。
累积抓取的思路是这样的:
python复制import pandas as pd
from datetime import datetime, timedelta
all_records = []
latest_ts = int(datetime.now().timestamp() * 1000)
while True:
batch = sp.current_user_recently_played(limit=50, before=latest_ts)
if len(batch["items"]) == 0:
break
all_records.extend(batch["items"])
latest_ts = batch["items"][-1]["played_at"]
用played_at的时间戳去更新latest_ts,就能不断往前翻。实际运行时会发现,这个循环往往跑不了几轮就到了数据窗口的边界。
3.3 把JSON处理成干净的DataFrame
拿到原始数据之后,接下来是做数据清洗。我的做法是把每条记录中关键字段提取出来,拼成列表,再转成DataFrame。
python复制records = []
for item in results["items"]:
track = item["track"]
records.append({
"played_at": item["played_at"],
"track_id": track["id"],
"track_name": track["name"],
"artist": ", ".join([artist["name"] for artist in track["artists"]]),
"album": track["album"]["name"],
"duration_ms": track["duration_ms"],
"popularity": track["popularity"],
})
df = pd.DataFrame(records)
df.to_csv("spotify_history.csv", index=False)
提取过程中有几个容易忽略的点,我踩过之后才意识到:
一是artists是数组不是字符串,一首歌可能有多个艺术家,如果不做", ".join()处理,存到DataFrame里就变成了列表对象,后续处理会很麻烦。
二是played_at返回的是ISO格式的字符串,带时区偏移,不能直接拿来做时间分析,需要先转成datetime类型。
三是popularity这个字段,它是这首歌在整个平台上的热度,范围0到100,不是你自己播放的频率,分析时不要搞混。
清洗完之后的DataFrame大概长这样:
| played_at | track_name | artist | album | duration_ms | popularity |
|---|---|---|---|---|---|
| 2025-05-10T08:30:00 | Midnight City | M83 | Hurry Up, We're Dreaming | 245306 | 82 |
| 2025-05-10T08:25:00 | Intro | The xx | xx | 127720 | 76 |
这一步做完,后面所有分析都是在清洗后的DataFrame上进行。
4. 时间维度:你到底在什么时间听歌
4.1 时间戳解析与小时分布
数据到手之后,我做的第一件事是看时间分布。把played_at转成datetime类型,提取出小时,看看一天24小时里哪个时间段播放记录最多。
python复制df["played_at_dt"] = pd.to_datetime(df["played_at"])
df["hour"] = df["played_at_dt"].dt.hour
这里有个很隐蔽的问题:played_at返回的字符串是带时区偏移的,比如2025-05-10T08:30:00+00:00。如果你直接.dt.hour,拿到的可能是UTC时间,不是你的本地时间。
我第一次分析的时候没注意这个,结果发现凌晨3点听歌最多,整个人都懵了,因为我那会儿肯定在睡觉。后来排查发现是时区没转换。
正确做法是先转成UTC-aware的datetime,再转换到你的本地时区:
python复制df["played_at_dt"] = pd.to_datetime(df["played_at"], utc=True)
df["played_at_local"] = df["played_at_dt"].dt.tz_convert("Asia/Shanghai")
df["hour"] = df["played_at_local"].dt.hour
转换完之后再看小时分布就有意义了:
python复制hour_counts = df["hour"].value_counts().sort_index()
import matplotlib.pyplot as plt
import seaborn as sns
plt.figure(figsize=(12, 5))
sns.barplot(x=hour_counts.index, y=hour_counts.values)
plt.title("24小时听歌分布")
plt.xlabel("小时")
plt.ylabel("播放次数")
plt.show()
我自己的数据做出来之后,峰值出现在上午8点到9点和晚上10点到12点。上午那段显然是通勤路上,晚上那段是睡前放松,逻辑完全说得通。
4.2 工作日和周末的差异
只看小时分布还不够,我进一步把工作日和周末拆开对比,发现差异非常明显。
python复制df["weekday"] = df["played_at_local"].dt.dayofweek # 周一是0,周日是6
df["is_weekend"] = df["weekday"].apply(lambda x: 1 if x >= 5 else 0)
周六和周日的数据量确实比工作日少,这倒不意外,因为我周末经常不想开音乐软件。更有意思的是周末的听歌峰值从晚上的10点挪到了下午2点到4点。工作日的听歌行为基本被通勤和午休框住了,周末反而随着生活节奏变得松散。
这两种完全不同的节奏,放到一张图上对比就很直观:
python复制weekday_df = df[df["is_weekend"] == 0]
weekend_df = df[df["is_weekend"] == 1]
weekday_count = weekday_df["hour"].value_counts().sort_index()
weekend_count = weekend_df["hour"].value_counts().sort_index()
plt.figure(figsize=(12, 5))
plt.plot(weekday_count.index, weekday_count.values, label="工作日")
plt.plot(weekend_count.index, weekend_count.values, label="周末")
plt.legend()
plt.show()
4.3 从月份趋势看口味变化
如果你的历史数据积累得够久,可以把月份维度也加上。我这里的时间跨度不够长,但我用累积数据做过一个粗略的版本,按月份统计播放量和Top艺人,能明显看到几个月里口味的变化。
python复制df["month"] = df["played_at_local"].dt.to_period("M")
monthly_counts = df.groupby("month").size()
monthly_counts.plot(kind="line", marker="o")
这种趋势分析对长期收集数据的场景更有意义,攒半年以上再看会很有意思。如果你刚开始做,可以先专注于小时和星期维度的分析,等数据量多了再扩展。
5. 内容维度:排行榜和音频特征
5.1 艺术家/曲目TOP榜
时间维度分析完之后,自然就会想看内容维度:我到底最喜欢谁?
统计排行榜用pandas的value_counts就能搞定:
python复制top_artists = df["artist"].value_counts().head(10)
top_tracks = df["track_name"].value_counts().head(10)
print(top_artists)
print(top_tracks)
不过直接用track_name统计有一个坑:不同专辑里可能有重名的歌,或者同一首歌的Live、Remix版本名字不一样。稳妥的做法是用track_id统计,然后再映射回歌名:
python复制track_counts = df.groupby("track_id").size().sort_values(ascending=False)
top_tracks = df.drop_duplicates("track_id").set_index("track_id").loc[track_counts.index]
排行榜画成横向条形图最直观:
python复制top_artists_df = df.groupby("artist").size().sort_values(ascending=False).head(10)
plt.figure(figsize=(10, 6))
sns.barplot(
x=top_artists_df.values,
y=top_artists_df.index,
)
plt.title("我的Top10艺人")
plt.show()
5.2 音频特征:一首歌的"体检报告"
排行榜只是数量维度的统计,想要更深入了解自己口味,还得看音频特征。
Spotify会对每首歌生成一组0到1之间的特征值,这些特征描述了一首歌的"气质":
| 特征 | 含义 |
|---|---|
| danceability | 舞蹈性,节奏是否适合跟着摇摆 |
| energy | 能量,歌曲的强度和活跃度 |
| valence | 情绪积极度,越接近1越欢快,越接近0越忧郁 |
| acousticness | 原声性,是否偏器乐现场感 |
| instrumentalness | 器乐性,有没有人声 |
| speechiness | 语言密度,说唱、朗读类偏高 |
| liveness | 现场感,是否像演唱会录音 |
获取这些特征需要调用GET /v1/audio-features/{id}接口,spotipy里封装好了sp.audio_features()方法,支持一次传入最多100个track_id。
python复制track_ids = df["track_id"].drop_duplicates().tolist()
all_features = []
for i in range(0, len(track_ids), 100):
batch = track_ids[i:i+100]
feats = sp.audio_features(batch)
all_features.extend([f for f in feats if f is not None])
feature_df = pd.DataFrame(all_features)
feature_df = feature_df[["id", "danceability", "energy", "valence",
"acousticness", "instrumentalness", "speechiness", "liveness"]]
请求返回的字段非常多,包括tempo、key、mode这些作曲参数,初期先用文档里已经描述清楚的核心特征就好。
特别提醒一下:sp.audio_features()的返回列表里,个别元素可能是None,脚本不像正式工程项目会去做严格的异常处理,但最好还是过滤一遍,不然后面合并表会报错。
5.3 用雷达图画出自己的听歌偏好
拿到特征值以后,最简单直观的分析就是算均值,然后画雷达图。
python复制feature_cols = ["danceability", "energy", "valence",
"acousticness", "instrumentalness", "speechiness", "liveness"]
mean_features = feature_df[feature_cols].mean().round(2)
print(mean_features)
画雷达图用plotly的Scatterpolar最方便,一次性出交互图:
python复制import plotly.graph_objects as go
fig = go.Figure(data=go.Scatterpolar(
r=mean_features.values,
theta=feature_cols,
fill="toself",
name="我的听歌特征均值"
))
fig.update_layout(
polar=dict(radialaxis=dict(visible=True, range=[0, 1])),
title="我的听歌偏好雷达图"
)
fig.show()
我自己的数据画出来之后,energe大概在0.68左右,valence在0.45上下,acousticness偏低,说明我平时听的歌整体偏动感但情绪又不够单纯明快。后来想想确实如此,我歌单里大量的是带点合成器音色的独立摇滚,很多歌表面旋律很亮,歌词却很丧。
这种"特征均值+主观感受交叉验证"的过程,是这个项目里最有意思的部分。
6. 进阶玩法:用聚类发现"隐藏歌单"
6.1 为什么要聚类
做完基本统计之后,我一直在想一个问题:光看均值会掩盖掉很多结构,比如一个又听重型摇滚又听轻音乐的人,均值算出来可能处于中间状态,看起来"口味平淡",但实际上他的世界里有明确的两极。
KMeans聚类的价值就在这里——它可以通过音频特征,把你自己都未必意识到的听歌结构找出来。
我在自己的歌曲列表上做了这个实验,聚类出来的结果让人惊喜。
6.2 数据标准化与K值选择
聚类之前必须做数据标准化,这个很多新手会忽略。因为KMeans是基于距离的算法,如果特征量纲不一致,数值大的特征会主导聚类结果。好在音频特征都是0到1之间,量纲是一致的,但保险起见我还是做了标准化。
python复制from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import silhouette_score
X = feature_df[feature_cols].values
X_scaled = StandardScaler().fit_transform(X)
for k in range(2, 7):
model = KMeans(n_clusters=k, random_state=42, n_init=10)
labels = model.fit_predict(X_scaled)
score = silhouette_score(X_scaled, labels)
print(f"k={k}, 轮廓系数={score:.4f}")
轮廓系数越高,说明样本和它所属簇的相似度越高,聚类效果越好。我这边算出来k=3时轮廓系数最高,说明我的听歌口味大致可以分成三类。
定下k=3之后,正式建模并给每首歌曲打上簇标签:
python复制model = KMeans(n_clusters=3, random_state=42, n_init=10)
feature_df["cluster"] = model.fit_predict(X_scaled)
6.3 解读聚类结果
聚类完之后最关键的是解读。把每个簇的特征均值算出来,然后给它命名。
python复制cluster_profile = feature_df.groupby("cluster")[feature_cols].mean().round(3)
print(cluster_profile)
我自己得出的三个簇非常清晰:
- 簇0:danceability高、energy高、valence高,典型的"周末晚上出门前听的歌"。
- 簇1:energy低、acousticness高、instrumentalness高,典型的"工作或者看书时的背景乐"。
- 簇2:valence低、speechiness偏高、energy中等,典型的"深夜胡思乱想时听的歌"。
然后把这个簇标签合并回原始播放记录,就能看到一天里不同时段会落在哪个簇:
python复制df = df.merge(feature_df[["id", "cluster"]], left_on="track_id", right_on="id")
df.groupby(["hour", "cluster"]).size()
# 用透视表看清时段和簇的关系
pivot = pd.pivot_table(
df,
index="hour",
columns="cluster",
values="track_name",
aggfunc="count",
fill_value=0
)
从透视表能明显看到,白天工作时段簇1的比例更高,晚上10点之后簇2的比例快速上升。这个发现完全符合直觉,但把它用数据的方式验证出来,感觉是完全不同的。
7. 全部踩坑记录:从Token过期到时区陷阱
最后把我在整个过程中遇到过的坑集中整理一遍。这些坑单个看都不大,但碰上的时候非常耽误时间。
7.1 played_at字段的时区问题
前面已经提到过,played_at字段是有时区偏移的ISO 8601字符串。我在实际项目里的处理方式是先转成UTC-aware datetime,再转换到本地时区。
如果你直接用pd.to_datetime(df["played_at"]).dt.hour,pandas会在没有时区信息时默认为naive时间,直接取本地时区会便宜行事,但最终结果对不对就看运气。这种情况我建议统一用utc=True参数强制指定时区,避免服务器和本地时区不一致带来的问题。
7.2 音频特征接口的批量上限
sp.audio_features()一次最多只能传100个track_id。超过100个会被降级处理,个别ID可能返回空结果。我在一次抓取2000多首歌曲特征时,因为没做分批处理,导致部分歌曲的特征直接缺失,后来加了个循环才解决。
另一个细节是在抓取时过滤返回值为None的元素,有的歌曲ID无效或者音频特征不可用,接口会返回null而不是报错,不过滤的话整个DataFrame都会出问题。
7.3 Token过期与缓存
如果你手写Token刷新逻辑,一定要处理好refresh_token。access_token有效期只有1小时,过期之后所有请求都会返回401,排查这类问题的方法是在请求函数里打印响应状态码。
用spotipy则省心很多,它会把Token缓存到cache_path指定的文件里,并自动用refresh_token刷新。不过要注意:如果同时开了多个进程,缓存文件会被反复读写,偶尔会出现token失效报错,可以在脚本末尾加个清理逻辑。
7.4 其他容易忽略的细节
有几个小地方虽然不影响主流程,但第一次遇到的人很容易迷茫。
第一个是scopes权限。user-read-recently-played和user-read-private是两个不同权限,如果某个接口报403,第一步检查是不是scope没带上。我把scope直接写在SpotifyOAuth初始化参数里,这样最不容易漏。
第二个是免费账号与权限差异。recently-played这个接口免费账号通常是可以用的,但不同地区账号的支持情况不完全一样。如果发现请求返回地区不可用之类的错误,先检查应用支持的市场范围和技术文档说明,不一定是你代码的问题。
第三个是数据去重。同一首歌在短时间被连续播放多次,会在历史记录里产生多条记录。做时长类分析时,建议在时间维度上再做一次持续播放窗口合并;做歌曲特征分析时,则要对track_id去重,否则重复播放的歌曲会对聚类结果产生不成比例的影响。
第四个是不要把Token和Client Secret提交到GitHub。哪怕仓库是私有的,也建议用环境变量管理这些敏感信息。我身边确实有朋友顺手把自己的Client Secret推到公开仓库,没过多久就被人盗刷API配额。
整个项目做下来,我最深的体会是:数据分析和写代码不一样,写代码的成就感在"跑通",数据分析的成就感在"发现"。跑通API调用只是开始,真正有意思的是从几百条听歌记录里看到自己的生活节奏,看到那些连自己都没意识到的偏好。如果你也在学Python,强烈推荐拿自己的Spotify数据试一次。就算最后只画出一张小时分布柱状图,也比在教程网站上刷十道练习题收获大。
