1. 项目背景与核心价值
作为一名音乐发烧友兼数据爱好者,我一直在寻找量化自己听歌习惯的方法。Spotify的年度总结虽然有趣,但颗粒度太粗且无法自定义。于是萌生了一个想法:能不能像记账软件记录消费那样,完整记录自己的音乐消费行为?
这个项目的核心价值在于:
- 数据主权回归用户:摆脱平台限制,建立个人专属的音乐数据库
- 时间维度分析:从"年回顾"升级到"周统计",捕捉更细微的听歌模式变化
- 风格偏好洞察:通过算法识别那些自己都没意识到的音乐品味倾向
- 可扩展性:基础架构做好后,可以轻松添加播放情绪分析、时段偏好等维度
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据采集方案设计
2.1 主流音乐平台API调研
经过实测对比三大平台的数据接口:
| 平台 | 历史记录范围 | 单次请求上限 | 包含信息 | 获取难度 |
|---|---|---|---|---|
| Spotify | 最近50条 | 50条 | 曲名/艺人/时长/专辑 | ★★☆☆☆ |
| 网易云音乐 | 全部历史 | 1000条 | 曲名/艺人/时长/风格标签 | ★★★☆☆ |
| QQ音乐 | 最近100条 | 100条 | 曲名/艺人/时长 | ★★★★☆ |
实测发现网易云的"听歌排行"API最友好,通过开发者工具抓包可以看到返回数据包含
style字段,这对后续风格分析至关重要。
2.2 自动化采集实现
使用Python+Requests构建采集器时,有几个关键细节需要注意:
python复制def fetch_netease_history(user_id, limit=1000):
headers = {
'Cookie': '你的登录Cookie', # 通过浏览器登录后获取
'Referer': 'https://music.163.com/'
}
params = {
'uid': user_id,
'type': '-1', # -1表示全部历史记录
'limit': limit,
'offset': 0
}
response = requests.get(
'https://music.163.com/api/v1/play/history',
headers=headers,
params=params
)
return response.json()['data']
避坑指南:
- Cookie需要定期更新(约15天失效)
- 网易云对高频请求有限流,建议添加
time.sleep(1)避免被封 - 返回的时长单位是毫秒,需要做
/1000转换
3. 数据存储与清洗
3.1 数据库选型对比
考虑到音乐记录的特点:
- 写多读少(每天新增几十条)
- 需要时间范围查询
- 后期可能要做聚合分析
最终选择SQLite作为存储方案:
python复制import sqlite3
from datetime import datetime
def init_db():
conn = sqlite3.connect('music_stats.db')
c = conn.cursor()
c.execute('''CREATE TABLE IF NOT EXISTS play_history
(id INTEGER PRIMARY KEY AUTOINCREMENT,
song_id TEXT,
song_name TEXT,
artist TEXT,
duration INTEGER,
style TEXT,
play_time TIMESTAMP)''')
conn.commit()
conn.close()
优化技巧:
- 对
song_id建立索引加速去重 - 使用
TIMESTAMP类型存储播放时间,方便按周聚合 - 添加
last_update字段记录采集时间
3.2 数据清洗关键点
原始数据常见问题及处理方法:
| 问题类型 | 解决方案 | 示例代码 |
|---|---|---|
| 重复记录 | 根据song_id+play_time去重 | GROUP BY song_id, play_time |
| 风格标签缺失 | 调用网易云歌曲详情API补全 | /api/v3/song/detail?id={song_id} |
| 时间格式混乱 | 统一转为UTC时间戳 | datetime.strptime().timestamp() |
| 异常时长 | 过滤<30秒或>1小时的记录 | WHERE duration BETWEEN 30 AND 3600 |
4. 听歌时长统计实现
4.1 周维度聚合算法
核心SQL查询语句:
sql复制SELECT
strftime('%Y-%W', play_time) AS week,
SUM(duration)/3600 AS total_hours,
COUNT(DISTINCT song_id) AS unique_songs
FROM play_history
GROUP BY week
ORDER BY week DESC
注意事项:
%Y-%W格式会返回"年-周数"- 除以3600将秒转为小时
- 使用
DISTINCT避免重复歌曲干扰统计
4.2 可视化方案
使用Matplotlib+Seaborn绘制热力图效果最佳:
python复制import seaborn as sns
import matplotlib.pyplot as plt
def plot_weekly_heatmap(data):
# 数据预处理:转为周x时段的矩阵
pivot_data = data.pivot_table(values='duration',
index='day_of_week',
columns='hour',
aggfunc='count')
plt.figure(figsize=(12, 6))
sns.heatmap(pivot_data, cmap="YlGnBu", annot=True, fmt="d")
plt.title("Weekly Listening Pattern")
plt.xlabel("Hour of Day")
plt.ylabel("Day of Week")
plt.show()

(实际使用需替换为真实图表)
5. 音乐风格分析进阶
5.1 风格标签标准化
网易云返回的风格标签存在大量同义异形词,需要建立映射表:
python复制style_mapping = {
'流行': 'Pop',
'華語流行': 'Mandopop',
'獨立音樂': 'Indie',
'電子': 'Electronic',
'電音': 'EDM',
'搖滾': 'Rock',
'民謠': 'Folk',
'嘻哈': 'Hip-Hop',
'R&B': 'R&B',
# 其他映射...
}
def normalize_style(raw_style):
return style_mapping.get(raw_style, 'Other')
5.2 偏好度计算算法
简单的频率统计会偏向播放量大的风格,改进方案:
code复制偏好度 = (该风格播放时长 / 总时长) × log(该风格独特歌曲数)
Python实现:
python复制def calculate_preference(df):
total_hours = df['duration'].sum()
style_stats = df.groupby('style').agg({
'duration': 'sum',
'song_id': pd.Series.nunique
})
style_stats['preference'] = (style_stats['duration']/total_hours) * np.log(style_stats['song_id'])
return style_stats.sort_values('preference', ascending=False)
5.3 风格演进分析
使用滚动窗口观察品味变化:
python复制# 计算每4周的风格偏好变化
weekly_style = df.groupby(['week', 'style'])['duration'].sum().unstack()
rolling_style = weekly_style.rolling(window=4).mean()
# 绘制堆叠面积图
rolling_style.plot.area(figsize=(12,6), colormap='tab20')
plt.title('Music Style Evolution (4-week Rolling)')
plt.ylabel('Listening Hours')
plt.xlabel('Week Number')
6. 系统优化与扩展
6.1 增量更新机制
为避免重复采集,添加状态记录:
python复制def get_last_update():
"""获取最后采集的时间点"""
try:
with open('last_update.txt', 'r') as f:
return datetime.fromisoformat(f.read())
except FileNotFoundError:
return datetime(2000,1,1) # 默认从2000年开始
def save_last_update():
"""记录本次采集时间"""
with open('last_update.txt', 'w') as f:
f.write(datetime.now().isoformat())
6.2 异常检测模块
识别听歌行为异常(如设备被盗用):
python复制def detect_anomalies(df):
# 计算每日听歌时长的Z-Score
daily = df.resample('D', on='play_time').sum()
daily['zscore'] = (daily['duration'] - daily['duration'].mean()) / daily['duration'].std()
# 标记3个标准差以外的日期
anomalies = daily[daily['zscore'].abs() > 3]
if not anomalies.empty:
send_alert(f"异常听歌行为检测:{anomalies.index.date}")
6.3 移动端可视化
使用PyWebIO快速搭建看板:
python复制from pywebio.output import put_html
import plotly.express as px
def show_dashboard():
df = load_data()
fig = px.pie(df, names='style', values='duration',
title='音乐风格分布')
put_html(fig.to_html(include_plotlyjs='cdn'))
7. 实战经验与避坑指南
设备兼容性问题:
- 网易云APP和网页版的历史记录不同步,建议固定使用一个端
- 跨设备登录会导致记录混乱,可以在数据库添加
device_id字段区分
数据补全技巧:
- 对于没有风格标签的歌曲,可以用Last.fm的API补全
- 使用
lyricsgenius获取歌词分析情感倾向
长期维护建议:
- 设置crontab每天凌晨自动运行采集脚本
- 每月备份一次数据库文件
- 在NAS上搭建私有看板,避免数据泄露
这个项目最让我意外的发现是:工作日通勤时总会不自觉地循环某几首电子乐,而周末则偏好爵士。通过量化分析,我才意识到环境对音乐选择的巨大影响。现在我会刻意在周末探索新风格,打破算法构建的信息茧房。
