
简介面向高校计算机、人工智能、电子信息及相关专业学生的京东商品评论分析毕业设计项目完整覆盖爬虫采集、文本情感分析与可视化展示全流程既能用于课程设计、期末作业也适合作为毕业设计或项目原型演示。压缩包共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每页条数一般取 10isShadowSku是否套装商品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, headersheaders, paramsparams, timeout10) 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 包裹score0 拉取全部评论类型sortType5 使用默认排序保证评论列表能翻页取全。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, pages50): base_url https://club.jd.com/comment/productPageComments.action headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; x64), Referer: fhttps://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, headersheaders, paramsparams, timeout10) 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, pages20) print(f抓取评论数: {len(comments)}) with open(jd_comments.json, w, encodingutf-8) as f: json.dump(comments, f, ensure_asciiFalse, indent2)逻辑说明这里把请求、解析、结构化整个封装成一个函数。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(rhttps?://\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 分数的直方图观察中位数和上下四分位再决定 cutoffimport 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(f30% 分位数: {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 isShadowSku0 的组合拿不到评论。另外直接复制商品详情页 URL 中的 ID 时偶尔会复制成带问号参数的跳转 ID也会导致同样的现象。解决先用 isShadowSku1 做一次快速测试。如果还是空就换一个商品详情页的纯数字 ID 试试。实在不行用商品搜索页的结果对比确认真正的 productId 后再跑。4.2 现象同一批评论反复出现条数越抓越多翻页的时候前面几页的评论在后面页里又重新出现最后统计时发现重复评论占比超过 20%。原因京东评论接口在 sortType5 的默认排序下评论顺序并不稳定翻页过程中新评论插入会把旧评论挤到后面的页码导致同一评论被多次抓到。使用 sortType6 按时间排序可以部分缓解但并发请求时依然可能重复。解决入库前做去重以评论的 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导致两边字符集不一致。解决建表时统一指定 utf8mb4pymysql.connect 里显式声明 charsetutf8mb4。注意 utf8mb4 和 utf8 不是同一个东西emoji 和特殊符号必须用 utf8mb4 才能存进去否则碰到表情符号又要再踩一遍乱码坑。import pymysql conn pymysql.connect( hostlocalhost, userroot, passwordyour_password, databasejd_analysis, charsetutf8mb4, )逻辑说明这段连接配置的关键在于 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 条原始评论和对应的情感分数确认阈值和关键词修正没有跑偏再决定要不要把数据送进可视化看板。这个习惯帮我避开了不少“模型说很好、用户实际在骂”的翻车现场。希望帮到你。本文还有配套的精品资源点击获取