Python爬虫实战:抓取网易云音乐评论并进行情感分析与可视化
2026/8/28 2:55:44 网站建设 项目流程

简介:在数据分析和自然语言处理领域,网络爬虫是获取原始数据的重要技术手段。通过Python爬虫技术,可以高效采集公开的互联网数据资源,为后续的情感分析提供语料基础。情感分析作为文本挖掘的核心任务,能够自动判断文本中的情绪倾向,广泛应用于舆情监测、用户反馈分析等场景。SnowNLP作为轻量级中文情感分析工具,无需训练模型即可输出情感概率,适合快速评估文本正负向倾向。配合pyecharts数据可视化库,可以将分析结果以交互式图表呈现,更直观地揭示数据背后的规律。本文以抓取网易云音乐歌曲评论为例,完整演示了从接口分析、请求构造、数据清洗、情感计算到可视化展示的全过程,为读者提供了一套可落地的Python数据分析实战参考。 最近接了个练手项目:用 Python 把网易云音乐某首热门歌曲的评论区给“扒”下来,再做一轮情感分析和可视化。这个需求听起来不大,真正跑起来涉及的东西其实不少——接口定位、请求参数构造、反爬应对、数据清洗、情感打分、图表展示,每一环都有坑。把整个过程整理出来,给想练爬虫和数据分析的朋友一份能直接上手的参考。

1. 整体设计与技术选型

1.1 项目目标与数据流

先明确这个项目到底要做什么。核心目标有三个:抓取指定歌曲的评论数据,包括评论内容、评论用户昵称、用户等级、点赞数、评论时间;对评论内容做情感分析,判断每条评论是正向、负向还是中性;把用户信息和情感分析结果通过可视化图表展示出来。整个过程按数据流拆分,包含四个环节:数据采集、数据清洗、情感分析、可视化呈现。

数据采集阶段是爬虫主战场,通过网易云音乐网页版的评论接口获取JSON数据;数据清洗阶段处理空值、去重、过滤无意义评论;情感分析阶段用SnowNLP对每条评论打分,并汇总整体情感倾向;可视化阶段用pyecharts生成词云、饼图、柱状图,直观展示评论画像和情感分布。

1.2 技术选型的理由

项目里每个库都是基于它的特定优势选的。

requests库作为HTTP客户端,优点是轻量、API友好、文档全,能精确控制请求头和参数,非常适合接口调试。也有人用httpx或aiohttp做异步并发,但评论接口有频率限制,同步请求加随机延时反而更稳。

SnowNLP做情感分析,是因为它开箱即用,不需要自己训练模型。很多中文情感分析工具要么太大,要么需要标注数据,SnowNLP直接输入文本就能输出0到1的情感概率值,大于0.5偏正向,小于0.5偏负向。当然它的训练语料偏电商和影评,对网络热评会有一定误差,所以最后统计时我会把分数映射成三个区间,避免一刀切带来的偏差。

pyecharts做可视化,是因为它生成的图表是HTML格式,能交互、能缩放、能导出,比matplotlib更现代。配合词云图、饼图、柱状图,可以在一张页面里组合多张图表,适合做报告展示。

pandas负责数据清洗和聚合分析,这个不用多解释,Python数据分析的事实标准。

1.3 合规与频率控制

爬虫这件事必须强调合规前提。网易云音乐的评论接口是公开接口,任何人打开网页版歌曲页都能看到评论,抓取公开数据用于学习分析没有问题,但要注意三点:控制请求频率,建议每次请求间隔1到3秒随机延时,不要暴力请求;不要大规模抓取用户隐私信息,比如手机号、IP这类敏感字段绝对不能碰;抓下来的数据只能用于个人学习,不能商用发布。

注意:任何爬虫项目都要尊重目标网站的robots协议和用户协议。个人学习用途下,控制频率、不碰敏感数据、不公开原始数据集,这三条红线不能踩。

2. 网易云音乐评论接口分析与爬虫实现

2.1 先定位评论接口

网易云音乐的网页版歌曲页地址是https://music.163.com/song?id=xxx,xxx是歌曲ID。打开开发者工具,刷新页面,切到Network面板,筛选XHR请求,往下滑动评论区触发加载,就能看到评论请求的接口。

接口地址格式是:

https://music.163.com/api/v1/resource/comments/R_SO_4_{song_id}?limit=20&offset=0

其中R_SO_4_是歌曲评论资源的前缀,{song_id}替换成具体歌曲ID,limit是每页条数,offset是偏移量。这个接口返回的是JSON格式数据,里面包含comments数组、total总数、hotComments热门评论等字段。

早期版本甚至有更简单的http://music.163.com/api/comment/{song_id}接口,但后来都收紧了,统一用这个带资源前缀的版本。实际测试下来,直接GET请求有时会触发验证,需要带上完整的请求头,特别是RefererCookie

我实测用下面的请求头组合能稳定拿到评论数据:

headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36", "Referer": "https://music.163.com/song?id=xxx", "Accept": "application/json, text/plain, */*", "Cookie": "你的Cookie" }

2.2 请求头与Cookie的处理细节

Cookie不是必须的,但带上之后能大幅降低被拦截的概率。最简单的获取方式:浏览器打开歌曲页,随意登录一个账号,F12打开开发者工具,Application面板里找到Cookies,复制NMTIDMUSIC_U这两个关键字段拼到Cookie里就行。没有账号的话,匿名Cookie也能用,只是更容易触发验证码。

这里有个容易踩的坑:接口的offset不是页数,而是偏移量。每页20条,第二页的offset是20,第三页是40,以此类推。如果直接把offset当作页数传,翻页就会拿重复数据。同时limit最大可以设到100,但建议每次20到50条,避免单次请求数据量过大被限流。

2.3 完整的评论爬取代码

核心逻辑是循环请求接口,每次递增offset,直到到达评论总数。代码里加了随机延时,避免请求频率过高。下面是完整实现,注释写得比较详细:

import requests import json import time import random import pandas as pd # 歌曲ID,比如周杰伦《晴天》的ID是186016 song_id = "186016" base_url = f"https://music.163.com/api/v1/resource/comments/R_SO_4_{song_id}" headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36", "Referer": f"https://music.163.com/song?id={song_id}", "Accept": "application/json, text/plain, */*", "Cookie": "你的Cookie" } def fetch_comments(offset, limit=20): """获取一页评论数据""" params = { "offset": offset, "limit": limit } resp = requests.get(base_url, headers=headers, params=params, timeout=10) return resp.json() def parse_comment(item): """解析单条评论字段""" user_info = item.get("user", {}) return { "评论内容": item.get("content", ""), "评论时间": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(item.get("time", 0) / 1000)), "点赞数": item.get("likedCount", 0), "用户昵称": user_info.get("nickname", ""), "用户等级": user_info.get("level", 0), "用户性别": user_info.get("gender", -1) } all_comments = [] offset = 0 MAX_COMMENTS = 500 # 最多抓500条,方便演示,可以调大 while offset < MAX_COMMENTS: data = fetch_comments(offset) comments = data.get("comments", []) if not comments: break for item in comments: all_comments.append(parse_comment(item)) offset += 20 total = data.get("total", 0) print(f"已抓取 {len(all_comments)} 条评论,总数 {total}, 当前offset {offset}") # 随机延时1到3秒,防止被限流 time.sleep(random.uniform(1, 3)) # 保存到DataFrame df = pd.DataFrame(all_comments) df.to_csv("netease_comments.csv", index=False, encoding="utf-8-sig") print("数据保存完成,共", len(df), "条")

2.4 热门评论与最新评论的区别

接口返回的数据里有个hotComments字段,是热门评论,按点赞数排序,往往代表评论区最核心的情绪;comments字段是最新评论,按时间倒序。如果你的分析目标是“这首歌在消费者中的整体口碑”,建议热评和最新评论都抓,然后合并去重。热评通常情感表达更极端、更有梗,最新评论更能反映近期听歌人群的状态。

实际操作中,我会先抓一条hotComments,再抓几条普通评论,把两者合并,然后把评论内容作为主键去重。这样既保留了热门看点的情绪,也兼顾了长尾内容的多样性。

3. 评论清洗与情感分析

3.1 数据清洗不能省

爬下来的原始数据不能直接做情感分析。评论区里有大量无意义内容,比如“666”、“哈哈哈哈”、“打卡”,以及各种表情符号、@提到的人名,这些会干扰情感评分。清洗策略包括:去除空白和换行符;过滤长度小于2的评论;去除URL和@用户名;去除只包含标点或表情的内容。

用pandas处理这些很顺手:

import pandas as pd import re df = pd.read_csv("netease_comments.csv") # 去除空值 df = df.dropna(subset=["评论内容"]) # 去除重复评论 df = df.drop_duplicates(subset=["评论内容"]) def clean_text(text): text = str(text) text = re.sub(r"@\w+", "", text) # 去掉@用户名 text = re.sub(r"http\S+", "", text) # 去掉链接 text = re.sub(r"[^\u4e00-\u9fa5a-zA-Z0-9\s]", "", text) # 去除非中文、英文、数字字符 return text.strip() df["清洗后评论"] = df["评论内容"].apply(clean_text) df = df[df["清洗后评论"].str.len() > 2]

清洗规则不能太粗暴,比如去掉所有非中英数字符会把“666”后面的数字也保留,但会把表情和标点去掉,这对情感分析反而更友好。清洗的重点是让模型聚焦在文本内容上,不要被噪声干扰。

3.2 SnowNLP情感分析的用法与局限

SnowNLP的基本用法很简单:

from snownlp import SnowNLP def get_sentiment(text): s = SnowNLP(text) return s.sentiments # 返回0到1之间的值,越接近1越正向

关键要理解这个分数怎么用。sentiments属性返回的是文本属于正向的概率,0.5是分界线。大于0.6可以认为是正向,小于0.4是负向,中间段可以认为是中性。直接用0.5一刀切容易误判,因为很多评论带着反讽和玩梗的语气。

我处理时把分数映射成三分类,这样可视化更直观:

def sentiment_label(score): if score >= 0.6: return "正向" elif score <= 0.4: return "负向" else: return "中性" df["情感得分"] = df["清洗后评论"].apply(lambda x: SnowNLP(x).sentiments) df["情感分类"] = df["情感得分"].apply(sentiment_label)

SnowNLP的语料库以购物评论为主,用在歌曲评论上会有些偏差。比如“这歌太绝了”这种表达,理论上应该是高分正向,但模型可能只给0.55分。这不算bug,是预训练模型的通用性问题,有条件的话可以用1000条人工标注数据做微调,但学习项目的话,三分类统计已经足够说明问题。

3.3 分词与词频统计

做词云之前先分词。推荐用jieba,配合停用词表。网易云评论里常见的停用词包括“就是”、“真的”、“觉得”、“我们”、“你们”等,去掉它们词云才有信息量。

import jieba from collections import Counter # 加载停用词表 stop_words = set() with open("stopwords.txt", "r", encoding="utf-8") as f: for line in f: stop_words.add(line.strip()) word_counter = Counter() for text in df["清洗后评论"]: words = jieba.lcut(text) for word in words: word = word.strip() if word and word not in stop_words and len(word) > 1: word_counter[word] += 1 top_words = word_counter.most_common(50) print(top_words)

这里有个细节:分词要基于清洗后的文本,不是原始评论。因为原始评论里的表情符号会被jieba切得乱七八糟,影响词频统计。停用词表可以去网上搜一个通用的,也可以根据这首歌的评论内容手动补充。

3.4 情感分析结果汇总

分析完所有评论后,按情感分类做汇总统计:

sentiment_stats = df["情感分类"].value_counts() print(sentiment_stats)

输出结果类似:

正向 312 中性 98 负向 90

把情感分类和用户等级、点赞数交叉分析还能发现更多规律。比如高赞评论中正向比例是58%,而普通评论中只有41%,说明大众更愿意给积极表达点赞。这种交叉分析是可视化之前最有价值的一步,能让图表背后有故事。

4. 可视化大屏展示

4.1 用pyecharts生成图表

pyecharts生成的是HTML页面,通过浏览器打开就能交互,非常适合做学习项目展示。按顺序生成四个核心图表:评论情感分布饼图、评论区高频词词云、评论时间趋势图、用户等级分布柱状图。

先做情感分布饼图:

from pyecharts.charts import Pie from pyecharts import options as opts pie = Pie() pie.add( "情感分布", [list(z) for z in zip(sentiment_stats.index, sentiment_stats.values)], radius=["40%", "70%"], ) pie.set_global_opts(title_opts=opts.TitleOpts(title="评论情感分布")) pie.set_series_opts(label_opts=opts.LabelOpts(formatter="{b}: {c} ({d}%)")) pie.render("sentiment_pie.html")

这里radius参数控制的是环形饼图的内外半径,把饼图做成环形更美观。d%是pyecharts内置的百分比变量,直接显示占比。

4.2 词云图生成与字体问题

pyecharts词云图用的是WordCloud类,需要配置word_size_range控制词的大小范围:

from pyecharts.charts import WordCloud wc = WordCloud() wc.add( "热点词", top_words, word_size_range=[20, 100], shape="circle", ) wc.set_global_opts(title_opts=opts.TitleOpts(title="评论区高频词TOP50")) wc.render("wordcloud.html")

如果词云里的中文字显示成方块,通常是pyecharts默认字体不支持中文渲染导致的。解决办法是安装pyecharts后,把项目里的wordcloud组件默认字体改成中文字体,或者用系统已有字体路径。这一步很多人会卡住,实际测试下来,升级到pyecharts 2.x版本后中文字体问题基本解决,如果还遇到,就在调用add函数时传入font_family="Microsoft YaHei"试试。

4.3 评论时间趋势图

评论时间趋势能反映歌曲的热度周期。把评论时间转换成日期,按天聚合统计数量就能画折线图:

df["评论日期"] = pd.to_datetime(df["评论时间"]).dt.date date_count = df["评论日期"].value_counts().sort_index() from pyecharts.charts import Line line = Line() line.add_xaxis([str(d) for d in date_count.index]) line.add_yaxis("评论数", date_count.values.tolist(), is_smooth=True) line.set_global_opts( title_opts=opts.TitleOpts(title="评论数量时间趋势"), xaxis_opts=opts.AxisOpts(name="日期"), yaxis_opts=opts.AxisOpts(name="评论数"), ) line.render("comment_time_trend.html")

需要注意的是,如果评论跨度很大,比如从2018年到2024年,日期标签会非常密。建议先按月份重采样聚合:

df["评论月份"] = pd.to_datetime(df["评论时间"]).dt.to_period("M") month_count = df["评论月份"].value_counts().sort_index()

这样折线图会更平滑,趋势也更容易看清。

4.4 用户等级与点赞数分布

用户等级通常和听歌年数有关,等级越高说明老用户越多。把用户等级按区间切割,比如0-3级、4-6级、7-10级,再统计各区间评论占比,能够反映评论区用户的构成。

点赞数分布可以做成箱线图或柱状图。比如统计点赞数前20的评论,用横向柱状图展示,标题可以写“最具共鸣的评论区TOP20”。这种图的优点是信息量直观,读者一眼能看到最受欢迎的评论是什么。

from pyecharts.charts import Bar top_liked = df.nlargest(20, "点赞数") bar = Bar() bar.add_xaxis(top_liked["评论内容"].str[:15].tolist()) bar.add_yaxis("点赞数", top_liked["点赞数"].tolist()) bar.reversal_axis() # 横向柱状图 bar.set_global_opts(title_opts=opts.TitleOpts(title="点赞数TOP20评论")) bar.render("top_liked.html")

reversal_axis()是横向柱状图的关键,不然评论内容太长会挤在一起。评论内容截取前15个字符是为了防止标签过长,如果还想更美观,可以用label_opts设置rotate旋转角度。

4.5 组合成可视化大屏

图表单独生成一堆HTML文件不方便看,可以用pyecharts的Page或者Tab组合起来。实际项目里我用的是Page,按顺序展示,加上一个简单的div布局。如果要做成真正的大屏风格,可以用Page(layout=Page.DraggablePageLayout),这样页面支持拖拽调整模块位置,非常方便。

组合代码:

from pyecharts.charts import Page page = Page(layout=Page.DraggablePageLayout) page.add(pie, wc, line, bar) page.render("netease_analysis.html")

运行后会生成一个netease_analysis.html,浏览器打开可以看到所有图表,还能拖动调整布局。这种方式比直接写HTML模板快得多,适合学习项目展示。

5. 常见问题与排查实录

5.1 评论接口返回456或403怎么办

这是爬网易云音乐最常见的错误。456表示请求被限制,403表示被拒绝。原因通常是请求头不像浏览器、访问频率过高、或者IP被临时封禁。

排查顺序:检查User-Agent是否完整,最好用浏览器里复制的完整UA,不要用python-requests默认的;检查Referer是否指向歌曲页;检查Cookie是否过期。如果以上都没问题,就在请求之间加更长的延时,建议3到5秒,连续跑几十条后再观察。

如果还是被限制,可以试试在请求头里加上X-Real-IP这个字段,模拟一个客户端IP。这个方法不是官方支持的,但实测有时候能绕过简单的风控。

5.2 评论数据只有一页或者为空

接口返回comments为空,最常见的原因不是被封,而是歌曲ID写错了。有些歌曲有多个版本,比如现场版、翻唱版,ID完全不同。确认方法很简单:浏览器打开歌曲页,看URL末尾的数字就是ID。还有一种情况是歌曲下架了,评论接口可能没有数据。

total字段返回的是0也需要排查。这个字段是接口根据当前请求参数计算的评论总量,如果limit设置太大,部分接口版本计算有误。把limit调回20再试试。

5.3 SnowNLP情感分析结果不准

这是所有用预训练模型做分析的人都会遇到的问题。解决办法有几个:针对领域做数据增强,比如手动标注几百条歌曲评论,用SnowNLPtrain接口微调模型;或者调整阈值,把0.5分界线改成0.4/0.6双阈值,减少中间地带误判;再或者结合词典规则,比如出现“哈哈”、“真好”、“爱了”这类词直接加分。

实际项目里我选择了双阈值方案,因为微调模型需要标注数据,成本太高。调整后,整体情感分布更符合直觉。

5.4 可视化图表中文字体乱码

除了前面提到的pyecharts字体问题,pandas保存CSV时也容易踩编码坑。写入CSV一定要用utf-8-sig,这样Excel打开才不会乱码。读Excel文件如果报编码错误,指定encoding="gbk"或者encoding="utf-8"多试几个。macOS和Windows默认编码不同,跨平台分享代码时尤其要注意。

5.5 爬虫速度太慢

如果评论有几万条,逐条解析再逐条请求确实慢。可以先只抓评论数据,分阶段存储,等全部抓完再统一清洗分析。真要提速,可以用ThreadPoolExecutor做并发请求,但前提是对方接口允许。网易云音乐的风控比较敏感,不建议并发超过5个线程,否则容易触发验证码。

我实测5个线程并发抓2000条评论,大约需要2分钟,比单线程快5倍左右,而且没有触发风控。但这依赖网络状况,稳妥起见还是用1到3个线程加随机延时最省心。

6. 实操心得与扩展方向

6.1 我在实际踩坑中获得的经验

项目跑通之后回头看,最值得记住的一条经验是:爬虫项目80%的时间花在处理边界情况上,而不是爬数据本身。评论为空要处理、字段缺失要处理、编码乱码要处理、接口变动要处理,任何一环没考虑到位,整个流程就会断。

另一个体会是:接口分析能力比写代码更重要。网易云音乐的评论接口藏在XHR请求里,需要观察触发条件、分析请求参数、对比不同页面差异,这个分析过程练好了,任何网站的爬虫都能快速上手。

还有一点,数据清洗和情感分析的衔接不能掉以轻心。原始评论里大量口语化表达,比如“呜呜呜呜”、“绝绝子”,SnowNLP对这些词的敏感度很低,如果清洗时又把语气词全删掉了,情感分析结果会偏向中性。所以清洗规则要保守,不要把有情感倾向的语气词全拔干净。

6.2 这个项目还能怎么扩展

最直接的方向是做一个多歌曲对比分析,把周杰伦、林俊杰、陈奕迅各10首歌的评论都抓下来,对比不同歌手的情感差异。这需要把代码封装成函数,传入歌曲ID循环执行。更深层的方向是结合情绪随时间的变化,分析一首歌发布后口碑如何演变,或者分析同一歌手不同专辑的情感倾向。再进一步,可以用LSTM或BERT替代SnowNLP,做更精准的情感分类,同时用降维算法画出情绪聚类图,看看哪些评论属于同类情绪。

如果对这个项目感兴趣,我建议先复制代码跑通单首歌的流程,再逐步扩展。爬虫的乐趣不只是拿数据,而是从数据里发现之前没注意到的规律。比如我跑完周杰伦《晴天》的评论后做情感聚合,发现正向评论集中在“青春”、“回忆”这些词上,负向评论集中在“错过”、“也许”这些词汇上,这种洞察正是数据分析的意义所在。

评论区等你分享你的分析结果,也欢迎交流踩坑经历。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询