简介:这份资源面向Python爬虫与自然语言处理方向的初学者及课程设计者,围绕京东商品评论的采集、清洗、情感分析与可视化展开,可作为数据分析大作业或NLP入门练手项目。压缩包共20个文件,约5.87MB,以py脚本、csv数据表、txt文本、jpg与png图表为主,另含zbak备份与md说明,覆盖爬虫代码、情感分析脚本、训练脚本及原始与处理后评论数据。已有76人学习下载。读者可从中获得一套完整流程:从京东评论抓取、去重与格式标准化,到基于朴素贝叶斯或深度学习的正负情感分类,再到词云、饼图等可视化呈现,最终结果以CSV形式保存,便于复现与二次开发。资源还附带分类图标与依赖清单,适合对照搭建实验环境、理解情感分析建模思路,并作为课程报告或论文的参考素材。
1. 京东评论爬取与情感分析:从零到可视化大屏的完整落地路径
打开京东搜一款手机,动辄几十万条评价,好评差评混在一起,靠人工翻页根本看不出问题。我去年帮一个做小家电的朋友搭过一套流程,目标很明确:把京东商品评论批量抓下来,用 Python 做情感倾向判断,最后用可视化图表把「用户到底在夸什么、骂什么」摆到台面上。这套东西适合有 Python 基础、想入门爬虫实战和数据分析的开发者,也适合做电商运营、选品调研的从业者。核心链路就四步:请求评论接口拿数据、清洗入库、情感打分、可视化呈现。听起来简单,但每一步都有翻车点,下面按我实际跑通的顺序拆开讲。
2. 评论数据从哪来:京东评论接口的请求构造与翻页逻辑
2.1 为什么不用 Selenium 硬点页面
很多人第一反应是用 Selenium 或 Playwright 模拟浏览器点击翻页。这条路能走通,但效率极低,一个商品几千条评论要跑十几分钟,还容易被页面改版搞崩。京东的商品评论实际是通过一个独立的 JSON 接口返回的,常见做法是直接请求这个接口,拿到结构化数据。接口地址形如https://club.jd.com/comment/productPageComments.action,关键参数包括productId(商品 ID)、score(评分筛选,0 全部、1 差评、2 中评、3 好评)、sortType(排序方式)、page(页码)、pageSize(每页条数,默认 10)。这个接口返回的是 JSON,解析起来比解析 HTML 省事得多。
需要注意的是,这个接口有频率限制和 Referer 校验。我一般会带上Referer: https://item.jd.com/{productId}.html和一组真实的User-Agent,并且每次翻页间隔 1 到 2 秒。不带 Referer 的话,大概率返回空数据或者直接 403。
2.2 最小可运行的请求代码
import requests import time import json def fetch_comments(product_id, max_page=10): """ 抓取京东商品评论 product_id: 商品ID,从商品详情页URL中提取 max_page: 最大翻页数,控制抓取量 """ base_url = "https://club.jd.com/comment/productPageComments.action" headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/120.0.0.0 Safari/537.36", "Referer": f"https://item.jd.com/{product_id}.html" } all_comments = [] for page in range(max_page): params = { "productId": product_id, "score": 0, # 0=全部, 1=差评, 2=中评, 3=好评 "sortType": 5, # 5=按时间排序 "page": page, "pageSize": 10, "isShadowSku": 0, "fold": 1 } try: resp = requests.get(base_url, params=params, headers=headers, timeout=10) data = resp.json() comments = data.get("comments", []) if not comments: break for c in comments: all_comments.append({ "content": c.get("content", "").strip(), "score": c.get("score", 0), "time": c.get("creationTime", ""), "nickname": c.get("nickname", ""), "product_color": c.get("productColor", "") }) time.sleep(1.5) # 控制频率,避免触发风控 except Exception as e: print(f"第{page}页抓取失败: {e}") time.sleep(3) return all_comments if __name__ == "__main__": result = fetch_comments("100012043978", max_page=5) print(f"共抓取 {len(result)} 条评论") with open("comments.json", "w", encoding="utf-8") as f: json.dump(result, f, ensure_ascii=False, indent=2)这段代码的逻辑很直白:循环请求每一页,把返回 JSON 里的comments数组逐条提取,存成字典列表,最后写入 JSON 文件。几个参数需要重点说:score设为 0 表示不筛选评分,全量抓;sortType设为 5 是按时间倒序,方便你拿到最新评论;pageSize默认 10,有些接口版本支持调到 20 或 30,但调太大容易被限。time.sleep(1.5)是保命用的,别省。
2.3 翻页终止条件和数据量估算
接口返回的 JSON 里有一个maxPage字段,表示该商品评论的总页数。你可以在第一次请求后读取这个值,动态决定翻页上限,而不是写死max_page。一个热销商品的评论可能上万条,按每页 10 条算就是上千页。实际抓取时建议先抓前 50 到 100 页做分析,除非你确实需要全量。抓太多不仅慢,还容易触发 IP 级别的限流。
3. 情感分析怎么选:SnowNLP、BERT 与规则打分的取舍
3.1 三种方案的适用边界
情感分析是这套流程的核心环节,选错方案要么准确率惨不忍睹,要么部署成本高得离谱。我实际用过的三种方案:
| 方案 | 准确率(中文电商评论) | 部署难度 | 速度 | 适用场景 |
|---|---|---|---|---|
| SnowNLP | 约 70%-75% | 极低,pip 装完就用 | 快 | 快速验证、小批量 |
| 规则+词典 | 约 65%-70% | 低,需维护词典 | 极快 | 特定品类、有领域词表 |
| BERT 微调 | 约 88%-92% | 中高,需 GPU 或较长 CPU 推理 | 慢 | 正式项目、大批量 |
SnowNLP 是一个轻量级中文情感分析库,对每条评论返回 0 到 1 之间的情感分数,越接近 1 越正面。它的优点是开箱即用,缺点是训练语料偏旧,对「绝绝子」「yyds」这类网络用语和反讽句式基本无能为力。BERT 微调效果最好,但你需要标注数据、训练模型、搭推理服务,对个人项目来说偏重。我的建议是:先用 SnowNLP 跑通全流程,验证可视化效果,如果发现误判严重再考虑升级到 BERT。
3.2 用 SnowNLP 做批量情感打分的代码
from snownlp import SnowNLP import json def analyze_sentiment(comments): """ 对评论列表做情感分析 返回带情感分数的评论列表 """ results = [] for c in comments: text = c["content"] if not text or len(text) < 3: continue # 跳过空评论和过短评论 try: s = SnowNLP(text) sentiment = s.sentiments # 0~1,越接近1越正面 c["sentiment"] = round(sentiment, 4) c["label"] = "正面" if sentiment > 0.6 else ( "负面" if sentiment < 0.4 else "中性") results.append(c) except Exception as e: print(f"分析失败: {text[:20]}... 原因: {e}") return results if __name__ == "__main__": with open("comments.json", "r", encoding="utf-8") as f: comments = json.load(f) analyzed = analyze_sentiment(comments) positive = sum(1 for c in analyzed if c["label"] == "正面") negative = sum(1 for c in analyzed if c["label"] == "负面") print(f"正面: {positive}, 负面: {negative}, 总计: {len(analyzed)}") with open("comments_analyzed.json", "w", encoding="utf-8") as f: json.dump(analyzed, f, ensure_ascii=False, indent=2)这里的关键参数是阈值划分。我设的是大于 0.6 算正面、小于 0.4 算负面、中间算中性。这个阈值不是固定的,你可以根据实际数据分布调整。如果发现大部分评论都落在 0.5 附近,说明 SnowNLP 对这个品类的区分度不够,可以考虑用规则补充:比如评论里出现「退货」「垃圾」「差评」直接判负面,出现「推荐」「回购」「满意」直接判正面,规则和模型分数加权融合。
3.3 处理反讽和网络用语的补救手段
SnowNLP 最大的坑是对反讽句式的误判。比如「真是太好了,用了三天就坏了」,SnowNLP 大概率给高分。我的处理方式是在情感分析前加一层预处理:维护一个反讽触发词表(「呵呵」「真是」「太好了」配合负面结果词),命中后直接降权。另外,把评论里的网络用语做一次归一化映射,比如「yyds」映射为「很好」,「绝绝子」映射为「很好」,「拉胯」映射为「很差」,再送入模型。这一步能提升 5 到 8 个百分点的准确率。
4. 可视化怎么做:从词云到情感趋势图的落地细节
4.1 词云不是越花越好
词云是最直观的展示方式,但很多人做出来的词云全是「不错」「可以」「东西」这种无意义高频词。问题出在分词和停用词处理上。用 jieba 分词后,必须过滤掉停用词表,还要把「京东」「快递」「物流」这类和商品本身无关的词单独处理。我一般会做两版词云:一版是全部评论的词云,看整体关键词;另一版是只取负面评论做词云,看用户到底在抱怨什么。负面词云往往比正面词云有价值得多。
import jieba from wordcloud import WordCloud import matplotlib.pyplot as plt def generate_wordcloud(comments, label_filter=None, filename="wordcloud.png"): """ 生成词云 label_filter: None=全部, '正面'=只取正面, '负面'=只取负面 """ text = "" for c in comments: if label_filter and c.get("label") != label_filter: continue text += c["content"] + " " # 停用词表,实际使用时建议从文件加载更完整的列表 stopwords = {"的", "了", "是", "我", "你", "他", "她", "它", "在", "有", "和", "就", "不", "也", "都", "还", "京东", "快递", "物流", "东西", "可以", "不错"} words = jieba.cut(text) filtered = " ".join(w for w in words if w not in stopwords and len(w) > 1) wc = WordCloud( font_path="simhei.ttf", # 中文字体路径,必须指定 width=1200, height=800, background_color="white", max_words=150, colormap="viridis" ) wc.generate(filtered) wc.to_file(filename) print(f"词云已保存: {filename}") if __name__ == "__main__": with open("comments_analyzed.json", "r", encoding="utf-8") as f: data = json.load(f) generate_wordcloud(data, label_filter="负面", filename="negative_wc.png")font_path是中文字体的坑,不指定的话词云全是方块。Windows 下可以用C:/Windows/Fonts/simhei.ttf,Linux 下需要自己下载一个中文字体文件放到项目目录。
4.2 用 pyecharts 做情感趋势和评分分布
词云看关键词,趋势图看时间变化。把评论按日期聚合,计算每天的平均情感分数,用折线图展示,能看出某个时间点之后口碑是否下滑。pyecharts 生成的图表可以直接嵌入 HTML,适合做可视化大屏。
from pyecharts.charts import Line, Pie from pyecharts import options as opts from collections import defaultdict def sentiment_trend(comments): """按日期聚合情感分数,生成折线图""" daily = defaultdict(list) for c in comments: date = c.get("time", "")[:10] # 取日期部分 if date and "sentiment" in c: daily[date].append(c["sentiment"]) dates = sorted(daily.keys()) avg_scores = [round(sum(daily[d]) / len(daily[d]), 3) for d in dates] line = ( Line() .add_xaxis(dates) .add_yaxis("平均情感分", avg_scores, is_smooth=True) .set_global_opts( title_opts=opts.TitleOpts(title="评论情感趋势"), yaxis_opts=opts.AxisOpts(min_=0, max_=1) ) ) line.render("sentiment_trend.html") print("趋势图已生成: sentiment_trend.html") def score_distribution(comments): """评分分布饼图""" dist = defaultdict(int) for c in comments: dist[c.get("score", 0)] += 1 pie = ( Pie() .add("", [(f"{k}星", v) for k, v in sorted(dist.items())]) .set_global_opts(title_opts=opts.TitleOpts(title="评分分布")) ) pie.render("score_dist.html") print("评分分布图已生成: score_dist.html")is_smooth=True让折线更平滑,适合展示趋势。yaxis_opts把 Y 轴固定在 0 到 1,避免不同商品之间对比时尺度不一致。评分分布饼图能快速看出这个商品是好评为主还是差评扎堆。
4.3 可视化大屏的组装思路
如果你要做成一个可视化大屏,常见做法是用 Flask 或 FastAPI 起一个轻量服务,把 pyecharts 生成的 HTML 嵌入页面,再用 Grid 布局把词云、趋势图、评分分布拼在一起。不需要上 ECharts 全套,pyecharts 已经够用。关键是数据要提前算好存成 JSON,前端只负责渲染,不要在请求时实时跑情感分析。
5. 避坑指南:评论爬取与情感分析中翻过的那几次车
5.1 抓了 200 条就返回空数据
现象:前几页正常返回,翻到第 10 页左右开始返回空 JSON 或comments数组为空。原因:京东评论接口对未登录请求有翻页深度限制,通常只能翻到前 10 到 20 页。解决:降低翻页深度,或者按score参数分多次抓取(先抓好评、再抓中评、再抓差评),每次都在深度限制内。另一个办法是换sortType,不同排序方式下的翻页限制可能不同。
5.2 SnowNLP 对短评论一律给 0.5
现象:大量「好」「不错」「可以」这种两三个字的评论,情感分数全部接近 0.5,无法区分。原因:SnowNLP 基于贝叶斯模型,短文本特征太少,模型无法给出有区分度的分数。解决:对长度小于 5 个字的评论,不走模型,直接用规则词典判断。维护一个正面短词表和负面短词表,命中即给对应标签。
5.3 词云中文显示为方块
现象:生成的词云图片里所有中文都是方框。原因:WordCloud 默认字体不支持中文。解决:在WordCloud()里显式指定font_path参数,指向一个中文字体文件。Windows 用simhei.ttf,Mac 用/System/Library/Fonts/PingFang.ttc,Linux 需要自己下载字体文件。
5.4 情感分析结果和评分严重不一致
现象:五星好评的评论被 SnowNLP 判为负面,一星差评被判为正面。原因:京东的评分是用户手动选的,和评论文本的实际情感可能不一致。用户可能因为物流快给了五星,但评论内容在吐槽产品。解决:把评分和文本情感分开看,不要用评分去验证情感分析的准确率。真正需要关注的是「评分高但文本情感低」的评论,这些往往藏着真实问题。
5.5 抓取频率过高导致 IP 被临时限制
现象:运行一段时间后所有请求返回 403 或超时。原因:请求频率过高,触发了京东的风控机制。解决:把time.sleep调到 2 到 3 秒,并且不要连续抓多个商品。如果已经被限制,等 30 分钟到 1 小时再试。长期方案是控制单次抓取量,分时段执行。
6. 让分析结果真正可用:情感分数校准与交叉验证的一个技巧
跑通全流程只是第一步,真正决定这套东西有没有价值的是情感分析的准确率。我踩过最大的坑是:SnowNLP 跑出来的正面比例永远在 80% 以上,看起来一片大好,但实际翻评论发现差评被大量误判。后来我固定了一个校准习惯:每次分析完,随机抽 50 条评论,人工标注真实情感,和模型结果做对比,算一个混淆矩阵。如果负面评论的召回率低于 60%,就说明模型对这个品类不适用,需要换方案或者加规则。
具体操作上,我会把 SnowNLP 的分数和评分做一个交叉表。评分 1 到 2 星但情感分数大于 0.6 的评论,单独拉出来看,这些就是模型误判的重灾区。针对这些误判样本,提取高频词,补充到规则词典里。比如发现「发热」「卡顿」「掉电」这些词在差评里高频出现但 SnowNLP 给了高分,就把它们加入负面词典,在模型分数基础上做减分修正。
def calibrate(comments, negative_words): """ 对 SnowNLP 结果做规则校准 negative_words: 负面词典集合 """ for c in comments: text = c["content"] hit = sum(1 for w in negative_words if w in text) if hit >= 2: # 命中两个以上负面词,情感分打七折 c["sentiment"] = round(c["sentiment"] * 0.7, 4) c["label"] = "负面" if c["sentiment"] < 0.4 else c["label"] return comments negative_words = {"发热", "卡顿", "掉电", "退货", "垃圾", "差评", "后悔", "翻车", "虚假", "欺骗", "破损"}这个校准逻辑很简单,但实测能把负面评论的召回率从 55% 拉到 75% 左右。代价是可能把一些正面评论误判为负面,所以hit >= 2这个阈值不要调太低,命中一个词就降权容易误伤。
另一个习惯是:每次换品类(比如从手机换到食品),都要重新抽检校准。手机评论里的「发热」是负面,食品评论里的「发热」可能只是描述加热即食,同一个词在不同品类的情感极性完全不同。我一般会为每个品类单独维护一份负面词典,不混用。
最后说一个验证方法:把情感分析结果和评分分布做对比。如果评分分布是 70% 五星、10% 一星,但情感分析结果是 85% 正面、5% 负面,两者差距超过 15 个百分点,就说明情感分析有问题,需要回头检查。这个交叉验证不需要额外标注数据,用现成的评分就能做,是我每次跑完流程必看的一个指标。希望帮到你。
本文还有配套的精品资源,点击获取