1. 项目概述:当Python遇上你的音乐品味
作为一名长期使用Spotify的音乐爱好者,我发现自己经常对着年度音乐报告发呆——那些统计数字背后藏着太多未被挖掘的信息。直到某天用Python抓取了自己的听歌数据,才发现原来播放列表里藏着这么多秘密:凌晨两点偏爱后摇、周五下午总会切到Disco、甚至能精确找出哪个月沉迷City Pop。这种数据驱动的自我发现过程,比Spotify官方提供的年度总结有趣十倍。
这个项目本质上是通过Spotify官方API获取个人听歌记录,利用Python生态中的数据分析工具进行深度挖掘。不同于简单的播放次数统计,我们将实现:
- 时间维度分析(不同时段/星期/月份的音乐偏好变化)
- 音频特征解析(常听歌曲的BPM、情绪值等参数)
- 艺人/流派关联网络(发现你意想不到的音乐关联)
- 自定义数据可视化(制作比官方报告更个性的图表)
提示:虽然需要编程基础,但代码都已模块化封装,跟着步骤操作即可。最终你会得到一份HTML格式的交互式报告,包含各种你从未想过的音乐洞察。
2. 核心工具链与准备工作
2.1 Spotify开发者账号申请
在Spotify开发者仪表板创建应用,获取CLIENT_ID和CLIENT_SECRET。注意选择"个人用途"而非商业用途,权限范围勾选:
user-read-recently-playeduser-top-readplaylist-read-private
2.2 Python环境配置
推荐使用conda创建独立环境:
bash复制conda create -n spotify-analysis python=3.9
conda activate spotify-analysis
pip install spotipy pandas matplotlib seaborn networkx plotly
2.3 认证流程实现
创建auth.py文件处理OAuth流程,关键代码如下:
python复制import spotipy
from spotipy.oauth2 import SpotifyOAuth
scope = "user-read-recently-played user-top-read playlist-read-private"
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(
scope=scope,
redirect_uri="http://localhost:8888/callback")
)
3. 数据获取与清洗实战
3.1 获取近期播放记录
通过sp.current_user_recently_played(limit=50)获取数据后,需要处理两个关键问题:
- 去重:Spotify的API可能返回重复条目
- 时区转换:时间戳默认是UTC,需转换为本地时间
python复制def get_clean_history(sp_client, days=30):
raw = []
for i in range(0, 1000, 50):
batch = sp_client.current_user_recently_played(limit=50, after=int(time.time())*1000 - days*86400*1000)
raw.extend(batch['items'])
# 去重逻辑
seen = set()
return [x for x in raw if not (x['played_at'] in seen or seen.add(x['played_at']))]
3.2 音频特征增强
每个曲目的音频特征(如danceability、valence等)需要单独请求:
python复制def get_audio_features(sp_client, track_ids):
features = []
for i in range(0, len(track_ids), 50):
batch = sp_client.audio_features(track_ids[i:i+50])
features.extend([f for f in batch if f])
return pd.DataFrame(features)
4. 深度分析技巧揭秘
4.1 时间模式挖掘
使用pandas.resample可以轻松发现听歌习惯:
python复制# 按小时统计播放量
hourly = df.resample('H', on='played_at_local')['track_name'].count()
# 周末vs工作日对比
df['is_weekend'] = df['played_at_local'].dt.dayofweek >= 5
weekday_avg = df.groupby(['is_weekend', df['played_at_local'].dt.hour]).size()
4.2 情绪雷达图制作
结合多个音频特征生成情绪画像:
python复制features = ['danceability', 'energy', 'speechiness',
'acousticness', 'instrumentalness', 'valence']
radar_data = df[features].mean().values
angles = np.linspace(0, 2*np.pi, len(features), endpoint=False)
fig = plt.figure(figsize=(8,8))
ax = fig.add_subplot(111, polar=True)
ax.plot(angles, radar_data, 'o-', linewidth=2)
ax.fill(angles, radar_data, alpha=0.25)
5. 高级可视化实战
5.1 艺人关系网络图
使用networkx构建艺人共现网络:
python复制import networkx as nx
# 构建共现矩阵
co_matrix = pd.crosstab(df['track_id'], df['artist_name']).T.dot(
pd.crosstab(df['track_id'], df['artist_name']))
# 创建图对象
G = nx.from_pandas_adjacency(co_matrix[co_matrix > 3])
# 绘制力导向图
plt.figure(figsize=(15,15))
pos = nx.spring_layout(G, k=0.15)
nx.draw_networkx_nodes(G, pos, node_size=50)
nx.draw_networkx_edges(G, pos, alpha=0.2)
nx.draw_networkx_labels(G, pos, font_size=8)
5.2 交互式时间线
用Plotly实现可缩放的时间轴:
python复制import plotly.express as px
fig = px.scatter(df, x='played_at_local', y='danceability',
color='artist_name', hover_data=['track_name'],
title="Danceability Trend Over Time")
fig.update_layout(xaxis_rangeslider_visible=True)
fig.show()
6. 避坑指南与性能优化
6.1 API限流处理
Spotify API有严格速率限制(每分钟300次请求),需要添加重试逻辑:
python复制from tenacity import retry, stop_after_attempt, wait_exponential
@retry(stop=stop_after_attempt(5), wait=wait_exponential(multiplier=1, min=4, max=10))
def safe_api_call(func, *args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as e:
if e.code == 429:
print(f"Rate limited, sleeping {e.retry_after} seconds")
time.sleep(int(e.retry_after))
raise
6.2 数据缓存策略
建议使用SQLite存储原始数据避免重复请求:
python复制import sqlite3
def cache_to_db(data, db_file='spotify_cache.db'):
conn = sqlite3.connect(db_file)
data.to_sql('play_history', conn, if_exists='append', index=False)
conn.close()
7. 个性化报告生成
最后将所有分析整合成HTML报告:
python复制from jinja2 import Template
with open('report_template.html') as f:
template = Template(f.read())
html = template.render(
top_artists=top_artists,
time_heatmap=time_heatmap,
radar_chart=radar_chart
)
with open('my_spotify_report.html', 'w') as f:
f.write(html)
注意:完整项目代码应包含异常处理和日志记录,这里为简洁省略。实际使用时建议添加try-catch块和logging模块记录运行状态。
我在三个月的数据分析中发现几个反直觉的结论:雨天反而更常听高BPM歌曲;通勤时播放的歌曲中有23%后来被标记为"不喜欢";凌晨3-4点收听的曲目平均时长比其他时段长42秒。这些发现让我重新思考自己的音乐消费习惯——也许我们远比自己想象的更受环境因素影响。