
1. 项目概述用Python挖掘你的Spotify音乐DNA作为一名长期使用Spotify的音乐爱好者兼Python开发者我最近发现了一个有趣的现象虽然平台每年都会推出年度回顾功能但那些预制报表始终无法满足我对听歌数据的深度挖掘需求。于是我用Python构建了一套完整的分析工具链不仅能还原官方报告的所有维度还能发现许多隐藏的音乐偏好模式。这个项目的核心价值在于通过Spotify开放的Web API我们可以获取到包括播放频率、歌曲特征、时间分布等在内的完整听歌记录。结合Python生态中丰富的数据分析工具每个人都能像专业音乐分析师一样从多个角度解读自己的听觉习惯。2. 环境准备与API接入2.1 创建Spotify开发者应用首先需要在 Spotify开发者仪表板 新建应用点击Create an App按钮填写应用名称如My Listening Analytics记录下自动生成的Client ID和Client Secret重要提示在应用设置中必须添加回调地址本地开发建议使用http://localhost:8888/callback2.2 安装必要的Python库推荐使用conda创建独立环境conda create -n spotify-analysis python3.9 conda activate spotify-analysis pip install spotipy pandas matplotlib seaborn sklearn关键库说明spotipy官方推荐的Python SDKpandas数据处理核心工具matplotlib/seaborn可视化双雄sklearn用于高级聚类分析2.3 实现OAuth认证流程建立auth_manager.py处理认证逻辑import spotipy from spotipy.oauth2 import SpotifyOAuth def get_spotify_client(): return spotipy.Spotify(auth_managerSpotifyOAuth( client_id你的CLIENT_ID, client_secret你的CLIENT_SECRET, redirect_urihttp://localhost:8888/callback, scopeuser-library-read user-top-read user-read-recently-played ))3. 数据采集与清洗3.1 获取核心听歌数据通过Spotify API可以获取多种数据类型建议分批采集def fetch_all_data(sp): # 最近50条播放记录 recently_played sp.current_user_recently_played(limit50) # 收藏的歌曲最多50首 saved_tracks sp.current_user_saved_tracks(limit50) # 长期听歌偏好分短期/中期/长期 top_tracks_short sp.current_user_top_tracks(time_rangeshort_term, limit50) top_tracks_medium sp.current_user_top_tracks(time_rangemedium_term, limit50) top_tracks_long sp.current_user_top_tracks(time_rangelong_term, limit50) return { recently_played: recently_played, saved_tracks: saved_tracks, top_tracks: { short_term: top_tracks_short, medium_term: top_tracks_medium, long_term: top_tracks_long } }3.2 数据标准化处理原始API返回的JSON结构复杂需要转换为扁平化表格def flatten_track_data(raw_data): tracks [] for item in raw_data[items]: track item[track] if track in item else item features { id: track[id], name: track[name], artist: , .join([a[name] for a in track[artists]]), duration_ms: track[duration_ms], popularity: track[popularity], played_at: item.get(played_at, None), saved_at: item.get(added_at, None) } tracks.append(features) return pd.DataFrame(tracks)3.3 补充音频特征数据Spotify为每首歌曲提供了专业的音频分析def add_audio_features(sp, df): features sp.audio_features(df[id].tolist()) feature_cols [ danceability, energy, key, loudness, mode, speechiness, acousticness, instrumentalness, liveness, valence, tempo ] for f in features: for col in feature_cols: df.loc[df[id] f[id], col] f[col] return df4. 多维分析实战4.1 基础统计指标先快速生成描述性统计print(df[[danceability, energy, valence, tempo]].describe())典型输出示例danceability energy valence tempo count 150.000000 150.000000 150.000000 150.000000 mean 0.654213 0.672133 0.513200 122.453867 std 0.143885 0.198762 0.236745 28.771422 min 0.285000 0.038700 0.037900 65.958000 25% 0.563250 0.543250 0.331750 100.027250 50% 0.672000 0.712000 0.518000 122.936000 75% 0.761750 0.837750 0.704750 140.052750 max 0.943000 0.983000 0.965000 206.0070004.2 时间维度分析分析不同时段的听歌偏好变化# 转换时间戳 df[hour] pd.to_datetime(df[played_at]).dt.hour # 绘制24小时听歌分布 plt.figure(figsize(12,6)) sns.countplot(xhour, datadf, paletteviridis) plt.title(Hourly Listening Distribution) plt.show()4.3 音乐特征雷达图通过雷达图直观展示音乐偏好def plot_radar_chart(df): features [danceability, energy, speechiness, acousticness, instrumentalness, liveness, valence] stats df[features].mean().values angles np.linspace(0, 2*np.pi, len(features), endpointFalse) stats np.concatenate((stats,[stats[0]])) angles np.concatenate((angles,[angles[0]])) features.append(features[0]) fig plt.figure(figsize(8,8)) ax fig.add_subplot(111, polarTrue) ax.plot(angles, stats, o-, linewidth2) ax.fill(angles, stats, alpha0.25) ax.set_thetagrids(angles * 180/np.pi, features) ax.set_title(Audio Features Radar Chart, y1.1) plt.show()5. 高级分析技巧5.1 基于K-Means的音乐聚类将歌曲按特征自动分组from sklearn.cluster import KMeans def cluster_tracks(df, n_clusters4): features df[[danceability, energy, valence, tempo]] features (features - features.mean()) / features.std() kmeans KMeans(n_clustersn_clusters, random_state42) df[cluster] kmeans.fit_predict(features) # 可视化聚类结果 plt.figure(figsize(10,6)) sns.scatterplot(xvalence, yenergy, huecluster, datadf, paletteviridis, s100) plt.title(Track Clusters by Audio Features) plt.show() return df5.2 歌手影响力网络图分析不同歌手在你的播放列表中的关联程度import networkx as nx def build_artist_network(df): # 创建共现矩阵 artists df[artist].str.split(, ).explode().unique() co_matrix pd.DataFrame(0, indexartists, columnsartists) for _, row in df.iterrows(): track_artists row[artist].split(, ) for a1 in track_artists: for a2 in track_artists: if a1 ! a2: co_matrix.loc[a1, a2] 1 # 构建网络图 G nx.Graph() for a1 in artists: for a2 in artists: if co_matrix.loc[a1, a2] 0: G.add_edge(a1, a2, weightco_matrix.loc[a1, a2]) # 可视化 plt.figure(figsize(15,15)) pos nx.spring_layout(G, k0.3) nx.draw_networkx_nodes(G, pos, node_size50) nx.draw_networkx_edges(G, pos, alpha0.2) nx.draw_networkx_labels(G, pos, font_size8) plt.title(Artist Co-occurrence Network) plt.show()6. 实战经验与避坑指南6.1 API调用优化技巧请求限速处理from time import sleep from random import uniform def safe_api_call(func, *args, **kwargs): try: return func(*args, **kwargs) except spotipy.exceptions.SpotifyException as e: if e.http_status 429: retry_after int(e.headers.get(Retry-After, 5)) sleep(retry_after uniform(0.5, 1.5)) return safe_api_call(func, *args, **kwargs) raise数据缓存策略import json from pathlib import Path def cached_api_call(cache_file, func, *args, **kwargs): cache_path Path(fcache/{cache_file}) if cache_path.exists(): with open(cache_path) as f: return json.load(f) data func(*args, **kwargs) cache_path.parent.mkdir(exist_okTrue) with open(cache_path, w) as f: json.dump(data, f) return data6.2 常见问题解决方案问题1spotipy.oauth2.SpotifyOauthError: Invalid redirect URI➔ 检查开发者仪表板设置的回调地址必须与代码中的redirect_uri完全一致包括末尾斜杠问题2获取的播放记录远少于实际播放量➔ Spotify API目前只返回最近50条播放记录如需更完整数据需要定期自动采集问题3音频特征数据存在NaN值➔ 通常是因为某些区域限制的曲目建议过滤处理df df.dropna(subset[danceability, energy])6.3 可视化优化建议动态交互图表import plotly.express as px fig px.scatter_3d(df, xdanceability, yenergy, zvalence, colorcluster, hover_namename, hover_data[artist, tempo]) fig.show()时间序列热力图# 按小时和星期分析播放模式 df[weekday] pd.to_datetime(df[played_at]).dt.day_name() hour_week df.groupby([hour, weekday]).size().unstack() plt.figure(figsize(12,8)) sns.heatmap(hour_week, cmapYlGnBu, annotTrue, fmtd) plt.title(Listening Pattern by Hour and Weekday) plt.show()7. 项目扩展方向自动化定期采集import schedule import time def daily_job(): data fetch_all_data(sp) timestamp datetime.now().strftime(%Y%m%d_%H%M%S) with open(fdata/history_{timestamp}.json, w) as f: json.dump(data, f) schedule.every().day.at(23:59).do(daily_job) while True: schedule.run_pending() time.sleep(60)音乐推荐引擎from sklearn.neighbors import NearestNeighbors def build_recommender(df): features df[[danceability, energy, valence]] nn NearestNeighbors(n_neighbors5).fit(features) return nn def recommend_similar(df, track_id, nn_model): track_features df[df[id]track_id][[danceability,energy,valence]] distances, indices nn_model.kneighbors(track_features) return df.iloc[indices[0]]情绪波动分析def mood_analysis(df): df[mood] pd.cut(df[valence], bins[0,0.3,0.7,1], labels[低情绪,中性,高情绪]) plt.figure(figsize(10,6)) sns.countplot(xhour, huemood, datadf, palettecoolwarm) plt.title(Mood Distribution by Hour) plt.show()