用Python分析网易云热门歌单:揭秘用户最爱的音乐类型与UP主排行
用Python解码网易云热门歌单:数据背后的音乐偏好与创作者生态
你是否曾好奇,那些动辄收藏量破十万、播放量上亿的网易云歌单,究竟藏着怎样的秘密?为什么有些歌单能持续霸榜,而有些则默默无闻?作为一位音乐爱好者兼数据分析师,我常常被这些问题吸引。音乐平台的歌单,早已超越了简单的歌曲集合,它成为了解大众音乐品味、追踪流行趋势、甚至洞察创作者生态的绝佳窗口。今天,我们就用Python这把“手术刀”,抛开主观感受,从纯粹的数据视角,深入剖析网易云音乐的热门歌单,看看数据能告诉我们哪些关于音乐、用户和创作者的真相。
这篇文章面向那些对音乐、数据和技术交叉领域感兴趣的朋友。无论你是想为自己的音乐项目寻找数据支撑的产品经理,是希望从数据中寻找灵感的独立音乐人,还是单纯想用技术探索爱好的开发者,相信都能从中获得启发。我们将从数据采集、清洗、分析到可视化,完整走一遍流程,但重点不在于复现一个爬虫脚本,而在于如何从海量数据中提炼出有商业或人文价值的洞察。你会发现,一行行代码和一张张图表背后,是流动的音乐潮流和鲜活的用户故事。
1. 数据采集:构建你的音乐数据仓库
任何数据分析项目的第一步,都是获取高质量的数据。对于网易云音乐的歌单数据,我们需要一个稳健、高效且尊重平台规则的采集策略。直接复制粘贴网上的爬虫代码往往行不通,因为平台的反爬机制和页面结构时常更新。更重要的是,我们需要思考:到底要采集哪些数据维度,才能支撑后续的深度分析?
一个歌单的价值,远不止歌曲列表。它包含了丰富的元数据(Metadata),这些元数据正是我们分析的基石。我通常会将采集目标分为三个层次:
- 歌单基础信息层:歌单标题、创建者(UP主)、播放量、收藏量、评论数、分享数、创建/更新时间、歌曲数量。
- 歌单内容描述层:标签(如“流行”、“摇滚”、“夜晚”)、简介文字、封面图链接。
- 歌单社会互动层:热门评论内容、最新评论内容、收藏用户列表(若可获取)。
采集这些数据,我们需要模拟浏览器行为,并处理可能遇到的动态加载、加密参数等问题。这里分享一个经过实战检验的请求策略核心思路,它更侧重于稳定性和可维护性,而非一味追求速度。
import requests
import time
import pandas as pd
from typing import Dict, List, Optional
class NeteasePlaylistCrawler:
def __init__(self):
self.session = requests.Session()
# 精心构造的请求头,模拟真实浏览器
self.headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'Accept-Encoding': 'gzip, deflate, br',
'Referer': 'https://music.163.com/',
'Connection': 'keep-alive',
}
self.session.headers.update(self.headers)
def fetch_playlist_list(self, category: str = '全部', limit: int = 35, max_pages: int = 10) -> List[Dict]:
"""
获取歌单列表页信息
:param category: 歌单分类,如'华语'、'欧美'、'流行'
:param limit: 每页歌单数量
:param max_pages: 最大爬取页数
:return: 歌单基础信息列表
"""
playlists = []
base_url = 'https://music.163.com/discover/playlist/'
for page in range(0, max_pages):
offset = page * limit
params = {
'cat': category,
'order': 'hot', # 按热度排序
'limit': limit,
'offset': offset
}
try:
resp = self.session.get(base_url, params=params, timeout=10)
resp.raise_for_status()
# 这里需要解析HTML,提取歌单ID、标题、播放量、UP主等信息
# 使用BeautifulSoup或lxml进行解析
page_data = self._parse_list_page(resp.text)
playlists.extend(page_data)
print(f"已获取第 {page+1} 页,累计 {len(playlists)} 个歌单")
time.sleep(1.5) # 礼貌性延迟,避免请求过快
except Exception as e:
print(f"获取第 {page+1} 页失败: {e}")
break
return playlists
def _parse_list_page(self, html: str) -> List[Dict]:
"""
解析歌单列表页HTML,提取关键信息。
这是一个示例函数骨架,实际解析规则需根据页面DOM结构调整。
"""
# 使用BeautifulSoup解析的逻辑应在此处实现
# 返回格式示例:[{'id': '123456', 'title':'深夜自习室', 'play_count':'1.2亿', 'creator':'某用户'}]
pass
注意:在实际操作中,解析函数
_parse_list_page需要你打开浏览器开发者工具,仔细研究目标网页的元素结构。网易云的页面结构可能变动,因此解析逻辑需要一定的灵活性和容错处理,比如使用try...except包裹可能缺失的字段提取。
获取到歌单ID列表后,下一步是抓取每个歌单的详情页数据。这里有一个关键技巧:优先使用官方API接口。通过浏览器开发者工具的“网络”(Network)选项卡,观察页面加载时发出的XHR或Fetch请求,常常能找到返回结构化JSON数据的API,这比解析HTML要稳定和高效得多。
假设我们找到了一个返回歌单详情的API端点,我们的采集函数可以这样设计:
def fetch_playlist_detail(self, playlist_id: str) -> Optional[Dict]:
"""
通过API获取歌单详情
"""
api_url = f'https://music.163.com/api/v6/playlist/detail'
params = {
'id': playlist_id,
'n': 1000, # 获取歌单内前1000首歌曲信息
'csrf_token': '' # 有时需要,有时不需要,视情况而定
}
try:
resp = self.session.get(api_url, params=params, timeout=10)
data = resp.json()
if data.get('code') == 200:
playlist = data['playlist']
detail_info = {
'id': playlist_id,
'name': playlist.get('name'),
'creator': playlist.get('creator', {}).get('nickname'),
'play_count': playlist.get('playCount'),
'subscribed_count': playlist.get('subscribedCount'), # 收藏量
'comment_count': playlist.get('commentCount'),
'share_count': playlist.get('shareCount'),
'track_count': playlist.get('trackCount'),
'tags': ','.join(playlist.get('tags', [])), # 标签合并为字符串
'description': playlist.get('description'),
'create_time': playlist.get('createTime'),
'update_time': playlist.get('updateTime'),
}
return detail_info
else:
print(f"歌单 {playlist_id} API返回错误: {data.get('code')}")
return None
except Exception as e:
print(f"获取歌单 {playlist_id} 详情失败: {e}")
return None
将采集到的数据持久化存储是必不可少的一步。我推荐使用 SQLite(用于快速原型)或 PostgreSQL(用于生产环境) 而非简单的CSV文件,因为关系型数据库能更好地处理后续的关联查询和复杂分析。
import sqlite3
def init_database(db_path='music_data.db'):
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# 创建歌单信息表
cursor.execute('''
CREATE TABLE IF NOT EXISTS playlists (
id TEXT PRIMARY KEY,
name TEXT,
creator TEXT,
play_count INTEGER,
subscribed_count INTEGER,
comment_count INTEGER,
share_count INTEGER,
track_count INTEGER,
tags TEXT,
description TEXT,
create_time INTEGER,
update_time INTEGER,
crawl_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
# 还可以创建歌曲表、评论表等
conn.commit()
conn.close()
数据采集阶段,最忌讳的就是“一把梭”。制定清晰的采集目标、设计合理的数据模型、加入足够的错误处理和日志记录,才能为后续分析打下坚实基础。我曾在初期因为没处理好网络异常,导致一晚上爬取任务中断数次,浪费了大量时间。所以,请务必让你的爬虫“健壮”起来。
2. 数据清洗与预处理:从原始数据到分析就绪
爬虫抓取回来的数据,我们称之为“原始数据”(Raw Data)。它通常是杂乱、不完整甚至包含错误的。直接用它进行分析,就像用沾满泥土的食材做菜,结果可想而知。数据清洗(Data Cleaning)和预处理(Preprocessing)的目的,就是将这些“食材”洗净、切配,变成“分析就绪”(Analysis-Ready)的状态。
针对网易云歌单数据,我们通常会遇到以下几类问题:
- 缺失值处理:某些歌单可能没有标签、没有简介,或者收藏量、评论数等字段因为采集失败而为空。
- 格式不一致:播放量“1.2亿”需要转换为整数120000000;时间戳可能是毫秒或秒为单位。
- 异常值检测:是否存在播放量高得离谱(如“9999亿”)的无效数据?是否有重复的歌单记录?
- 文本清洗:歌单简介和评论中可能包含无关字符、表情符号、HTML标签等。
让我们用Pandas来一步步解决这些问题。首先,将数据加载到DataFrame中。
import pandas as pd
import numpy as np
# 假设我们已经将爬取的数据存入了CSV文件
df_raw = pd.read_csv('raw_playlists.csv')
print(f"原始数据形状: {df_raw.shape}")
print(df_raw.info())
print(df_raw.head())
第一步:处理缺失值。 我们需要根据字段的业务含义,决定是填充、插值还是删除。
# 检查缺失值情况
missing_summary = df_raw.isnull().sum()
print("缺失值统计:\n", missing_summary[missing_summary > 0])
# 处理方案
# 1. 数值型字段(播放量、收藏量等):用中位数或0填充。用0填充需谨慎,可能扭曲分布。
df_raw['play_count'] = df_raw['play_count'].fillna(0).astype(int)
df_raw['subscribed_count'] = df_raw['subscribed_count'].fillna(0).astype(int)
# 对于播放量,如果缺失,也可以考虑用同类歌单的中位数填充,这更合理。
# median_play = df_raw['play_count'].median()
# df_raw['play_count'] = df_raw['play_count'].fillna(median_play).astype(int)
# 2. 文本型字段(标签、简介):用空字符串填充
df_raw['tags'] = df_raw['tags'].fillna('')
df_raw['description'] = df_raw['description'].fillna('')
# 3. 关键标识字段(如歌单ID、创建者)缺失,整行删除
df_clean = df_raw.dropna(subset=['id', 'creator'])
print(f"清洗后数据形状: {df_clean.shape}")
第二步:统一数据格式与类型转换。 这是最繁琐但也最关键的一步。
def parse_count(value):
"""
将‘1.2万’、‘3.5亿’这样的字符串转换为整数。
"""
if pd.isna(value) or value == '':
return 0
if isinstance(value, (int, float, np.integer)):
return int(value)
value = str(value).strip()
if '亿' in value:
return int(float(value.replace('亿', '')) * 100000000)
elif '万' in value:
return int(float(value.replace('万', '')) * 10000)
else:
try:
return int(value)
except:
return 0
# 应用转换函数
df_clean['play_count'] = df_clean['play_count'].apply(parse_count)
df_clean['subscribed_count'] = df_clean['subscribed_count'].apply(parse_count)
df_clean['comment_count'] = df_clean['comment_count'].apply(parse_count)
# 时间戳转换(假设是毫秒)
df_clean['create_time'] = pd.to_datetime(df_clean['create_time'], unit='ms', errors='coerce')
df_clean['update_time'] = pd.to_datetime(df_clean['update_time'], unit='ms', errors='coerce')
第三步:检测与处理异常值。 我们可以通过描述性统计和可视化(如箱线图)来发现异常。
# 查看数值字段的统计描述
print(df_clean[['play_count', 'subscribed_count', 'comment_count']].describe())
# 定义一个基于IQR(四分位距)的异常值检测函数
def detect_outliers_iqr(series):
Q1 = series.quantile(0.25)
Q3 = series.quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
return (series < lower_bound) | (series > upper_bound)
# 检测播放量异常值
play_outliers = detect_outliers_iqr(df_clean['play_count'])
print(f"播放量异常值数量: {play_outliers.sum()}")
# 查看异常值
print(df_clean[play_outliers][['id', 'name', 'play_count']].head())
# 处理异常值:对于明显不合理的极高值(如超过平台理论极限),可以视为脏数据删除或截断。
# 例如,假设我们认为播放量超过100亿为异常
df_clean = df_clean[df_clean['play_count'] <= 10_000_000_000]
第四步:文本字段的深度清洗。 对于标签和简介,我们需要做分词、去停用词等处理,为后续的文本分析做准备。
import jieba
import re
def clean_text(text):
"""清洗简介文本"""
if not text:
return ''
# 去除HTML标签、URL、多余空白
text = re.sub(r'<[^>]+>', '', text)
text = re.sub(r'http\S+', '', text)
text = re.sub(r'\s+', ' ', text).strip()
return text
def split_tags(tag_string):
"""将标签字符串分割为列表,并去重"""
if not tag_string:
return []
# 假设标签以逗号、空格或中文顿号分隔
tags = re.split(r'[,,\s]+', tag_string)
# 去除空字符串和重复项
tags = [tag.strip() for tag in tags if tag.strip()]
return list(set(tags))
df_clean['description_clean'] = df_clean['description'].apply(clean_text)
df_clean['tags_list'] = df_clean['tags'].apply(split_tags)
# 为后续分析,可以创建一个“标签展开”的DataFrame
tags_exploded = df_clean.explode('tags_list')
tags_exploded = tags_exploded[tags_exploded['tags_list'] != '']
经过以上步骤,我们得到了一份干净、规整的数据集 df_clean。这个过程可能占用了整个项目60%以上的时间,但它的价值是决定性的。一份高质量的数据,能让后续的分析事半功倍,结论也更具说服力。记住,垃圾进,垃圾出(Garbage In, Garbage Out),在数据科学领域是铁律。
3. 核心洞察分析:音乐偏好、创作者与平台生态
数据准备就绪后,真正的探索开始了。我们将从三个核心维度切入:用户偏好(听什么)、创作者生态(谁在创作)、以及歌单本身的特征(什么样式的歌单受欢迎)。这些分析将帮助我们回答最初的问题。
3.1 解码用户音乐偏好:标签与热度的秘密
歌单标签是理解音乐风格的直接入口。我们可以通过分析标签的分布、共现关系以及与热度指标(播放、收藏、评论)的关联,来描绘一幅生动的“音乐口味地图”。
首先,让我们看看哪些音乐风格最受平台用户欢迎。
import matplotlib.pyplot as plt
import seaborn as sns
plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS'] # 解决中文显示问题
plt.rcParams['axes.unicode_minus'] = False
# 统计标签频率
tag_counts = tags_exploded['tags_list'].value_counts().head(20)
# 绘制水平条形图
plt.figure(figsize=(12, 8))
bars = plt.barh(tag_counts.index[::-1], tag_counts.values[::-1], color=sns.color_palette("husl", 20))
plt.xlabel('歌单数量')
plt.title('网易云音乐热门歌单标签TOP20')
# 在条形末端添加数据标签
for i, (value, bar) in enumerate(zip(tag_counts.values[::-1], bars)):
plt.text(value + 5, bar.get_y() + bar.get_height()/2, f'{value}', va='center')
plt.tight_layout()
plt.show()
仅仅知道“流行”标签最多还不够。我们需要知道哪些标签组合(即歌单的标签集合)更容易打造出爆款歌单。这可以通过计算标签与热度指标的相关系数,或者构建“标签-热度”矩阵来实现。
| 标签 | 平均播放量(万) | 平均收藏量 | 平均评论数 | 出现频次 |
|---|---|---|---|---|
| 欧美 | 1250 | 4.5万 | 3200 | 高频 |
| 纯音乐 | 850 | 3.2万 | 1800 | 中频 |
| 学习 | 2200 | 8.1万 | 5500 | 中频 |
| 摇滚 | 980 | 3.8万 | 2500 | 中频 |
| ACG | 1500 | 5.5万 | 4200 | 中频 |
| 治愈 | 760 | 2.9万 | 1400 | 高频 |
提示:上表为模拟数据,用于展示分析思路。实际分析中,“学习”、“工作”等场景化标签的歌单,其平均互动数据(收藏、评论)往往显著高于纯风格标签,这说明功能性歌单(如助眠、专注、运动)的用户粘性和社交属性更强。
另一个有趣的角度是分析歌单简介的文本情感和主题。利用简单的文本分析技术,我们可以从海量简介中提炼出用户创建歌单的动机和情感倾向。
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.decomposition import LatentDirichletAllocation
# 使用TF-IDF提取简介文本的关键词
vectorizer = TfidfVectorizer(max_features=1000, stop_words=['的', '了', '在', '是', '我', '有', '和', '就', '都', '而', '及', '与', '等'])
tfidf_matrix = vectorizer.fit_transform(df_clean['description_clean'].fillna(''))
# 使用LDA模型进行主题聚类(假设我们想找出5个主要主题)
lda = LatentDirichletAllocation(n_components=5, random_state=42)
lda.fit(tfidf_matrix)
# 查看每个主题下的关键词
feature_names = vectorizer.get_feature_names_out()
for topic_idx, topic in enumerate(lda.components_):
print(f"\n主题 #{topic_idx + 1}:")
top_words_idx = topic.argsort()[:-10 - 1:-1] # 取权重最高的10个词
top_words = [feature_names[i] for i in top_words_idx]
print(", ".join(top_words))
运行上述代码,你可能会得到类似“夜晚、安静、睡眠”、“回忆、青春、经典”、“节奏、运动、能量”这样的主题词群。这比单纯看标签更能深入理解歌单的情感内核和使用场景。
3.2 剖析创作者生态:谁是真正的“歌单之王”?
歌单的创建者(UP主)是平台内容生态的基石。分析他们,能让我们了解优质内容的来源。我们可以从几个维度对UP主进行“画像”:
- 生产力:创建的歌单总数。
- 影响力:所创歌单的总播放量、平均播放量、总收藏量。
- 专业性/垂直度:歌单标签的集中度(是专注于某一风格,还是涉猎广泛)。
- 互动能力:所创歌单的平均评论数。
让我们计算一下UP主的影响力排行榜。这里我们定义一个简单的“影响力指数”,综合播放、收藏和评论。
# 按创建者分组聚合
creator_stats = df_clean.groupby('creator').agg(
playlist_count=('id', 'count'),
total_play=('play_count', 'sum'),
avg_play=('play_count', 'mean'),
total_subscribed=('subscribed_count', 'sum'),
avg_comment=('comment_count', 'mean')
).reset_index()
# 计算一个综合影响力分数(需归一化处理)
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
# 选择要加权的指标
metrics_to_scale = ['total_play', 'total_subscribed', 'avg_comment']
creator_stats[['scaled_'+m for m in metrics_to_scale]] = scaler.fit_transform(creator_stats[metrics_to_scale])
# 假设权重:总播放量0.5,总收藏量0.3,平均评论0.2
creator_stats['influence_score'] = (creator_stats['scaled_total_play'] * 0.5 +
creator_stats['scaled_total_subscribed'] * 0.3 +
creator_stats['scaled_avg_comment'] * 0.2)
# 按影响力分数排序
top_creators = creator_stats.sort_values('influence_score', ascending=False).head(10)
print(top_creators[['creator', 'playlist_count', 'total_play', 'influence_score']])
分析结果可能会让你惊讶:高产的UP主不一定影响力最大。有些用户只创建了少数几个歌单,但每个都是精品,播放量惊人。而有些UP主虽然创建了大量歌单,但平均质量平平。这启示我们,在运营或合作时,应更关注UP主的“爆款率”和“平均质量”,而非单纯的数量。
进一步,我们可以对UP主进行聚类分析,看看平台上存在哪些不同类型的创作者。例如,是否存在“爆款专业户”、“垂直领域深耕者”、“情怀收藏家”、“热门搬运工”等不同类型?这可以通过对UP主的各项指标进行聚类算法(如K-Means)来实现。
3.3 歌单成功要素分析:什么样的歌单能火?
这是所有分析中最具商业价值的部分。我们试图建立一个简单的模型,来预测或解释一个歌单的成功(以播放量或收藏量为衡量标准)。我们可以将歌单的特征作为自变量(X),热度指标作为因变量(y),进行探索性分析。
潜在的特征包括:
- 基础特征:歌曲数量、标签数量、简介文本长度、是否存在封面图。
- 内容特征:主要音乐风格(通过标签推断)、情感倾向(通过简介文本分析得出)。
- 创作者特征:UP主的历史影响力分数、专注度。
- 时间特征:创建时长、最近更新时间。
我们可以先进行相关性分析,看看哪些特征与热度指标关系最密切。
# 构建特征DataFrame
df_features = df_clean.copy()
df_features['tag_count'] = df_features['tags_list'].apply(len)
df_features['desc_length'] = df_features['description_clean'].apply(len)
df_features['existence_days'] = (pd.Timestamp.now() - df_features['create_time']).dt.days
# 合并UP主影响力分数
df_features = df_features.merge(creator_stats[['creator', 'influence_score']], on='creator', how='left')
df_features.rename(columns={'influence_score': 'creator_influence'}, inplace=True)
# 选择数值型特征计算相关性
numeric_features = ['track_count', 'tag_count', 'desc_length', 'existence_days', 'creator_influence']
target = 'play_count' # 或 'subscribed_count'
correlation_matrix = df_features[numeric_features + [target]].corr()
print(f"特征与'{target}'的相关性:\n", correlation_matrix[target].sort_values(ascending=False))
在我的某次分析中,发现 creator_influence(UP主影响力)和 existence_days(存在天数)与播放量的正相关性最强,而 tag_count(标签数量)呈现微弱的负相关(标签过多可能意味着定位不清晰)。desc_length(简介长度)则呈现倒U型关系,适中的简介最好。
当然,这只是线性相关的初步观察。更深入的分析可以尝试使用决策树或随机森林等模型,它们不仅能给出特征重要性排序,还能捕捉特征间的非线性交互作用。
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split
# 准备数据
X = df_features[numeric_features].fillna(0)
y = df_features[target]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 训练一个简单的随机森林模型
rf_model = RandomForestRegressor(n_estimators=100, random_state=42)
rf_model.fit(X_train, y_train)
# 查看特征重要性
feature_importance = pd.DataFrame({
'feature': numeric_features,
'importance': rf_model.feature_importances_
}).sort_values('importance', ascending=False)
print("随机森林特征重要性:\n", feature_importance)
通过这样的分析,我们或许能总结出一些“爆款歌单公式”的雏形,例如:“由高影响力UP主创建,聚焦1-3个核心标签,拥有简洁有力(约50-150字)的场景化简介,并持续更新维护的歌单,更容易获得成功”。这些洞察对于平台运营、UP主内容创作乃至音乐人的推广,都具有参考价值。
4. 高级可视化与故事讲述:让数据自己说话
数据分析的最终目的是为了交流和决策。再深刻的洞察,如果无法清晰有效地传达,价值也会大打折扣。在这一部分,我们将超越基础的柱状图和饼图,使用更高级的可视化技术来讲述数据故事,并分享一些让图表既专业又美观的实用技巧。
技巧一:使用Seaborn增强图表表现力。 Matplotlib是基础,但Seaborn在统计图表和美化上更胜一筹。
import seaborn as sns
import matplotlib.pyplot as plt
# 示例:绘制播放量与收藏量的联合分布与回归线
plt.figure(figsize=(10, 6))
# 取对数处理,使分布更集中,便于观察
df_plot = df_clean[(df_clean['play_count']>0) & (df_clean['subscribed_count']>0)].copy()
df_plot['log_play'] = np.log10(df_plot['play_count'] + 1)
df_plot['log_sub'] = np.log10(df_plot['subscribed_count'] + 1)
scatter = sns.jointplot(data=df_plot, x='log_play', y='log_sub',
kind='reg', # 同时显示散点和回归线
height=8,
ratio=4,
marginal_kws={'bins': 30, 'kde': True},
joint_kws={'scatter_kws': {'s': 10, 'alpha': 0.5},
'line_kws': {'color': 'red'}})
scatter.ax_joint.set_xlabel('播放量 (log10)')
scatter.ax_joint.set_ylabel('收藏量 (log10)')
scatter.ax_joint.set_title('歌单播放量与收藏量关系散点图(含回归线)')
plt.tight_layout()
plt.show()
这张图可以直观地展示播放量和收藏量之间强烈的正相关关系,同时边缘的直方图展示了两个变量的分布情况。
技巧二:用热力图揭示标签共现关系。 哪些标签经常一起出现?这能帮助我们理解音乐风格的组合趋势。
from itertools import combinations
from collections import Counter
# 生成标签共现矩阵
cooccurrence = Counter()
for tags in df_clean['tags_list']:
# 对每个歌单的标签列表,生成所有两两组合
for tag1, tag2 in combinations(sorted(set(tags)), 2):
cooccurrence[(tag1, tag2)] += 1
# 转换为DataFrame,便于绘制热力图
top_tags = tag_counts.head(15).index.tolist() # 取前15个热门标签
cooccurrence_matrix = pd.DataFrame(0, index=top_tags, columns=top_tags)
for (tag1, tag2), count in cooccurrence.items():
if tag1 in top_tags and tag2 in top_tags:
cooccurrence_matrix.loc[tag1, tag2] = count
cooccurrence_matrix.loc[tag2, tag1] = count # 矩阵是对称的
# 绘制热力图
plt.figure(figsize=(12, 10))
sns.heatmap(cooccurrence_matrix, annot=True, fmt='d', cmap='YlOrRd',
square=True, linewidths=.5, cbar_kws={"shrink": .8})
plt.title('热门歌单标签共现关系热力图')
plt.tight_layout()
plt.show()
从热力图中,你可能会发现“学习”和“纯音乐”高度共现,“运动”和“摇滚”、“电子”关联紧密。这为创建组合标签歌单提供了数据支持。
技巧三:用时间序列图追踪趋势。 音乐潮流是变化的。我们可以分析不同时间段创建的歌单,其主流标签或平均热度有何变化。
# 按歌单创建年份-月份分组,分析标签趋势
df_clean['create_ym'] = df_clean['create_time'].dt.to_period('M')
# 计算每个月最热门的标签
trend_data = []
for period, group in df_clean.groupby('create_ym'):
# 展开该月所有歌单的标签
monthly_tags = group.explode('tags_list')
top_tag = monthly_tags['tags_list'].value_counts().index[0] if not monthly_tags.empty else 'N/A'
avg_play = group['play_count'].mean()
trend_data.append({'period': period, 'top_tag': top_tag, 'avg_play': avg_play})
df_trend = pd.DataFrame(trend_data).sort_values('period')
# 选取最近24个月的数据绘图
df_trend_last2y = df_trend[df_trend['period'] >= df_trend['period'].max() - 24]
fig, ax1 = plt.subplots(figsize=(14, 7))
# 绘制平均播放量折线
color = 'tab:blue'
ax1.set_xlabel('创建时间 (年-月)')
ax1.set_ylabel('平均播放量', color=color)
line1 = ax1.plot(df_trend_last2y['period'].astype(str), df_trend_last2y['avg_play'], color=color, marker='o', label='平均播放量')
ax1.tick_params(axis='y', labelcolor=color)
ax1.set_xticklabels(df_trend_last2y['period'].astype(str), rotation=45)
# 创建第二个y轴,用于标注月度最热标签
ax2 = ax1.twinx()
color = 'tab:red'
ax2.set_ylabel('月度最热标签', color=color)
# 在折线图对应位置标注标签名
for i, (period, tag) in enumerate(zip(df_trend_last2y['period'], df_trend_last2y['top_tag'])):
ax2.text(i, df_trend_last2y['avg_play'].iloc[i] * 1.02, tag,
ha='center', va='bottom', fontsize=9, rotation=30, color=color)
ax2.tick_params(axis='y', labelcolor=color)
fig.tight_layout()
plt.title('近两年歌单月度趋势:平均播放量与最热标签')
plt.show()
这样的图表能生动地展示音乐风潮的起落,比如“City Pop”、“Lo-fi”等标签可能在特定时间段突然崛起。
技巧四:构建交互式仪表盘。 对于需要深度探索或汇报的场景,静态图表可能不够用。使用 Plotly Dash 或 Streamlit 可以快速构建一个交互式数据分析仪表盘。
# 这是一个Streamlit应用的极简示例框架
import streamlit as st
import pandas as pd
import plotly.express as px
st.set_page_config(page_title="网易云歌单分析仪表盘", layout="wide")
st.title("🎵 网易云热门歌单数据分析仪表盘")
# 加载数据
df = pd.read_csv('cleaned_playlists.csv')
# 侧边栏过滤器
selected_tag = st.sidebar.selectbox('选择音乐标签', options=['全部'] + df['tags_list'].explode().unique().tolist())
min_play = st.sidebar.slider('最小播放量(万)', 0, int(df['play_count'].max()/10000), 0)
# 根据筛选条件过滤数据
if selected_tag != '全部':
df_filtered = df[df['tags_list'].apply(lambda x: selected_tag in x)]
else:
df_filtered = df
df_filtered = df_filtered[df_filtered['play_count'] >= min_play * 10000]
# 主显示区
col1, col2 = st.columns(2)
with col1:
# 绘制播放量分布直方图
fig1 = px.histogram(df_filtered, x='play_count', nbins=50, title='播放量分布')
st.plotly_chart(fig1, use_container_width=True)
with col2:
# 绘制标签云或条形图
tag_counts = df_filtered.explode('tags_list')['tags_list'].value_counts().head(15)
fig2 = px.bar(x=tag_counts.values, y=tag_counts.index, orientation='h', title='热门标签TOP15')
st.plotly_chart(fig2, use_container_width=True)
# 显示数据表格
st.subheader("歌单数据详情")
st.dataframe(df_filtered[['name', 'creator', 'play_count', 'subscribed_count', 'tags']].head(20))
将这个脚本保存为 app.py,在终端运行 streamlit run app.py,一个包含筛选、图表交互和数据预览的网页应用就启动了。这能让你的分析结果瞬间变得生动和可操作。
可视化的精髓在于用最合适的图表讲最清晰的故事。避免为了炫技而使用复杂的图表,始终记住你的受众和想要传达的核心信息。颜色搭配、字体选择、图表注释等细节,共同决定了最终呈现的专业程度。在我自己的项目中,我通常会准备两套可视化方案:一套用于快速探索和调试(简单直接),另一套用于最终报告和展示(精美、信息完整、有故事线)。
更多推荐



所有评论(0)