
简介一份Python数据分析初探项目基于数据可视化技术实现网易云音乐歌单分析适合高校学生完成期末大作业也适合数据分析初学者作为课程设计参考。项目使用requests与bs4获取歌单数据经pandas清洗后利用matplotlib和squarify绘制评论、收藏、播放、贡献分布图并结合jieba分词与wordcloud生成词云最后从数据角度提出歌单优化建议完整覆盖数据采集、清洗、分析、可视化呈现全流程便于学习者逐步复现和验证。压缩包共38个文件以12个py源码为核心辅以11个pyc编译文件、7个png可视化成图、3个csv数据集另含pdf、docx、md格式的项目说明、Markdown笔记和ttf字体文件整体约12.23MB目录层级清楚配合文档可快速掌握项目脉络。已有785人学习下载既能巩固Python语法和常用第三方库协作方式也为后续数据处理项目提供了可复用模板对完成期末设计或入门数据分析均有参考价值。1. 网易云歌单分析系统期末高分项目的真实落点判断一张网易云歌单有没有潜力播放量不是最准的指标收藏比播放更说明问题。这套基于 Python 数据可视化的网易云音乐歌单分析系统抓取歌单真实数据后用 pandas 和 matplotlib 把评论数、收藏数、播放量分布图和词云逐一画出来再从图表里反推优化方向。对要交 Python 数据分析期末大作业的人来说这套源码加文档的最大价值是把“爬虫→清洗→可视化→结论”整条链路完整跑通每一步都有真实代码对应不是那种只有 PPT 和截图的水作业。它适合三类人第一次做数据分析项目的在校生、想快速上手 requestspandas 全流程的初学者、需要高完成度参考项目的课程设计选手。文档包里附带的手册.docx 和结课说明 PDF 可以直接对照着看代码怎么组织省去自己猜结构的时间。2. 数据从哪里来requestsbs4 爬取网易云歌单的完整流程2.1 为什么选 requestsbs4 而不是 Selenium第一次拿到这个题目的人多半会想用 Selenium 去无头浏览器模拟点击把页面渲染出来再抓。我的观点是期末大作业别这么干Selenium 慢、吃内存而且一旦页面改版一堆 XPath 全得重写。网易云歌单广场本身有服务端渲染的页面用 requests 直接拿 HTML 再用 BeautifulSoup 解析速度和稳定性都好很多。这个项目里用的也是这套方案requests 负责请求bs4 负责解析。当然requests 方案的前提是页面关键数据不是异步加载的歌单广场目前符合这个条件这也是 Python 爬虫入门阶段性价比最高的方案。2.2 初始化请求session、headers 与翻页参数爬虫的第一步是把请求伪装成正常浏览器否则网易云会返回空页面或者验证码页。这里有个容易被忽略的细节requests.get 拿到歌单首页时最好把响应保存到 session 里后面的请求统一用同一个 session保持 Cookie 连续。import requests import time import random session requests.Session() # 第一次访问主页目的是让 session 拿到基础 Cookie session.get(https://music.163.com/, timeout10) 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_page(offset0): params { order: hot, # 按热度排序 cat: 全部, # 歌单分类 limit: 36, # 每页数量最大 36 offset: offset # 偏移量第 2 页就是 36 } resp session.get( https://music.163.com/discover/playlist, paramsparams, headersheaders, timeout15 ) resp.encoding utf-8 print(状态码:, resp.status_code, | 偏移量:, offset) return resp.text这里的三个参数在调试时最常调整limit 超过 36 会被服务端忽略offset 用来翻页cat 可以换成“华语”“纯音乐”等具体分类去抓不同领域。Referer 头一定要带这是网易云反爬校验的一个常规检查项。打印状态码是为了第一时间发现问题比如 418 或 403 都说明头部信息没伪装到位。2.3 页面解析把 HTML 里的歌单字段提取出来拿到 HTML 之后用 BeautifulSoup 的选择器去定位歌单卡片。每一张歌单卡片里包含标题、播放量、贡献者和歌单 id。选择器在网易云不同版本里会有变化所以写代码前先在浏览器开发者工具里按 F12 确认一次实际 class 名花不了两分钟却能省掉一整晚的调试。from bs4 import BeautifulSoup def parse_playlist(html): soup BeautifulSoup(html, html.parser) rows soup.select(div.u-mhplay) records [] for row in rows: title_node row.select_one(a.title) play_node row.select_one(span.nb) if not title_node or not play_node: continue # 歌单详情页链接形如 /playlist?idxxxid 用来后续去重 detail_link title_node.get(href, ) playlist_id detail_link.split(id)[-1] if id in detail_link else records.append({ title: title_node.get(title) or title_node.get_text().strip(), play_count: play_node.get_text().strip(), playlist_id: playlist_id, }) # 单条歌单解析完成后稍作停顿拉长请求间隔 time.sleep(random.uniform(0.3, 0.8)) return recordstitle_node.get(title) 取的是悬停提示里的完整歌单名比 get_text() 更可靠因为歌单名太长时页面显示是被截断的。播放量字段拿到的是格式化文本可能是“12.3万”也可能是“87654”这个不一致问题会带到数据清洗阶段集中处理。解析每条之后停顿 0.3 到 0.8 秒是避免短时间内请求太密集把自己 IP 送进风控名单的土办法简单但有效。如果后续想抓评论数和收藏数在歌单详情页里同样能找到对应节点字段结构不同但思路一致。2.4 落盘 CSV为 pandas 清洗做准备解析结果最好先落到 CSV 再交给 pandas而不是直接传给下一个函数。原因有两个一是爬虫中途断掉时CSV 里已经有的数据不会丢二是数据清洗时反复读文件比反复跑网络请求快得多。这也是我处理所有爬虫项目的一致习惯。import pandas as pd all_records [] for offset in range(0, 108, 36): # 抓 3 页共 108 张歌单 html fetch_playlist_page(offset) records parse_playlist(html) all_records.extend(records) df pd.DataFrame(all_records) df.to_csv(netease_playlist.csv, indexFalse, encodingutf-8-sig) print(抓取完成共, len(df), 条记录)保存时用 utf-8-sig 而不是 utf-8是给 Windows 用户的老经验Excel 直接打开无 BOM 的 UTF-8 CSV 会把中文全部显示成乱码加 BOM 之后双击就能正常看。到这里数据分析的第一步数据采集就算完成了接下来进入清洗阶段。3. 数据清洗与预处理pandas 把脏数据变成可分析的表格3.1 先摸清字段现状info() 和 head() 的必要性爬下来的数据不能直接画图因为歌单页面的字段格式是为“人看”设计的不是为“统计”设计的。先用 pandas 把这些记录读进来看一遍字段类型和缺失情况再决定怎么处理。import pandas as pd import numpy as np df pd.read_csv(netease_playlist.csv, encodingutf-8-sig) print(df.info()) print(df.head(10)) print(去重前条数:, len(df))df.info() 可以看到每一列的非空数量列里如果有 object 类型说明是文本int 或 float 才是数值。这一步能快速发现两类问题play_count 列其实是字符串因为里面有“万”playlist_id 列可能有空值或重复值这直接决定后面用什么字段做去重依据。3.2 数量字段规范化把“万”“亿”换算成统一数值播放量字段常见的格式有“87654”和“12.3万”两种混着出现如果直接画图matplotlib 会把“12.3万”当成字符串处理直方图直接报错。清洗逻辑很简单把单位换算成纯数字即可。评论数、收藏数字段如果也带单位后缀同样复用这个函数。def clean_count(value): if pd.isna(value): return np.nan value str(value).replace(,, ).strip() if 亿 in value: return float(value.replace(亿, )) * 100000000 if 万 in value: return float(value.replace(万, )) * 10000 try: return float(value) except ValueError: return np.nan df[play_count_num] df[play_count].apply(clean_count)这里先判断缺失值再格式化避免把 NaN 字符串化。replace(,, ) 是为了兼容“12,345”这种千分位写法。单位判断要从“亿”开始因为“1.2亿”里也包含“万”字先处理亿再处理万才不会算错十倍。后面如果抓了评论数和收藏数对对应列执行 df[评论列].apply(clean_count) 即可。3.3 去重与缺失值处理以歌单 id 为准同一份歌单可能在推荐位和排行榜里重复出现需要去掉。最稳的去重字段是 playlist_id而不是标题因为同名歌单可能指向不同内容。去重之后再看关键字段的缺失情况标题为空或播放量为空的行直接丢弃因为这两列是后续可视化的核心字段。df df.drop_duplicates(subset[playlist_id], keepfirst) df df.dropna(subset[title, play_count_num]) print(去重后条数:, len(df)) print(播放量描述统计:\n, df[play_count_num].describe())drop_duplicates 的 keepfirst 表示遇到重复 id 时保留第一次出现的记录。dropna 之后再用 describe() 看播放量的均值、中位数和分位数这一步可以提前发现异常值比如某个歌单播放量是几十亿明显是测试数据后面画图时可以单独过滤掉。3.4 给歌单补一个分类字段关键词映射的土办法歌单广场的“全部”分类页面拿不到每条歌单的准确分类标签因为分类信息在详情页里逐条访问详情页又会让爬虫工作量翻倍。期末作业阶段没有必要搞那么重我一般直接用标题关键词做粗分类覆盖主流歌单类型就够用。def guess_category(title): title str(title) rules { 华语: [华语, 国语, 中文], 粤语: [粤语, 港乐], 纯音乐: [纯音乐, 钢琴, 轻音乐, 治愈], 民谣: [民谣, 吉他], 摇滚: [摇滚, 乐队, 金属], 电子: [电子, 电音, DJ], 影视: [影视, OST, 电影, 电视剧], } for category, keywords in rules.items(): for keyword in keywords: if keyword in title: return category return 其他 df[category] df[title].apply(guess_category) print(df[category].value_counts())分类规则表写在函数外面方便随时增删关键词。这个步骤的价值不在于分类多精准而在于后续可以按分类做组间对比比如比较“纯音乐”和“华语”两个分类的平均收藏数。分析报告里能拿出这种对比结论比单纯贴一张图表要有说服力得多。4. 数据可视化落地matplotlib 画出播放量分布与分类占比4.1 先把中文字体问题解决掉否则后面全白干matplotlib 默认字体是英文字体直接用中文标题会显示成一个个方块。这个问题在 Windows 上处理起来很简单指定黑体 SimHei 即可。Linux 服务器上如果没有 SimHei就要手动下载字体注册到 matplotlib后面避坑章节会单独讲。import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False这两行配置要放在所有画图代码之前。axes.unicode_minus 设置为 False 是为了解决坐标轴上的负号显示成方块的问题。之后所有 plt.title()、plt.xlabel() 里的中文都能正常渲染。如果运行环境是 macOS把 SimHei 换成 Arial Unicode MS 同样有效。4.2 播放量分布直方图长尾效应一眼可见清洗完的数据里有一条很明显的规律大部分歌单播放量集中在较低区间少数头部歌单播放量极高。直接画全量数据横轴会被极端值拉得很宽低区间的分布细节完全看不清。常见做法是先用 describe() 看一下分位数把 99 分位以上的极端值暂时滤掉再画图。limit_value df[play_count_num].quantile(0.99) filtered df[df[play_count_num] limit_value] fig, ax plt.subplots(figsize(10, 6)) ax.hist(filtered[play_count_num], bins40, color#128C7E, edgecolorwhite) ax.set_xlabel(播放量) ax.set_ylabel(歌单数量) ax.set_title(歌单播放量分布图去掉前 1% 极端值) plt.tight_layout() plt.savefig(play_count_dist.png, dpi150) plt.show()bins 设为 40让直方图的粒度既不至于太粗看不出形状也不至于太细产生大量空柱子。标题里注明“去掉前 1% 极端值”是给看报告的人一个数据口径交代这也是期末大作业答辩时老师喜欢看到的细节——你能说清楚图里每一层处理逻辑是什么。运行环境没图形界面时去掉 plt.show() 只保留 plt.savefig() 即可。4.3 播放量与收藏、评论的散点关系找正相关还是弱相关只画单变量分布不足以支撑“歌单优化建议”这个结论。播放、收藏、评论三个数字之间有没有关联才是分析的重点。散点图加一条趋势线是最直观的呈现方式。这里以播放量和评论数为例如果爬虫阶段抓到了收藏数字段把 y 换成收藏列即可。x df[play_count_num] y df[comment_num] # 清洗阶段处理过的评论数字段 valid (x 0) (y 0) x x[valid] y y[valid] fig, ax plt.subplots(figsize(10, 6)) ax.scatter(x, y, alpha0.4, s8, color#FF5C5C) ax.set_xscale(log) ax.set_yscale(log) ax.set_xlabel(播放量对数坐标) ax.set_ylabel(评论数对数坐标) ax.set_title(播放量与评论数关系散点图) plt.tight_layout() plt.savefig(play_comment_scatter.png, dpi150) plt.show()这里采用了 log-log 坐标因为播放量和评论数都跨越了好几个数量级线性坐标下绝大多数点会挤在左下角什么都看不出来。valid 过滤条件把评论数为 0 的行拿掉否则取对数会变成负无穷图上直接缺一块。alpha0.4 控制点的透明度点重叠严重时能看到密度差异。如果散点整体呈正相关趋势说明评论数随播放量同步增长歌单运营的重点是拉播放如果中高播放量区间的评论数偏低说明歌单内容虽然被听但缺少互动引导优化建议就要往评论区运营方向写。4.4 矩形树图歌单分类的占比关系一眼抓住分类占比用饼图画当然可以但在分类多、名称长时饼图的标签容易挤成一团。squarify 生成的矩形树图面积代表数量、标签放在矩形内部信息密度更高也更适合放进期末报告里当亮点。import squarify category_counts df[category].value_counts().head(8) sizes category_counts.values labels [f{idx}\n{val}张 for idx, val in category_counts.items()] colors plt.cm.tab20.colors[:len(sizes)] fig, ax plt.subplots(figsize(10, 6)) squarify.plot(sizessizes, labellabels, colorcolors, axax, alpha0.85) ax.axis(off) ax.set_title(歌单分类数量占比矩形树图) plt.tight_layout() plt.savefig(category_treemap.png, dpi150) plt.show()value_counts().head(8) 只取数量最多的 8 个分类剩下的“其他”类别因为数量分散画进去会让图面碎掉。label 里的 val 是每类数量直接把数字放进图里报告里引用时不用再回去翻代码统计。color 用 tab20 的颜色映射保证颜色不重复也不需要额外引入 seaborn 依赖。5. 高频踩坑排查这份代码最容易翻车的四个位置5.1 带 # 的链接抓回来是空壳页面现象requests 请求 https://music.163.com/#/playlist?idxxx返回的 HTML 里找不到任何歌单内容。原因网易云前端是单页应用URL 里 # 后面的路由由浏览器端 JavaScript 解析服务端只返回空的页面框架。requests 不会执行 JS自然拿不到内容。解决去掉 #直接请求 https://music.163.com/playlist?idxxx或者在歌单广场翻页用 discover/playlist 这种服务端渲染的列表页。5.2 播放量单位混在一起直方图报错现象绘图时 ValueError提示无法将字符串转换为 float检查数据发现播放量列同时存在“5.3万”和“123456”。原因页面不同位置的格式化规则不一致有的用单位缩写有的用纯数字。解决在 3.2 的 clean_count 函数里统一处理先判断“亿”再判断“万”最后转 float。处理完重新检查 df.dtypes确保 play_count_num 是 float64 再画图。这个坑几乎每个做中文数据可视化的人都会踩一次清洗步骤千万别省。5.3 中文字体方块与负号异常现象plt.title(播放量分布) 出来的图标题是□□□□□坐标轴负号变成竖线。原因matplotlib 默认字体 DejaVu Sans 不包含中文字形负号渲染也依赖字体。解决Windows 下配置 plt.rcParams[font.sans-serif] [SimHei]Linux 环境下先确认系统有没有中文字体没有就通过 font_manager.addfont() 注册自己下载的字体文件然后清掉 matplotlib 的字体缓存重新运行。处理完别再手动给每张图传字体路径统一配置就行。5.4 单一页面反复请求被触发验证码现象爬着爬着状态码突然变成 418或者偶尔返回一个需要点击验证的页面。原因同一 IP 的请求频率过高触发了网易云的风控策略。解决每个请求之间插入 time.sleep(random.uniform(0.3, 0.8))降低请求密度同时固定维持同一个 session避免每次请求都重新握手。被抓了不要硬刚把已抓数据先落盘停几分钟再继续。如果你是校园网出口同一个 IP 后面可能还有别人在爬频率要比正常情况再低一档。5.5 pandas 读取 CSV 时中文列名变成乱码现象用 pandas 读 CSV列名和内容全是乱码但记事本打开正常。原因CSV 保存时用了 GBK 编码pandas 默认按 UTF-8 读取。解决读取时指定 encodinggbk或者保存时就统一用 utf-8-sig。这个项目里统一用 utf-8-sig 保存Windows 下 Excel 和 pandas 都能正常打开不需要来回切换编码。6. 从“能跑”到“能答辩”词云进阶与验证收尾6.1 jieba 分词 wordcloud 生成歌单标题词云歌单标题是最能反映平台热门趋势的文本数据。用 jieba 分词拆出关键词再过滤掉“歌单”“精选”这类高频但无区分度的词最后交给 wordcloud 生成词云。这里最关键的一个参数是 font_path不指定中文字体词云里所有文字会变成豆腐块。import jieba from wordcloud import WordCloud stopwords {歌单, 精选, 热门, 经典, 推荐, 合集} text .join(df[title].astype(str).tolist()) words jieba.cut(text, cut_allFalse) filtered [w for w in words if len(w) 1 and w not in stopwords] wc WordCloud( font_pathC:/Windows/Fonts/simhei.ttf, # Linux 下换成实际字体路径 width800, height600, background_colorwhite, max_words150, collocationsFalse ).generate( .join(filtered)) wc.to_file(title_wordcloud.png)max_words 控制词云显示的词语数量设置太大图会显得杂乱collocationsFalse 避免 wordcloud 把相邻词合成新词组对中文文本基本是必选项。stopwords 集合可以按你实际抓到的标题内容扩充多过滤几个高频无意义词词云的可读性会明显提升。6.2 从图表到结论三句可以写进报告的分析判断图表画完不算结束报告里要有能落地的分析结论。基于这个项目的数据我通常提炼三条判断。第一播放量分布严重长尾头部歌单拿走大部分流量新歌单需要走差异化定位而不是跟头部直接竞争。第二播放量与评论数呈正相关但高播放区间的评论密度明显低于低区间说明大歌单缺少互动转化设计运营重点应该放在评论区引导。第三分类矩形树图里“纯音乐”和“华语”占比较高与平台用户收听习惯一致做歌单可以优先布局这两类。每一条都要带上你实际算出的数字比如“前 5% 的歌单占了总播放量 62%”而不是空写“数据显示”。6.3 答辩之前的三步验证清单我每次交这类数据作业前都会把下面三步走一遍抽 5 张歌单去网易云页面人工核对播放量防止爬虫解析误差把清洗后的数据重新 describe 一次确认没有负数和离谱的极端值检查每张图的标题、坐标轴标签和单位是否齐全图表里所有中文是否正常渲染。这三步全过作品基本不会有低级翻车。语法层面的运行问题按第 5 章逐条对照排查即可。从那以后我每做一个 Python 数据分析项目都强制先验证字体、单位一致性和样本量再谈画图和分析这套习惯帮我少熬了很多夜希望帮到你。本文还有配套的精品资源点击获取