☰
京东评论爬虫与SnowNLP情感分析:从数据采集到可视化看板
2026/10/3 4:47:05 网站建设 项目流程

简介:面向高校计算机、人工智能、电子信息及相关专业学生的京东商品评论分析毕业设计项目,完整覆盖爬虫采集、文本情感分析与可视化展示全流程,既能用于课程设计、期末作业,也适合作为毕业设计或项目原型演示。压缩包共113个文件,大小约55.81兆字节;核心为15个Python源码文件,配套37个CSV评论数据集、文本情感分析模型及权重文件、8张PNG与30张JPG可视化图表、HTML结果页、系统设计文档、使用说明和浏览器驱动等,下载解压后即可按文档快速部署运行。目前已有81人学习下载。项目代码经过严格测试,功能稳定;借助内置的中文商品评论、京东商品评论等数据集,可快速跑通“评论采集—情感分类—图表展示”完整链路,还能在此基础上扩展其他电商平台或引入更丰富的情感分析算法,适合作为进阶实战的起点。

1. 京东评论爬虫与分析:从毕设选题到数据洞察一套走通

很多人的第一个 Python 实战项目是从电商评论开始的,因为它在爬虫、文本处理、数据入库和可视化四个环节都有硬骨头,一个项目练完四个方向的能力。这套京东评论分析系统刚好是把四块拼到一起的完整方案:用 requests 直连京东评论接口抓取数据,用 SnowNLP 给每条评论打情感分,把结果写进 MySQL,最后用 Echarts 渲染成词云、情感分布和时间趋势图。适合正在做 Python 方向毕业设计的学生,也适合想低成本验证“用户到底在吐槽什么”的产品和运营同学。你拿到手就能把一个商品的上千条评论自动变成正面率、负面高频词和口碑变化趋势,这套系统的价值不在算法多深,而在于数据链路完整——即便你只想复现其中爬虫或者情感分析一环,也能直接借走对应的代码模块,不踩重复的坑。

2. 爬虫采集层:requests 直连京东评论接口,翻页与字段提取一次跑通

2.1 评论接口的请求结构与参数含义

京东商品评论不是一个需要复杂模拟浏览器渲染的动态页面,它有一个直连的 HTTP 接口,返回 JSONP 格式数据。接口地址固定,参数含义清晰:

参数含义取值
productId商品编号见商品详情页 URL 中 10000 开头的数字
score评论类型筛选0=全部,1=好评,2=中评,3=差评,4=追评,5=晒图
sortType排序5=默认,6=按时间
page页码从 0 开始,每页最多 10 条
pageSize每页条数一般取 10
isShadowSku是否套装商品0=普通,1=套装
fold评论折叠0=展开,1=折叠

对比网页源码里的隐藏数据,京东评论接口的玩法和豆瓣、淘宝都不一样。它不需要登录态,也不用处理复杂加密参数,只要把 Referer 和 UA 带上,响应就能稳定返回。但也别高兴太早,这个接口对参数拼写非常敏感,缺一个 score 参数会直接导致后端返回的评论类型不完整,sortType 写错会出现时间乱序。所以把参数掰开揉碎讲清楚,是这一步最重要的功课。

这里最容易忽略的是 isShadowSku。京东很多自营商品的主 SKU 不是真正挂评论的 SKU,如果直接拿详情页 ID 请求,返回的 comments 是空的。套装商品必须把 isShadowSku 设为 1 才能拿到评论。这个参数在别的爬虫教程里很少特意讲,但实际跑的时候栽跟头概率很高。

2.2 构造请求并解析 JSONP 回包

接口默认返回 JSONP 格式,形如fetchJSON_comment98({...})。直接调用 resp.json() 会报错,需要把回调包裹去掉再解析,或者在请求时把 callback 参数置空,让服务端返回纯 JSON。我一般用后者,省一步字符串处理:

import requests url = "https://club.jd.com/comment/productPageComments.action" headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "Referer": "https://item.jd.com/100012043978.html", "Connection": "keep-alive", } params = { "callback": "", "productId": "100012043978", "score": 0, "sortType": 5, "page": 0, "pageSize": 10, "isShadowSku": 0, "fold": 1, } resp = requests.get(url, headers=headers, params=params, timeout=10) print(resp.status_code) data = resp.json() comments = data.get("comments", []) for c in comments: print(c.get("content"), c.get("score"), c.get("creationTime"))

代码逻辑说明:这段代码的关键不是复杂,而是参数的完整度。Referer 指向商品详情页,用来模拟从详情页跳转过来的访问行为;callback 置空避免解析 JSONP 包裹;score=0 拉取全部评论类型,sortType=5 使用默认排序,保证评论列表能翻页取全。timeout 设为 10 秒,防止某个慢请求把整个爬虫卡死。

实际运行时,比较常见的现象是 status_code 返回 200 但 comments 为空。这种情况要先看返回的 JSON 里有没有 error 字段,然后再检查 isShadowSku 和 productId 是不是真的对应。反应速度快的话,直接浏览器打开评论接口的 URL,把参数手动替换一遍看浏览器返回什么,能帮你在爬虫和接口之间快速定位问题。

2.3 多页面遍历、随机延迟与数据落盘

单页只有 10 条评论,一个上千评论的商品需要翻很多次。翻页逻辑很简单,page 从 0 递增,直到返回的 comments 为空或抓到目标条数为止。但这里有两个细节值得注意:一是京东评论页最多只能看到前几百条,超过一定页数后返回的会是空数组,所以抓取量级要提前有预期;二是请求频率要控制,否则高速模式下被拦的概率会快速上升。

import time import random import json def fetch_all_comments(product_id, pages=50): base_url = "https://club.jd.com/comment/productPageComments.action" headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; x64)", "Referer": f"https://item.jd.com/{product_id}.html", } all_comments = [] for page in range(pages): params = { "callback": "", "productId": product_id, "score": 0, "sortType": 5, "page": page, "pageSize": 10, "isShadowSku": 0, "fold": 1, } try: resp = requests.get(base_url, headers=headers, params=params, timeout=10) resp.raise_for_status() data = resp.json() comments = data.get("comments", []) except Exception as e: print(f"第 {page} 页请求失败: {e}") continue if not comments: print(f"第 {page} 页无数据,提前结束") break for c in comments: all_comments.append({ "id": c.get("id"), "content": c.get("content", ""), "score": c.get("score"), "time": c.get("creationTime"), "nickname": c.get("nickname", ""), "product_color": c.get("productColor", ""), "product_size": c.get("productSize", ""), }) time.sleep(random.uniform(1, 3)) return all_comments if __name__ == "__main__": comments = fetch_all_comments("100012043978", pages=20) print(f"抓取评论数: {len(comments)}") with open("jd_comments.json", "w", encoding="utf-8") as f: json.dump(comments, f, ensure_ascii=False, indent=2)

逻辑说明:这里把请求、解析、结构化整个封装成一个函数。id 字段特意保留下来,后续入库去重会用到,这是很多人忽略的一步。random.uniform(1, 3) 的延迟让请求间隔在 1 到 3 秒之间波动,避免掉进固定的请求规律。pages 参数控制抓取上限,经验值是 50 页封顶。每条评论只保留 content、score、time、nickname 和规格属性字段,够后续做情感分析和维度拆解用。最后落盘成 JSON 文件,方便在还没配置数据库的时候先验证爬虫和情感分析两个模块。

参数调整建议:如果你只想看差评,把 score 改为 3;想按时间排序,sortType 改为 6。pageSize 保持 10 就行,不用强行改成更大的值——接口本身对每页数量有上限限制,改大了反而容易触发异常返回。

3. 文本情感分析:SnowNLP 打分、清洗规则与正负面阈值调优

3.1 中文短文本情感分析方法选型

评论分析的核心是把一条条中文短文本量化成可统计的情感分数。常见做法有三种:基于情感词典打分、基于机器学习模型、基于预训练大模型。词典法效果好但需要人工维护词表,大模型精度高但在批量处理上千条评论时效率不划算;SnowNLP 是面向中文短文本的朴素贝叶斯情感分析库,开箱即用,单个短文本打分速度在毫秒级,非常适合批量处理场景。

SnowNLP 对中文支持比较完整,底层用字符 n-gram 做特征,再通过训练好的朴素贝叶斯模型输出情感概率。sentiment 属性返回 0 到 1 之间的分数,越接近 1 表示越正面,越接近 0 表示越负面。它内置的模型是基于电商购物评论训练的,所以对京东这种商品评价数据有天然的适配度,这也是我选择它而不是直接用一个通用情感词典的原因。

需要提醒的是,SnowNLP 的模型不是为所有场景准备的。把它拿到新闻文本、影评或者社交媒体短句上,准确率会明显下降。在这个系统里,它的角色定位就是处理京东商品评论,超出这个范围请自己重新评估。

3.2 数据清洗、保留边界标点与停用词策略

评论里的内容并不都是规整的文本,常见的干扰包括:符号堆叠、纯数字评价、商家回复模板、表情符号。清洗规则要做三件事:去掉 URL 和 @用户,把全角标点统一成半角,过滤长度小于 5 个字符的评论。注意,不要把标点删除得干干净净,因为 SnowNLP 在分词时依赖标点切分句子边界,把所有的逗号和句号删掉会拖累分词质量,导致情感分数失真。

import re from snownlp import SnowNLP def clean_text(raw): text = re.sub(r"https?://\S+", "", raw) text = re.sub(r"@\S+", "", text) text = re.sub(r"[【】\[\]()()]", "", text) text = text.strip() return text def sentiment_score(text): text = clean_text(text) if len(text) < 5: return None s = SnowNLP(text) return s.sentiments

逻辑说明:clean_text 负责去掉噪声字符,但保留了逗号句号这类边界标点。sentiment_score 在清洗后做长度过滤,少于 5 个字的评论在情感判定上置信度太低,直接返回 None,后续统计时排除。这个过滤阈值可以根据数据调整,如果商品评论以“不错”“很好”这种短评为主,可以把阈值降到 3。

参数说明:SnowNLP 对输入文本长度没硬性限制,但输入过长时打分速度会明显变慢。批量处理时,我一般把每条评论截断到 200 字以内,足够覆盖大多数京东评论的场景,又能控制整体耗时。

停用词的处理在评论场景里要格外小心。通用中文停用词表里有大量虚词,比如“的”“了”“和”,这些词在情感分析打分时本来就不会贡献太多权重,删与不删对 SnowNLP 的分数影响不大。真正值得过滤的是品牌词和商品型号——比如“小米”“iPhone 15”“JD 物流”,它们在所有评论里高频出现,把情感词的注意力稀释了。做法是自己维护一份补充停用词表,只在这个商品的分析范围内生效,不要直接套用到所有商品上,否则换品类时反而会误删有效特征。

3.3 正负面判定:固定阈值、动态分位数与关键词修正

拿到每条评论的 sentiment 分数之后,需要把 0-1 的连续值映射成“正面 / 中性 / 负面”三个类别。最直接的做法是固定阈值:0.6 以上正面,0.4 以下负面,中间是中性。但在实际数据里,分数分布往往严重偏斜,大量评论集中在 0.5 附近,固定阈值会造成几乎所有评论都被判成中性。

一个更稳妥的做法是先用样本跑一遍分布,再根据分位数动态确定阈值。比如画一条 sentiment 分数的直方图,观察中位数和上下四分位,再决定 cutoff:

import pandas as pd df = pd.DataFrame(comments) df["sentiment_score"] = df["content"].apply(sentiment_score) df = df.dropna(subset=["sentiment_score"]) q30 = df["sentiment_score"].quantile(0.3) q70 = df["sentiment_score"].quantile(0.7) print(f"30% 分位数: {q30:.3f}, 70% 分位数: {q70:.3f}") df["sentiment_label"] = pd.cut( df["sentiment_score"], bins=[-1, q30, q70, 2], labels=["负面", "中性", "正面"], ) print(df["sentiment_label"].value_counts())

逻辑说明:这里用 pandas 先给所有评论打上情感分数,再算 30% 和 70% 分位数作为动态阈值,最后用 pd.cut 把连续分数切成三段。相比固定阈值,这种方式能适应不同品类的数据分布——有些品类差评集中,有些品类好评集中,动态阈值不会出现整批评论全是中性的尴尬局面。

参数说明:30% 和 70% 分位数不是唯一选择,如果你希望正面和负面样本更极端,可以改用 20% 和 80%。我一般建议至少保留 20% 的中性区间,否则分类结果会带上太多噪声。

同时还需要配合一个简单的关键词修正机制。评论区里出现“差劲”“垃圾”“退货”“客服没人理”等强负面词时,即使 SnowNLP 分数偏高,也应当人工拉低。同理,出现“超值”“惊艳”“回购”等强正面词时,人工拉高。关键词修正本质上是对模型在长难句上误判的兜底:

strong_negative = ["差劲", "垃圾", "退货", "退款", "客服不理", "质量问题", "坏"] strong_positive = ["超值", "惊艳", "回购", "强烈推荐", "完美"] def adjust_score(text, score): for kw in strong_negative: if kw in text: return min(score, 0.2) for kw in strong_positive: if kw in text: return max(score, 0.8) return score

逻辑说明:这个修正函数在情感分数打完之后执行,规则很简单:命中强负面词,分数强行压到 0.2 以下;命中强正面词,强行抬到 0.8 以上。这样即使 SnowNLP 误判了一个带“退货”的长句,最终结果也能被拉回来。词表不需要很大,覆盖高频强情绪词即可,词表越大误伤概率越高。

4. 避坑排查:反爬拦截、乱码和情感分数失效的五个实战记录

这几条踩坑记录不是从文档里抄的,是我跑这个系统时真实遇到并记录下来的问题。每一条都按“现象 → 原因 → 解决”的顺序写,你看的时候可以先跳到最像自己当前处境的那一条。

4.1 现象:返回 200 但 comments 一直是空数组

爬虫跑起来没有报错,status_code 是 200,但解析出来的 comments 列表始终是空。打印 resp.text 发现返回的是一个包含空数组的结构。

原因:多半是 productId 和 isShadowSku 不匹配。京东的商品分成普通单品和套装商品两类,套装商品的评论挂在子 SKU 上,使用详情页主 ID + isShadowSku=0 的组合拿不到评论。另外,直接复制商品详情页 URL 中的 ID 时,偶尔会复制成带问号参数的跳转 ID,也会导致同样的现象。

解决:先用 isShadowSku=1 做一次快速测试。如果还是空,就换一个商品详情页的纯数字 ID 试试。实在不行,用商品搜索页的结果对比确认真正的 productId 后再跑。

4.2 现象:同一批评论反复出现,条数越抓越多

翻页的时候,前面几页的评论在后面页里又重新出现,最后统计时发现重复评论占比超过 20%。

原因:京东评论接口在 sortType=5 的默认排序下,评论顺序并不稳定,翻页过程中新评论插入会把旧评论挤到后面的页码,导致同一评论被多次抓到。使用 sortType=6 按时间排序可以部分缓解,但并发请求时依然可能重复。

解决:入库前做去重,以评论的 id 字段作为去重键。京东评论接口返回的每条 comment 都带一个全局唯一的 id,爬取时把这个字段单独存下来,写入 MySQL 时加 unique 索引,重复评论靠数据库层面拦截。

4.3 现象:SnowNLP 分数集中在 0.5,所有评论判成中性

清洗后跑了一批数据,发现 sentiment_score 几乎全部落在 0.5 附近的正负 0.05 区间里,正负面数量均为 0,整个情感分析环节等于没做。

原因:这是 SnowNLP 的已知习性。它在处理文本长度适中、情感表达不极端的评论时,后验概率容易趋近 0.5。另一个常见诱因是清洗过度——把逗号句号全部删掉之后,分词器分不出句子边界,概率输出被严重平滑。还有可能是补充停用词表误把“很好”“不错”这类形容词删掉,导致模型拿到的是“物流”“发货”这种中性词。

解决:清洗阶段保留标点,把长度过滤阈值收窄;情感打分之后用分位数而不是固定阈值做分类,把中性区间控制在 20% 左右。如果仍然大面积扎堆 0.5,说明这批评论本身以中性描述为主,可以考虑引入关键词修正机制来区分。

4.4 现象:MySQL 写入中文全部乱码

爬虫逻辑正常,数据也拿到本地了,但在 Navicat 里一看,评论内容全是问号或者乱码字符串。

原因:MySQL 表默认字符集不是 utf8mb4,而 Python 端写入时又把字符串编码搞混了。京东接口返回的 content 是 UTF-8 编码的中文,连接 MySQL 时没有显式指定 charset,导致两边字符集不一致。

解决:建表时统一指定 utf8mb4,pymysql.connect 里显式声明 charset="utf8mb4"。注意 utf8mb4 和 utf8 不是同一个东西,emoji 和特殊符号必须用 utf8mb4 才能存进去,否则碰到表情符号又要再踩一遍乱码坑。

import pymysql conn = pymysql.connect( host="localhost", user="root", password="your_password", database="jd_analysis", charset="utf8mb4", )

逻辑说明:这段连接配置的关键在于 charset 参数必须和建库建表时保持一致。如果你的库已经建好了但字符集是 latin1,需要先修改库本身的字符集,再改连接,不然连接串指定了 utf8mb4 也白搭。检查库表字符集用SHOW CREATE TABLE jd_comments;看 DEFAULT CHARSET 那一行。

4.5 现象:Echarts 看板数字明显比实际评论数少

可视化看板渲染完成,饼图和折线图都能出图,但把爬虫落库的条数和图表展示的总数一对,发现差了将近三分之一。

原因:写 SQL 时用了错误的过滤条件;或者是清洗过程中 sentiment_score 为 None 的评论被全部剔除,导致样本量骤减。另一个容易被忽略的来源是去重逻辑生效太晚,重复评论在入库后被计数了两次,图表展示时却按去重后的条数渲染,两个数字自然对不上。

解决:建立一条完整的校验链路——爬虫结束先打印“抓取 N 条 / 去重后 M 条”,入库后再用 SQL 的COUNT(DISTINCT comment_id)核对落库数量,前端渲染的原始数据表来自同一张表,确保口径一致。

5. 验证与进阶:用人工标注集校准情感分,让分析结果更可信

这个系统的价值不只在于“能跑通”,更在于分析结果能不能支撑结论。毕业设计答辩时,评委常问的问题是:你的情感分析准确率有多少?如果不做校准,这个答案只能停留在“SnowNLP 是知名库,结果应该可信”的水平。我的做法是:随机抽样 100 条评论,人工标注正/负/中性,再和模型分类结果做对比,算出准确率和混淆矩阵。

from sklearn.metrics import accuracy_score, confusion_matrix y_true = ["正面", "负面", "负面", "正面", "中性"] # 人工标注 y_pred = ["正面", "负面", "中性", "正面", "中性"] # 模型分类 acc = accuracy_score(y_true, y_pred) conf = confusion_matrix(y_true, y_pred) print(f"准确率: {acc:.2f}") print(conf)

抽样人工标注是一道细活。我一般先把评论按商品评分分层,好评里抽 40 条,中差评里抽 60 条,避免标注内容集中在单一声量上。标注完后计算准确率,如果低于 0.8,就去检查被误判的样本,看是阈值切错还是关键词修正误伤,然后调阈值重跑。

人工标注模型判定正面模型判定中性模型判定负面
正面(40)3352
中性(30)8193
负面(30)2424

上面这张表能直观地看出模型在哪些类别上容易混淆。如果中性样本大量被分到正面,说明阈值偏低;如果负面样本被大量分到中性,说明关键词修正强度不够。调整逻辑很简单:改阈值分位数,或修改强负面词表,然后重新跑校准,两三轮之后准确率通常能回到 0.85 以上。

再进阶一步,情感分数校准之后,可以多做两个分析方向:一是按商品评分和情感分数做交叉验证,能看到用户打分与实际评论情感是否一致,比如打一星但评论内容是正面的用户,可能是误点;二是按时间维度聚合情感分数,画出情感趋势线,观察某次促销活动或负面事件爆发时分数是否出现明显拐点。

从那以后,我每次跑完一批评论数据,都会先抽样看 10 条原始评论和对应的情感分数,确认阈值和关键词修正没有跑偏,再决定要不要把数据送进可视化看板。这个习惯帮我避开了不少“模型说很好、用户实际在骂”的翻车现场。希望帮到你。

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

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

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

立即咨询