简介:基于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节点的页面。
接口的核心参数包括商品ID(productId)、评分筛选(score)、排序方式(sortType)和页码(page)。score=0表示全部评价,1到3分别对应差评、中评、好评;sortType=5表示按时间倒序,6表示按推荐排序。page从0开始计数,每页pageSize条,常见是10条。还有一个fold参数控制是否需要折叠无意义内容,一般设为1即可。
这个项目里有个online_shopping_10_cats.csv,存放了十个分类下的商品ID和名称,爬虫脚本就是从这个文件里读取商品ID,再逐个去请求评论接口。我在复现时整理的请求代码基本可以直接套用:
import requests import time def fetch_comments(product_id, page=0, page_size=10): 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": f"https://item.jd.com/{product_id}.html" } resp = requests.get(url, params=params, headers=headers, timeout=10) 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_retries=3): 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", encoding="utf-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", mode="a", header=False, index=False, encoding="utf-8-sig") else: df.to_csv("jd_comment.csv", index=False, encoding="utf-8-sig")这里的去重键是三元组:商品ID、用户昵称、评论文本。为什么不用评论ID?因为这个接口返回的每条评论确实有id字段,但项目里抓过的一些数据中,部分评论的id是重复的,用三元组反而更稳。nickname加content能区分绝大多数场景,同一用户对同一商品发两条完全相同的评论几乎不可能存在。
CSV落盘的编码用utf-8-sig而不是utf-8,目的是让Excel直接打开不出现乱码。Python自带的csv模块写中文有时候会踩编码坑,用pandas的to_csv配上utf-8-sig是最稳妥的组合。文件追加模式要注意header=False,否则第二次运行会在文件中间多插一行列名,后续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(r"https?://\S+|www\.\S+", "", text) # 去掉URL text = re.sub(r"&\w+;", "", text) # 去掉 这类实体 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", encoding="utf-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( "此用户未填写评价内容|此评价是否对您有帮助|默认好评", na=False ) df = df[~placeholder] df["time"] = pd.to_datetime(df["time"], errors="coerce") df.to_csv("processed_comment_data.csv", index=False, encoding="utf-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", encoding="utf-8").read().split()) neg_words = set(open("negative.txt", encoding="utf-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", encoding="utf-8"): line = line.strip() if line: train_data.append((line, "pos")) for line in open("negative.txt", encoding="utf-8"): line = line.strip() if line: train_data.append((line, "neg")) sentiment.train(train_data) sentiment.save("sentiment.marshal.3")train接口接收的是一个「(文本,标签)」元组构成的列表标签。不一定要叫pos和neg,snownlp内部会做二分类映射。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", encoding="utf-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", index=False, encoding="utf-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统一用encoding="utf-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_path,Windows上常见的是C:\Windows\Fonts\msyh.ttc或simhei.ttf,macOS上可以用/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", encoding="utf-8-sig") plt.figure(figsize=(8, 6)) plt.pie(df["sentiment"].value_counts(), labels=df["sentiment"].value_counts().index, autopct="%.1f%%", startangle=90) plt.savefig("sentiment_pie.png", dpi=300, bbox_inches="tight") plt.close() wc = WordCloud(font_path="C:/Windows/Fonts/msyh.ttc", width=800, height=600, background_color="white") wc.generate(" ".join(df["clean_content"].dropna())) wc.to_file("jd_ciyun.jpg")评分柱状图的核心是bins参数,把1到5分切成5个桶,再配xticks对齐刻度,一张图就出来了。词云的font_path必须指向系统中文字体,否则出来的图没法看。
6.2 人工抽检与图表归档
图表生成后不要直接当交付物。我的习惯是随机抽20条预测结果人工核对,算一个粗略准确率,偏差大的话回头调阈值和词表,整个流程才能算闭环。从那以后,我每次跑完情感分析都会强制走一遍抽检流程,抽20条对一遍标签再出图,省得被导师一句「这个准确率怎么验证」问倒。希望帮到你。
本文还有配套的精品资源,点击获取