
简介这是一份面向Python数据分析初学者与高校学生的实战项目源码包以网易云音乐歌单为分析对象完整演示从数据获取到可视化呈现的全流程适合用作期末大作业、课程设计或数据分析入门练手。项目综合运用requests、bs4爬取歌单数据借助numpy、pandas完成清洗与统计再通过matplotlib、squarify绘制评论、收藏、播放、贡献及分布数量图并用jieba与wordcloud生成词云最终给出歌单优化建议。压缩包共38个文件约12.23MB包含12个py源码、11个pyc编译文件、7张png结果图、3份csv数据、1个ttf字体、1份pdf与1份docx说明文档及md说明结构清晰便于对照复现。目前已有786人学习下载读者可据此掌握爬虫、数据清洗、可视化与词云生成的完整链路并参考文档说明快速理解项目组织与实现思路。1. 从一份歌单里能挖出什么网易云音乐数据可视化项目的真实边界很多人第一次做数据分析大作业选题就卡住了手头没有企业级数据仓库也拿不到商业数据接口最后往往随便找个 CSV 凑数。网易云音乐歌单其实是个被低估的切入点——它天然带有歌曲、歌手、风格标签、播放量、收藏时间这些结构化字段又足够贴近生活答辩时讲得清楚。这个项目的核心不是爬多少歌而是把一份歌单数据从原始 JSON 一路清洗成能讲故事的可视化图表风格分布、歌手占比、年份趋势、歌单热度与曲目数量的关系。适合谁适合刚学完 pandas 和 matplotlib、想找一个完整闭环练手的同学也适合需要交期末大作业但不想做烂大街电影票房分析的人。它不涉及复杂模型重点在数据清洗和图表表达这正是数据分析入门最该练的基本功。2. 数据从哪来、长什么样歌单字段拆解与采集思路2.1 歌单数据的字段结构网易云音乐的歌单页面在浏览器里看到的是一张张卡片但底层是一段结构化数据。一个歌单通常包含这些字段歌单 ID、歌单名称、创建者、创建时间、收藏量、播放量、标签如华语流行深夜、曲目列表。每首曲目又有歌曲 ID、歌名、歌手、专辑、时长。做可视化之前先要明确你打算分析哪几个维度因为不同维度需要的字段不一样。我一般会先画一张字段映射表把想分析的指标和需要的原始字段对应起来避免爬到一半发现缺字段又得重来。下面这张表是我做这个项目时用的字段规划分析目标需要的原始字段数据类型风格分布歌单标签 tags字符串列表歌手占比曲目 artist字符串年份趋势曲目专辑发行时间时间戳热度关系收藏量、曲目数量整数时长分布曲目 duration毫秒整数这张表的价值在于它决定了你后面清洗脚本要保留哪些列、丢弃哪些列。很多同学上来就全字段抓取结果数据里一半是没用的封面 URL 和评论 ID清洗时反而增加负担。2.2 采集方式与合规边界采集歌单数据有两条路一是手动导出二是写脚本请求公开页面。手动导出适合数据量小、只做演示的场景把歌单页面另存为 HTML 再用解析库提取或者直接复制曲目列表粘贴进 Excel。脚本方式适合需要几百上千条歌单做统计的场景但要注意频率控制别把请求打得像 CC 攻击一样密集那既不道德也容易被封。我一般用 requests 配合合理的间隔来抓取公开的歌单页面核心是解析页面里内嵌的 JSON 数据块。下面是一个最小可运行的采集片段只抓单个歌单的曲目列表import requests import json import time # 请求头模拟正常浏览器访问避免被识别为异常流量 headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36, Referer: https://music.163.com/ } def fetch_playlist(playlist_id): 抓取单个歌单页面的曲目数据 url fhttps://music.163.com/playlist?id{playlist_id} resp requests.get(url, headersheaders, timeout10) resp.raise_for_status() # 页面里曲目数据藏在 ul classf-hide 的 li 标签中 from bs4 import BeautifulSoup soup BeautifulSoup(resp.text, html.parser) tracks [] for li in soup.select(ul.f-hide li): a li.find(a) tracks.append({ song_id: li[id] if li.has_attr(id) else None, name: a.text.strip(), link: a[href] }) return tracks if __name__ __main__: data fetch_playlist(你的歌单ID) print(f抓到 {len(data)} 首曲目) time.sleep(2) # 每次请求间隔 2 秒控制频率这段代码的逻辑很直白请求歌单页面用 BeautifulSoup 定位到隐藏的曲目列表逐条提取歌名和链接。参数上timeout10防止请求卡死time.sleep(2)是频率控制的关键别小看这两秒它决定了你的脚本是温和采集还是暴力扫描。headers里的 Referer 不能省否则部分页面会返回空数据。提示采集频率建议不低于 1.5 秒一次单次任务别超过几百个歌单。数据只用于本地分析不要二次分发。3. 把脏数据洗成能画图的表pandas 清洗实战3.1 原始数据的四类脏法从页面抓下来的数据直接丢进 DataFrame 会发现根本没法用。常见的脏数据有四类一是时长字段是毫秒整数画图时显示成 240000 这种没法读的数字二是歌手字段里多个歌手用斜杠或逗号混用统计时会把周杰伦/方文山当成一个歌手三是标签字段是列表字符串需要拆开四是存在重复曲目和空值。清洗的目标就一句话让每一列都是可直接聚合的干净类型。我一般按先统一格式、再拆分字段、最后去重补空的顺序来。下面这段清洗脚本覆盖了这四类问题import pandas as pd import numpy as np # 假设原始数据已经存成 playlist_raw.csv df pd.read_csv(playlist_raw.csv) # 1. 时长毫秒转分钟保留一位小数 df[duration_min] (df[duration] / 1000 / 60).round(1) # 2. 歌手统一分隔符后拆分只取第一位主歌手 df[artist_main] df[artist].str.replace(、, /).str.split(/).str[0].str.strip() # 3. 标签把字符串形式的列表还原成真正的列表 import ast def parse_tags(x): try: return ast.literal_eval(x) if isinstance(x, str) else [] except (ValueError, SyntaxError): return [] df[tags_list] df[tags].apply(parse_tags) # 4. 去重同一歌单内 song_id 重复的只留一条 df df.drop_duplicates(subset[song_id], keepfirst) # 5. 补空播放量缺失的填 0避免聚合时报错 df[play_count] df[play_count].fillna(0).astype(int) print(df[[name, artist_main, duration_min, play_count]].head())逐段说明时长转换用除法加 round是因为可视化时分钟比毫秒直观得多歌手拆分先统一分隔符再取第一位是为了让歌手占比这类统计不被合唱曲目干扰标签解析用ast.literal_eval而不是eval是安全习惯避免执行恶意字符串去重按 song_id 而不是歌名因为同名歌曲很常见播放量补 0 而不是补均值是因为缺失往往意味着无数据而非平均值。3.2 聚合出可视化需要的中间表清洗完的明细表还不能直接画图需要先聚合成几张中间表。比如风格分布要按标签展开计数歌手占比要按主歌手分组统计。这一步是很多人忽略的环节直接拿明细表画图会导致图表混乱。# 风格分布把标签列表炸开再计数 tags_exploded df.explode(tags_list) tag_count tags_exploded[tags_list].value_counts().head(15) # 歌手占比按主歌手统计曲目数 artist_count df[artist_main].value_counts().head(10) # 时长分布分箱统计 df[duration_bin] pd.cut(df[duration_min], bins[0, 2, 3, 4, 5, 6, 100], labels[2min, 2-3min, 3-4min, 4-5min, 5-6min, 6min]) duration_dist df[duration_bin].value_counts().sort_index() print(tag_count) print(artist_count) print(duration_dist)explode是把列表列炸成多行的关键函数一个歌单有多个标签就会变成多行这样 value_counts 才能正确统计每个标签出现次数。pd.cut做分箱bins 的边界要根据你数据的实际时长分布调整我这份数据大部分歌曲在 3 到 5 分钟所以分箱粒度设成 1 分钟。如果数据里有很多超长曲目最后一档要留够空间。注意value_counts()默认按频次降序画柱状图前记得用.sort_index()或.sort_values()明确顺序否则图表顺序会很乱。4. 用 matplotlib 和 pyecharts 把结论画出来4.1 静态图matplotlib 的四个必调参数matplotlib 是入门首选但默认样式很丑中文还会显示成方框。我一般会固定一套配置让每张图都统一风格。下面这段代码画一张风格分布横向柱状图包含中文修复和样式调整import matplotlib.pyplot as plt import matplotlib # 中文显示修复Windows 用 SimHeiMac 用 Arial Unicode MS matplotlib.rcParams[font.sans-serif] [SimHei, Arial Unicode MS] matplotlib.rcParams[axes.unicode_minus] False # 负号正常显示 fig, ax plt.subplots(figsize(10, 6)) tag_count.sort_values().plot(kindbarh, axax, color#c20c0c) # 网易云红 ax.set_title(歌单风格标签 Top15, fontsize14) ax.set_xlabel(曲目数量) ax.set_ylabel(标签) for i, v in enumerate(tag_count.sort_values()): ax.text(v 0.5, i, str(v), vacenter, fontsize9) plt.tight_layout() plt.savefig(tag_distribution.png, dpi150) plt.show()四个必调参数分别是font.sans-serif解决中文乱码axes.unicode_minus解决负号变方框figsize控制画布比例dpi决定导出清晰度。ax.text那段是给每根柱子加数值标签答辩时老师一眼就能看到具体数字比光看柱子长度有说服力。颜色用网易云标志性的红色视觉上和主题呼应。4.2 交互图pyecharts 做可下钻的图表如果想让作业更出彩可以用 pyecharts 做交互式图表鼠标悬停能看到具体数值还能导出 HTML 直接演示。下面是一个饼图加词云的组合思路from pyecharts.charts import Pie, WordCloud from pyecharts import options as opts # 歌手占比饼图 pie ( Pie() .add(, [list(z) for z in zip(artist_count.index, artist_count.values)]) .set_global_opts(title_optsopts.TitleOpts(title歌手曲目占比 Top10)) .set_series_opts(label_optsopts.LabelOpts(formatter{b}: {c} ({d}%))) ) pie.render(artist_pie.html) # 标签词云 wc ( WordCloud() .add(, [list(z) for z in zip(tag_count.index, tag_count.values)], word_size_range[15, 60]) .set_global_opts(title_optsopts.TitleOpts(title风格标签词云)) ) wc.render(tag_wordcloud.html)pyecharts 的优势是生成的 HTML 可以独立打开不依赖 Python 环境交作业时直接打包 HTML 文件即可。formatter{b}: {c} ({d}%)是标签格式b 是名称、c 是数值、d 是百分比这个格式在饼图里最实用。词云的word_size_range控制字号范围太小看不出差异太大又会挤在一起15 到 60 是我试过比较平衡的区间。提示pyecharts 生成的 HTML 默认引用在线 CDN 资源如果演示环境没网需要把资源文件一起打包或者改用离线模式。5. 避坑与排查做这个项目最容易翻车的五个地方5.1 中文乱码图表全是方框现象matplotlib 画出来的标题和标签全是小方框完全看不懂。原因默认字体不支持中文系统里也没配置中文字体。解决在代码开头设置rcParams[font.sans-serif]Windows 用 SimHeiMac 用 Arial Unicode MSLinux 需要先确认系统装了中文字体没有的话手动指定字体文件路径。这个坑几乎每个人都会踩一次建议把字体配置写成一个单独的模块所有画图脚本都 import 它。5.2 标签统计翻倍explode 用错位置现象风格分布的总数比实际曲目数多出好几倍。原因一个歌单有多个标签explode 之后每个标签都算一行如果没意识到这点会以为数据出错了。解决这是正常现象标签统计的基数本来就是标签出现次数而非曲目数。如果想让总数对得上可以在图表标题里注明按标签计次或者改用每个歌单只取第一个标签的策略。关键是搞清楚你要统计的到底是曲目还是标签。5.3 时长单位混乱毫秒和秒混着用现象时长分布图里所有歌曲都落在6min这一档。原因原始数据是毫秒忘了除以 1000240000 毫秒被当成 240000 分钟。解决清洗第一步就统一单位并且把转换后的列名改成duration_min这种带单位的名字避免后面又搞混。我一般会在列名里强制带单位后缀_min、_count、_ratio这样看代码时不会记错。5.4 采集被封请求太密集返回空数据现象脚本跑了几十个歌单后返回的曲目列表突然变空。原因请求频率过高触发了服务端的限流。解决加大time.sleep间隔至少 1.5 到 2 秒并且加异常重试逻辑。如果还是被封就降低单次任务量分几天跑。别想着用多线程加速那只会让封得更快。这个坑的血泪经验是慢就是快稳扎稳打比一次跑完更重要。5.5 图表结论站不住样本偏差没说明现象答辩时老师问你这个歌手占比能代表整个平台吗答不上来。原因只抓了几个歌单样本量太小且有选择偏差。解决在报告里明确写清数据来源和样本范围比如本文分析基于 XX 个公开歌单共 XX 首曲目不要用网易云音乐用户都爱听 XX这种过度概括的结论。数据分析的严谨性不在于数据多大而在于你对自己数据边界的诚实。6. 让图表会说话从画出来到讲清楚的进阶技巧做完基础图表后很多人会停在图能显示这一步但真正拉开差距的是让图表自己讲出结论。我分享三个自己常用的进阶技巧。第一个是对比维度叠加。单看风格分布没意思但如果把高收藏歌单和低收藏歌单的风格分布并排画出来就能看出哪些风格更容易被收藏。实现方式很简单给数据加一个is_hot标记列然后分组画两组柱状图# 按收藏量中位数划分热门和冷门歌单 median_fav df[fav_count].median() df[is_hot] df[fav_count] median_fav # 分组统计风格分布 hot_tags df[df[is_hot]].explode(tags_list)[tags_list].value_counts().head(8) cold_tags df[~df[is_hot]].explode(tags_list)[tags_list].value_counts().head(8) # 并排柱状图 import numpy as np labels hot_tags.index.union(cold_tags.index) x np.arange(len(labels)) width 0.35 fig, ax plt.subplots(figsize(12, 6)) ax.bar(x - width/2, [hot_tags.get(l, 0) for l in labels], width, label热门歌单, color#c20c0c) ax.bar(x width/2, [cold_tags.get(l, 0) for l in labels], width, label冷门歌单, color#999999) ax.set_xticks(x) ax.set_xticklabels(labels, rotation45, haright) ax.legend() ax.set_title(热门与冷门歌单的风格分布对比) plt.tight_layout() plt.savefig(hot_vs_cold.png, dpi150)这段代码的关键是union取两组标签的并集保证两个柱状图的横轴对齐get(l, 0)处理某个标签只在一组出现的情况。对比图比单组图有信息量得多答辩时也更容易讲出热门歌单更偏向 XX 风格这样的结论。第二个是时间维度挖掘。如果数据里有专辑发行时间可以画一条年份趋势线看看歌单里的歌是集中在某几年还是均匀分布。这个分析能引出怀旧歌单和新歌歌单的区分是个不错的加分点。第三个是图表叙事顺序。别把十几张图随便堆在报告里按整体概览 → 风格分布 → 歌手集中度 → 时长特征 → 热度关系的顺序排列每张图配一句话结论。读者跟着你的顺序走自然就接受了你的分析逻辑。最后说个我自己的习惯每次做完图表我会把图发给一个不懂数据分析的朋友看如果他能一眼说出这张图在讲什么说明图合格了如果他要我解释半天那就是图没画好回去改。数据可视化的终点不是代码跑通而是别人看懂。希望帮到你。本文还有配套的精品资源点击获取