京东评论爬虫与情感分析:Python全流程实践
简介基于Python的京东商品评论爬取与情感分析可视化研究资料包聚焦电商评论数据的完整处理链路。资源面向Python爬虫与数据分析初学者以及需要完成课程设计或毕业设计的开发者覆盖从京东平台评论自动抓取、原始数据清洗与去重到朴素贝叶斯、支持向量机等机器学习情感分类模型构建再到以饼图、折线图、词云图等展示情感倾向与趋势的完整项目流程。压缩包共20个文件约5.87MB主要包含Python脚本爬虫、训练与情感分析、CSV格式的原始评论、清洗后数据与最终结果数据、说明文档、情感词典文本、依赖配置文件及可视化图片等文件类型涵盖py、csv、txt、md、jpg等目录结构清晰便于按阶段对照学习。目前已有76人学习适合作为课程作业、项目实战或入门自然语言处理的参考资料。1. 从京东评论区到情感曲线一套Python爬虫资源包能跑通什么做电商数据分析的人迟早会碰上一个问题想拿真实商品评论做情感分析却连数据都搞不定。京东评论不像豆瓣那样直接渲染在HTML里它走的是独立JSON接口带登录态和风控校验手动复制几页就够折腾半天。这套以Python爬虫为主线的资源包恰好把评论采集、数据清洗、文本情感分析、可视化出图整条链路串了起来。包里既有抓评论的jd_comment.py也有训练情感模型的train.py、做批量预测的sentiment_analysis.py还附带已经跑好的jd_comment.csv、processed_comment_data.csv和result.csv三份数据以及positive.txt、negative.txt两份情感词库。适合正在做课程设计、毕业设计或者刚入门NLP想拿真实电商数据练手的人。这个包是网络分享的学习资源版权归原作者所有别拿去做商业用途就行。2. 京东评论爬虫接口参数拆解、Cookie伪装与翻页去重2.1 评论区JSON接口的参数拆解京东的评论数据不在商品详情页的HTML源码里而是由前端异步请求一个独立接口。常见做法是请求 club.jd.com 下的 productPageComments.action传入商品ID和分页参数返回一段JSON。这个接口是评论区唯一值得关心的数据源掌握了它就不需要去解析那些堆满DOM节点的页面。接口的核心参数包括商品IDproductId、评分筛选score、排序方式sortType和页码page。score0表示全部评价1到3分别对应差评、中评、好评sortType5表示按时间倒序6表示按推荐排序。page从0开始计数每页pageSize条常见是10条。还有一个fold参数控制是否需要折叠无意义内容一般设为1即可。这个项目里有个online_shopping_10_cats.csv存放了十个分类下的商品ID和名称爬虫脚本就是从这个文件里读取商品ID再逐个去请求评论接口。我在复现时整理的请求代码基本可以直接套用import requests import time def fetch_comments(product_id, page0, page_size10): url https://club.jd.com/comment/productPageComments.action params { productId: product_id, score: 0, # 0全部, 1差评, 2中评, 3好评 sortType: 5, # 5按时间倒序, 6按推荐 page: page, # 页码从0开始 pageSize: page_size, isShadowSku: 0, fold: 1 } headers { User-Agent: (Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36), Referer: fhttps://item.jd.com/{product_id}.html } resp requests.get(url, paramsparams, headersheaders, timeout10) resp.raise_for_status() return resp.json()这里有几个参数值得注意pageSize虽然可以调大但服务端对单次返回条数有限制设成20甚至50不一定每次都灵我一般保持10靠减少单页数据量来降低触发风控的概率。Headers里的Referer必须写成商品详情页地址否则部分请求会落到风控策略上。判断是否被风控看返回JSON里是否出现captcha或verify字段或者comments直接为空这时就不是页码翻完了而是被拦了。返回的JSON里comments字段是当页评论列表maxPage告诉你能翻到多少页还有productCommentSummary之类的汇总信息。写爬虫时优先解析comments不要盯着整个JSON硬啃很多字段是给前端UI用的对我们没有意义。网上很多教程一上来就教你解析整个页面这套路在京东这里行不通因为评论内容全是异步加载的静态页面里只能拿到空洞的骨架。2.2 Cookie伪装与请求频率控制京东的评论接口对未登录用户的可用性有限翻到后面几页经常返回空列表。我复现这个项目时遇到的情况是前10页正常第11页开始comments为空maxPage却显示还有几十页。这不是数据真的没有而是服务端根据登录态和IP维度做了限制。最省事的做法是把浏览器里登录后的Cookie复制到代码里用requests.Session维护。Cookie中真正起作用的主要是那几个和用户身份相关的字段比如登录后的ticket其他字段缺一两个一般不影响。但Cookie有过期时间放几天再用就会失效需要重新从浏览器复制。不要指望一次复制能撑到大作业答辩数据量大的话建议每次跑之前都刷新一遍。import requests import random session requests.Session() session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Cookie: 这里粘贴浏览器复制出来的完整Cookie字符串 }) def fetch_with_retry(product_id, page, max_retries3): for attempt in range(max_retries): try: return fetch_comments(product_id, page) except Exception: time.sleep(5 * (attempt 1)) return None笨办法往往最可靠。登录之后能爬的页数会显著增加但并不是无限常见是100页以内也就是说一个商品最多能拿1000条。如果你的研究需要更多数据要么把score参数切换成好评、差评、中评分开抓要么拆成多个时间窗口去翻两种方式都能绕过单次查询的页数限制代价是代码里要多套一层循环。请求频率控制在1到3秒之间随机sleep不要用固定间隔。固定间隔很容易被识别成脚本行为随机间隔至少能把特征抹平一些。很多人把“爬得快”当目标实际上在这个场景下“爬得稳”才是核心诉求一小时内抓完和一下午抓完结果数据质量差很多。2.3 翻页循环、去重与CSV落盘翻页循环是爬虫脚本的主旋律但翻页不等于无脑请求。把这个项目里的jd_comment.py跑通后你会发现它在翻页循环里做了两件额外的事断点续爬和去重。断点续爬解决的是爬了一半程序崩了怎么办的问题去重解决的是重复抓取同一批评论的问题。import pandas as pd import os seen set() if os.path.exists(jd_comment.csv): df_old pd.read_csv(jd_comment.csv, encodingutf-8-sig) seen set(zip(df_old[product_id], df_old[nickname], df_old[content])) all_rows [] for page in range(0, 50): data fetch_comments(product_id, page) for c in data.get(comments, []): key (product_id, c.get(nickname), c.get(content)) if key in seen: continue seen.add(key) all_rows.append({ product_id: product_id, nickname: c.get(nickname, ), content: c.get(content, ), score: c.get(score, 0), time: c.get(creationTime, ), like_count: c.get(usefulVoteCount, 0), reply_count: c.get(replyCount, 0) }) if not data.get(comments): break time.sleep(random.uniform(1, 3)) df pd.DataFrame(all_rows) if os.path.exists(jd_comment.csv): df.to_csv(jd_comment.csv, modea, headerFalse, indexFalse, encodingutf-8-sig) else: df.to_csv(jd_comment.csv, indexFalse, encodingutf-8-sig)这里的去重键是三元组商品ID、用户昵称、评论文本。为什么不用评论ID因为这个接口返回的每条评论确实有id字段但项目里抓过的一些数据中部分评论的id是重复的用三元组反而更稳。nickname加content能区分绝大多数场景同一用户对同一商品发两条完全相同的评论几乎不可能存在。CSV落盘的编码用utf-8-sig而不是utf-8目的是让Excel直接打开不出现乱码。Python自带的csv模块写中文有时候会踩编码坑用pandas的to_csv配上utf-8-sig是最稳妥的组合。文件追加模式要注意headerFalse否则第二次运行会在文件中间多插一行列名后续read_csv时就会出现错位。跑完这个脚本你会得到一份jd_comment.csv通常包含几千到上万条原始评论字段有product_id、nickname、content、score、time、like_count、reply_count。到这里爬虫部分就结束了接下来的清洗环节才是决定情感分析效果的分水岭。3. 数据清洗流水线四类脏数据和处理规则的落地3.1 原始评论里的脏数据长什么样爬下来的评论数据比想象中脏得多。HTML标签、转义符号、emoji、广告链接、无意义字符串全部混在一起。京东评论里最常见的脏数据有这几种一种是评价内容里夹杂span标签片段多半是用户复制粘贴时带进来的第二种是「此用户未填写评价内容」「此评价是否对您有帮助」这类系统占位文本第三种是大量重复评价同一个用户对同一个商品反复提交相同内容第四种是「京东商城」「京东超市」等默认追加内容和商品本身的质量体验无关。把脏数据直接丢给情感分析模型轻则把准确率拉低几个点重则让整个词云图变成一堆无意义的品牌词。清洗这步做得够不够细直接决定后面result.csv里每一行是否可解释。判断的标准是清洗后留下的每一行都应该是「一个真实用户针对这个商品写的一段有效评价」。凡是违背这个标准的行都应该被过滤或修正。清洗前我习惯先跑一下describe和value_counts看看每条content的长度分布。你会发现大量记录集中在0到3个字符这些短评里混着占位符和空内容是后续清洗要重点关照的部分。不要一上来就写一堆正则先搞清楚脏数据集中在哪个字段、大概占多少比例清洗规则才有针对性。3.2 清洗函数与过滤规则清洗核心是几组正则表达式。不同项目的清理顺序略有差异我一般按这个顺序执行先剥HTML标签再去URL和特殊符号然后统一空白符最后按业务规则过滤。顺序不能乱比如先去掉HTML标签再处理特殊符号能避免标签属性里的符号干扰后续正则。import re import pandas as pd def clean_text(text): if not isinstance(text, str): return text re.sub(r[^], , text) # 去掉HTML标签 text re.sub(rhttps?://\S|www\.\S, , text) # 去掉URL text re.sub(r\w;, , text) # 去掉nbsp;这类实体 text re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9\s], , text) # 去掉emoji和特殊符号 text re.sub(r\s, , text).strip() # 合并空白符 return text df pd.read_csv(jd_comment.csv, encodingutf-8-sig) df[clean_content] df[content].apply(clean_text)第四行正则把中文、英文、数字和空格保留下来其余全部删除emoji基本就在这一步被处理掉了。比如「质量超5星」里的感叹号和星号会被删掉但绝大多数有效信息会保留下来。如果某段评论里含有「❤️」这种Unicode符号它不在白名单内直接被过滤这样处理的代价是极少数包含特殊符号的评论会丢失但对整体分析结果的影响可以忽略。实体符号这一步容易被漏掉。京东评论里经常出现”这种转义实体不处理的话清洗后文本里会残留一堆以开头的乱码词云里也会出现莫名其妙的英文片段。很多人清洗只做了去标签和去重漏了实体符号这一步结果词云图里全是乱码符号还以为是字体问题实际上是数据没洗干净。3.3 去重、无效评论剔除与字段标准化清洗完文本内容后紧接着做三件事过滤空文本、按三元组去重、剔除系统占位文本。这三件事的顺序建议是先过滤再拼接去重因为去重依赖清洗后的干净文本内容被清洗前后完全不同的情况虽然少见但确实存在。df df[df[clean_content].str.len() 5] # 过滤短文本 df df.drop_duplicates( subset[product_id, nickname, clean_content] ) # 三元组去重 placeholder df[clean_content].str.contains( 此用户未填写评价内容|此评价是否对您有帮助|默认好评, naFalse ) df df[~placeholder] df[time] pd.to_datetime(df[time], errorscoerce) df.to_csv(processed_comment_data.csv, indexFalse, encodingutf-8-sig)过滤短文本的下限设为5个字符这个值不是拍脑袋定的。「好用」两个字虽然没有清洗问题但作为情感分析样本信息量太低模型很难从这么短的文本里学到有效特征。如果只做词典打分可以把下限降到3但一旦要训练分类器建议保留5以上。时间字段用pd.to_datetime解析解析失败的单元会被置为NaT。这里如果直接dropna会误删有效样本我的做法是保留NaT等到画时间趋势图时再单独处理。processed_comment_data.csv在结构上比jd_comment.csv多了clean_content列少了原始content列因为后续情感分析和可视化都用不到带标签的原文保留干净文本就够了。到这里数据已经从「爬下来的原始页面」变成了「可以直接进模型的分析表」。4. 情感分析的两种落地方式词典打分与朴素贝叶斯模型4.1 基于正负情感词的词典打分法情感分析在这个项目里有两套实现路径。先看最简单的词典打分法它不需要任何训练过程只需要一份正面词表和一份负面词表。项目里正好提供了positive.txt和negative.txt里面的词形如「好用」「实惠」「物流快」「假货」「差评」「退货」之类覆盖了电商场景的高频情感词。词典打分的逻辑是统计一条评论中命中正面词表的词数量和命中负面词表的词数量正多则判正面负多则判负面相等则判中性。代码实现非常短十来行就够。pos_words set(open(positive.txt, encodingutf-8).read().split()) neg_words set(open(negative.txt, encodingutf-8).read().split()) def score_by_dict(text): pos_hits [w for w in pos_words if w in text] neg_hits [w for w in neg_words if w in text] if len(pos_hits) len(neg_hits): return 正面, len(pos_hits), len(neg_hits) elif len(neg_hits) len(pos_hits): return 负面, len(pos_hits), len(neg_hits) else: return 中性, len(pos_hits), len(neg_hits)把词表转成set而不是list是为了让后面的in判断从O(n)降到O(1)几千条评论跑起来没感知但几万条时差距就出来了。这里还有一个细节词典里的词直接做子串匹配无法处理否定结构。比如「物流不快」命中了「不快」这个词但词表里如果只有「快」没有「不快」就会被误判成正面。这是词典法的天然短板不是调参能解决的。这类误判在电商评论里非常常见。解决的一半办法是让词表尽可能包含完整词组negative.txt里直接放「不快」「不好」「太差」这类整体词比放「不」和「好」两个散词要靠谱得多。另一半办法就是上机器学习模型让模型自己学语法结构。4.2 train.py用自定义语料训练情感模型train.py的存在说明这个项目不只停留在词典打分还训练了一个机器学习模型。这里用的方案是snownlp的训练接口它底层是朴素贝叶斯分类器。训练数据的来源就是positive.txt和negative.txt每行一条分别标记为pos和neg。from snownlp import sentiment train_data [] for line in open(positive.txt, encodingutf-8): line line.strip() if line: train_data.append((line, pos)) for line in open(negative.txt, encodingutf-8): line line.strip() if line: train_data.append((line, neg)) sentiment.train(train_data) sentiment.save(sentiment.marshal.3)train接口接收的是一个「文本标签」元组构成的列表标签。不一定要叫pos和negsnownlp内部会做二分类映射。save之后生成的sentiment.marshal.3就是序列化后的模型文件后续sentiment_analysis.py运行时直接加载不需要重新训练。这个文件名的扩展名看起来有点怪其实是snownlp内部保存模型时自己约定的格式不影响加载。训练语料的数量直接影响模型效果。positive.txt和negative.txt各几百行的话训练出的模型能覆盖常见电商评论场景但遇到方言、谐音词、网络流行语还是会翻车。如果实验中对准确率有硬性要求可以自己多收集一些带标签评论追加到词表里重新跑一遍train.py即可。需要特别提醒的是snownlp首次运行时会尝试加载内置的情感模型文件如果当前目录下没有就会很慢。所以train.py跑完之后务必确认sentiment.marshal.3已经生成在与sentiment_analysis.py相同的目录下否则后续预测会走默认模型效果和自定义训练出来的是两回事。4.3 sentiment_analysis.py批量预测与结果落盘训练完成后的预测脚本非常轻量。sentiment_analysis.py的核心逻辑是读processed_comment_data.csv对clean_content逐条做情感判断把结果追加为sentiment列最终导出result.csv。import pandas as pd from snownlp import SnowNLP df pd.read_csv(processed_comment_data.csv, encodingutf-8-sig) def predict(text): if not isinstance(text, str) or len(text) 2: return 中性 s SnowNLP(text) prob s.sentiments if prob 0.6: return 正面 elif prob 0.4: return 负面 else: return 中性 df[sentiment] df[clean_content].apply(predict) df[sentiment_prob] df[clean_content].apply( lambda t: SnowNLP(str(t)).sentiments if isinstance(t, str) else 0.5 ) df.to_csv(result.csv, indexFalse, encodingutf-8-sig)SnowNLP(text).sentiments返回的是一个0到1之间的浮点数越接近1越正面越接近0越负面。0.6和0.4这两个阈值把区间分成三段中间是中性带。阈值不是死的如果场景里中性评价比例特别高可以把阈值放宽到0.7和0.3反之则收紧。建议先跑一版统计一下result.csv里三类的分布再回头调阈值比凭感觉设更靠谱。最后多存了一列sentiment_prob是为了后续画图时能展示情感强度的分布而不只是三个离散类别。这一列也能用来做阈值敏感性分析比如看看0.5到0.6之间有多少条评论能直观感受分类边界的拥挤程度。到这里result.csv已经包含了每个商品的评论、清洗后的文本、情感分类和概率剩下的工作就是把它变成看得懂的图。5. 避坑与常见问题爬取、清洗与训练中的六次翻车5.1 爬虫拿不到数据空列表与验证码弹窗坑一评论接口返回空列表。现象是接口正常返回200但comments字段是空数组maxPage却显示还有几十页。原因多半是未登录状态限流或者请求频率太高触发风控。解决方法是复制浏览器登录态的Cookie到session里请求间隔从1秒调到2秒以上把score参数拆开分批抓取别指望一次翻到底。坑二响应里出现验证码字段。现象是response.text里出现了captcha字样或者页面返回一段JSON里带verifyFlag。原因通常是请求头不完整尤其是缺少Referer或者Cookie已经失效。解决方法是把Referer补为商品详情页地址重新从浏览器复制Cookie再看看User-Agent是不是被识别成默认的python-requests。这个坑在换电脑换网络环境后特别容易重现每次换环境都检查一遍这三个头信息。5.2 清洗阶段的两个隐蔽问题坑三CSV写入后Excel打开乱码。现象是Python里读回来一切正常但用Excel双击打开全是乱码。原因是pandas默认utf-8编码Excel默认按GBK解码字节流被错误解释。解决方法是所有to_csv统一用encodingutf-8-sig不要只改一处。这个坑在清洗阶段出现一次情感分析结果落盘时又会遇到一次属于高频复发的老问题。坑四清洗后数据量减半。现象是jd_comment.csv里有一万条评论跑完清洗后processed_comment_data.csv只剩五千条。原因是短文本过滤门槛设为5大量像「好用」「一般」这样的短评被过滤同时三元组去重去掉了重复内容系统占位文本又筛掉一部分三者叠加数据量自然缩水。解决方法是清洗前把三个过滤条件拆开单独统计看看各自砍掉多少行。如果短评占比高把长度阈值从5降到3如果重复率高反而应该保留严格去重避免模型过拟合到重复样本上。5.3 模型和绘图阶段的坑坑五情感分析准确率低到没法解释。现象是明显骂人的评论「质量巨差千万别买」被预测为正面。原因可能是snownlp内置模型偏向通用语料对电商场景的特定表达不敏感另一个常见原因是train.py没有重新训练出sentiment.marshal.3运行时加载的还是缓存里的默认模型。解决方法是先确认模型文件是否真的生成且路径正确然后把「巨差」「千万别买」「太坑了」这类整句高频表达追加进词表重新训练。如果准确率还是不够把阈值调整到0.7/0.3让更多样本落进中性区精度会上升但召回率下降要有所取舍。坑六词云图全是乱码方块。现象是wordcloud生成图片后打开看到的全是方块和问号。原因是wordcloud默认字体不支持中文。解决方法是初始化时指定font_pathWindows上常见的是C:\Windows\Fonts\msyh.ttc或simhei.ttfmacOS上可以用/System/Library/Fonts/PingFang.ttc。不知道系统字体路径时先用fc-list | grep -i font查一遍再填。6. 可视化与结果验证三张图撑起一篇课程设计6.1 情感饼图、评分柱状图与词云的生成result.csv拿到手后可视化的活基本就是套模板。这个项目里的fig.png和jd_ciyun.jpg分别对应统计类图表和词云图。情感饼图用value_counts加plt.pie就能画评分柱状图用plt.hist按1到5分切段词云需要单独引wordcloud库。import matplotlib.pyplot as plt import pandas as pd from wordcloud import WordCloud df pd.read_csv(result.csv, encodingutf-8-sig) plt.figure(figsize(8, 6)) plt.pie(df[sentiment].value_counts(), labelsdf[sentiment].value_counts().index, autopct%.1f%%, startangle90) plt.savefig(sentiment_pie.png, dpi300, bbox_inchestight) plt.close() wc WordCloud(font_pathC:/Windows/Fonts/msyh.ttc, width800, height600, background_colorwhite) wc.generate( .join(df[clean_content].dropna())) wc.to_file(jd_ciyun.jpg)评分柱状图的核心是bins参数把1到5分切成5个桶再配xticks对齐刻度一张图就出来了。词云的font_path必须指向系统中文字体否则出来的图没法看。6.2 人工抽检与图表归档图表生成后不要直接当交付物。我的习惯是随机抽20条预测结果人工核对算一个粗略准确率偏差大的话回头调阈值和词表整个流程才能算闭环。从那以后我每次跑完情感分析都会强制走一遍抽检流程抽20条对一遍标签再出图省得被导师一句「这个准确率怎么验证」问倒。希望帮到你。本文还有配套的精品资源点击获取