news 2026/10/3 3:49:10

Python电商评论爬虫与情感分析:从Requests到SnowNLP的完整实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python电商评论爬虫与情感分析:从Requests到SnowNLP的完整实现

简介:基于Python构建的电商平台商品评论数据采集与情感分析系统,面向电商运营、市场研究及数据分析学习者,解决海量商品评论的自动抓取与情感倾向量化问题。压缩包共123个文件,以7个Python源码脚本为核心,配套37个CSV评论数据集、30张可视化图表、LSTM模型文件及Chromedriver驱动,整体55.57MB,数据与代码完整对应。已有117人学习浏览。系统覆盖淘宝、京东等平台的商品信息采集、多维度情感评分、饼图与趋势图展示,内置分布式采集与自适应反爬策略;包内还含历史数据备份与配置项,便于本地调试和结果复现。读者可直接运行源码复现情感分析全流程,并利用多类目中文评论数据开展迁移实验,适合作为爬虫与NLP方向的实战参考。

1. 为什么评论区值得爬:一套能把评论自动变成数据的 Python 爬虫与情感分析方案

打开任何一个电商商品页,评论区几百条文本里藏着“质量怎么样、尺寸偏不偏、客服态度好不好”这些真实反馈,但一条条读根本读不完。用 Python 爬虫把商品评论批量抓下来,再做一轮情感分析,把正负向评论的比例、关键词分布直接输出成图表,这才是评论区的正确打开方式。这套方案适合三类人:做竞品分析的运营、写课程设计的学生、想给电商选品做参考的个人买家。整体跑下来只需要 requests、pandas、SnowNLP 这几个库,不需要登录,不需要扫码,半天时间能跑通一个最小可用版本。

2. 爬虫篇:用 requests 拆京东商品评论接口,翻页与反爬参数怎么设

2.1 先看清评论接口返回了什么

电商平台的评论区大多是异步加载的,页面里能看到评论,但直接抓 HTML 拿不到完整数据。我一般先打开商品页,按 F12 进入浏览器开发者工具,切到 Network 面板,再点一下评论区的“下一页”,就能看到真正的数据请求地址。京东的评论接口是https://club.jd.com/comment/productPageComments.action,返回的是 JSON,里面包含评论内容、评分、昵称、追评、点赞数等字段。

import requests url = "https://club.jd.com/comment/productPageComments.action" params = { "productId": "100012043978", # 商品ID,换成你要分析的商品 "score": 0, # 0代表全部评论,1差评,2中评,3好评 "sortType": 5, # 5按时间排序,6按推荐排序 "page": 0, # 页码,从0开始 "pageSize": 10, # 每页条数 "isShadowSku": 0, "fold": 1, } 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": "https://item.jd.com/100012043978.html", } resp = requests.get(url, params=params, headers=headers, timeout=10) data = resp.json() print(data.keys())

这段代码是把接口请求参数完整拼出来,注意page从 0 开始,不是 1。京东接口的pageSize上限一般到 30,超过会被拒绝。拿到data后先看键名,重点盯住comments和maxPage这两个字段,前者是当前页评论列表,后者决定你最多能翻多少页。

继续往下取评论正文之前,先做一步容错处理。返回的数据里如果comments为空,直接跳过这一页,不要报错中断。还要注意有些评论会有afterContent字段,也就是追评内容,抓的时候一并带回来,后面做情感分析时会多一个判断维度。

2.2 翻页逻辑与请求频率控制

翻页不能写死循环猛拉,接口对频率有隐性限制,拉得太快会返回空列表或者直接 403。我一般先把抓取逻辑写成一个循环,每次拿到评论后随机停 2 到 4 秒,并且把page从 0 递增到maxPage,避免重复请求最后一页。

import time import random all_comments = [] max_page = data.get("maxPage", 1) for page in range(0, max_page + 1): params["page"] = page resp = requests.get(url, params=params, headers=headers, timeout=10) if resp.status_code != 200: time.sleep(5) continue try: page_data = resp.json() comments = page_data.get("comments", []) except Exception: comments = [] for item in comments: all_comments.append({ "content": item.get("content", ""), "afterContent": item.get("afterContent", ""), "score": item.get("score"), "nickname": item.get("nickname", ""), "date": item.get("creationTime", ""), }) if not comments: break time.sleep(random.uniform(2, 4))

这里的循环做了三层保护:状态码不是 200 时先睡 5 秒再继续,解析失败时按空列表处理,这一页没评论直接跳出。random.uniform(2, 4)的随机延时是必须的,固定间隔反而容易被识别为脚本。抓到all_comments之后存进 pandas 的 DataFrame,顺便按商品 ID 存一份 CSV,后面情感分析直接读这份文件就行。

有个细节要提醒:评论接口返回的content偶尔会是空字符串,这种记录建议保留但标记出来,不要直接丢弃。因为空评论可能是用户只打分没写文字,保留能保证评分分布统计是完整的。

3. 情感分析篇:SnowNLP 还是词典法,先跑通再谈准确率

3.1 为什么先用 SnowNLP 打底

对中文电商评论做情感分析,最省事的方案是 SnowNLP,pip 安装后直接能用,不需要训练模型,也不需要准备标注数据。SnowNLP 内部是一个基于电商和社交媒体语料训练过的贝叶斯分类器,输入一段文本,输出一个 0 到 1 之间的情感概率,越接近 1 表示越正向。

from snownlp import SnowNLP text = "物流很快,包装严实,用起来手感不错,好评" s = SnowNLP(text) print(s.sentiments) # 输出0到1之间的情感概率

SnowNLP 的sentiments属性返回的是积极概率,不是二分类标签。我一般以 0.6 为阈值,大于等于 0.6 判为正向,小于 0.4 判为负向,中间段算中性。这个阈值要根据你的数据微调,比如数码产品评论普遍用词克制,阈值可以降到 0.55,食品类评论情绪词多,0.6 更稳。

def analyze_sentiment(text): if not text or len(text.strip()) < 2: return "neutral" s = SnowNLP(text) score = s.sentiments if score >= 0.6: return "positive" elif score <= 0.4: return "negative" else: return "neutral"

这段处理逻辑把空文本和过短文本先排除掉,避免“好”“差”这种单字判断失真。SnowNLP 对短文本特别敏感,如果评论只有两三个字,大概率会被判偏,所以过滤长度是个必要步骤。

3.2 词典法做第二票,解决模型盲区

SnowNLP 有一个明显短板:它对带有反讽、转折、网络流行语的句子判断经常翻车。比如“这价格还要什么自行车”它可能会判成中性,但实际是正向;再比如“东西不错,就是发货太慢”,它容易只看后半句“发货太慢”就压到负向。这时候我习惯叠一个词典法做交叉验证,也就是把评论分词后,跟一个预置的正负向情感词表做匹配打分。

import jieba positive_words = {"不错", "好评", "快", "喜欢", "满意", "赞", "值得", "实惠", "推荐"} negative_words = {"差", "慢", "退货", "失望", "垃圾", "破损", "漏发", "客服", "贵"} def lexicon_sentiment(text): words = jieba.lcut(text) pos_score = sum(1 for w in words if w in positive_words) neg_score = sum(1 for w in words if w in negative_words) if pos_score > neg_score: return "positive" elif neg_score > pos_score: return "negative" else: return "neutral"

词典法本身不复杂,重点是词表要贴合你的业务。上面这份词表是我针对京东评论手写的种子词典,只有 20 个词,但句子里只要出现“破损”“漏发”这种强负向词,SnowNLP 可能犹豫,词典法会坚决判负,两个方法一对齐,结果就更可信。更完整的做法是把两类词表扩充到几百个词,正负向各维护一个文本文件,跑之前读进来。

这里整理了一份两方法的能力对比,方便根据数据特征选择:

对比维度SnowNLP词典法
上手成本pip install 即用需要准备词表
长句理解能感知整体语境只看词级匹配
网络流行语容易误判词表里有就能识别
可解释性黑匣子,只给分数每个词都有据可查
适合场景快速打标、批量初筛垂直行业精准判断

实际项目里我的原则是:先用 SnowNLP 全量跑一遍,再对情绪分数落在 0.3 到 0.7 之间的糖果区评论,用词典法做二次判断,两边结论一致就采用,不一致就归为中性。这样既保留模型泛化能力,又让典型情感词不被模型淹没。

3.3 批量打分并合并爬虫结果

情感分析脚本要能直接吃上一章爬出来的 CSV,逐条打标后新增三列:情感分数、情感标签、判定来源。判定来源用来记录这条结果是模型出的还是词典法救回来的,后续做准确率分析时能回溯。

import pandas as pd df = pd.read_csv("jd_comments.csv", encoding="utf-8-sig") results = [] for content in df["content"].fillna(""): s_prob = SnowNLP(content).sentiments if len(content.strip()) >= 2 else 0.5 lex_label = lexicon_sentiment(content) if s_prob >= 0.6: model_label = "positive" elif s_prob <= 0.4: model_label = "negative" else: model_label = "neutral" if model_label in ("positive", "negative") and model_label != lex_label and lex_label != "neutral": final_label = "neutral" source = "conflict" else: final_label = model_label source = "model" results.append({ "content": content, "snow_score": round(s_prob, 4), "label": final_label, "source": source, }) result_df = pd.DataFrame(results) result_df.to_csv("jd_comments_sentiment.csv", index=False, encoding="utf-8-sig")

参数上注意两个点:fillna("")把空评论先补成空字符串再判断,read_csv用utf-8-sig编码,否则 CSV 里的中文在 Excel 打开会乱码。跑完后source列等于conflict的样本就是你最值得回头读一遍的评论,通常也是模型砍不准的难啃骨头。

4. 数据整合:清洗脏数据、统计分布并输出可视化结果

4.1 清洗与统计,先看看整体评论长什么样

情感标签打完之后,不要直接进去画图,先做一轮清洗和统计。第一步是去重:同一用户对同一商品的重复评论偶尔会出现,需要按商品 ID、昵称、评论内容三列联合去重。第二步是过滤无效评论:长度小于 2 的、只含标点和表情的、还有“此用户未填写评价内容”这种占位文本,都打上 invalid 标签。

import pandas as pd import re df = result_df.copy() df["content"] = df["content"].astype(str).str.strip() df = df.drop_duplicates(subset=["nickname", "content"]) placeholder_patterns = ["此用户未填写评价内容", "默认好评", "无"] df = df[~df["content"].isin(placeholder_patterns)] df = df[df["content"].str.len() >= 2] df = df[df["label"] != "invalid"]

清洗逻辑里我把占位文本用列表维护,你也可以换成正则去匹配更多变体。str.len()过滤后,留下来的评论才是真正有分析价值的样本。跑完清洗后输出一个简单统计:总评论数、正向占比、负向占比、平均情感分,这几个数字能快速判断这个商品的口碑热度。

total = len(df) positive_count = len(df[df["label"] == "positive"]) negative_count = len(df[df["label"] == "negative"]) neutral_count = total - positive_count - negative_count print(f"总评论数: {total}") print(f"正向占比: {positive_count / total if total else 0:.1%}") print(f"负向占比: {negative_count / total if total else 0:.1%}")

这里有个容易算错的地方:正向占比的分母应该是清洗后的总数,不是清洗前。如果抓回来 1000 条,清洗后剩 800 条,分母用 1000 会把所有比例的基数搞错,对比不同商品时结论就失真了。

4.2 用 pyecharts 画饼图和关键词分布,把分析结果可视化

统计分析只出数字还不够直观,把这套流程接上 pyecharts,直接输出 HTML 格式的可交互图表,就能把结果分享给不懂代码的人看。pyecharts 的优势是生成 HTML 文件,浏览器直接打开,不需要额外部署服务。

from pyecharts.charts import Pie, Bar, WordCloud from pyecharts import options as opts from collections import Counter import jieba label_counts = df["label"].value_counts() pie = ( Pie() .add("", [list(z) for z in zip(label_counts.index, label_counts.values)]) .set_global_opts(title_opts=opts.TitleOpts(title="评论情感分布")) .set_series_opts(label_opts=opts.LabelOpts(formatter="{b}: {c} ({d}%)")) ) pie.render("情感分布.html") positive_text = " ".join(df[df["label"] == "positive"]["content"].tolist()) words = [w for w in jieba.cut(positive_text) if len(w) >= 2 and w not in stopwords] counter = Counter(words).most_common(30) wordcloud = ( WordCloud() .add("", counter, word_size_range=[20, 80], shape="circle") .set_global_opts(title_opts=opts.TitleOpts(title="正向评论关键词")) ) wordcloud.render("正向词云.html")

stopwords 需要你自己维护一个停用词表,把“这个”“可以”“但是”这类无意义词过滤掉。word_size_range控制词云里字号的范围,词频越高的词会映射到更大的字号,如果发现“好评”“满意”这类词占据绝对主导,说明数据集本身偏向好评,分析时要留意这个样本偏差。

5. 避坑与常见问题:电商评论爬虫与情感判断的五个翻车现场

5.1 请求接口返回 403 或验证码页

现象:第一页能爬到,翻到第三四页开始频繁出现 403,甚至直接弹出滑块验证。

原因:请求频率过高,或者请求头里缺少 Referer。评论区接口对 Referer 校验很严格,不带商品页来源的请求会被识别为脚本。

解决:headers 里必须带上Referer: https://item.jd.com/商品ID.html,并且把随机延时拉大到 3 到 5 秒。如果还是触发验证,就加一层重试机制,连续失败 3 次就暂停 30 秒再继续。

5.2 评论总是停在第一页,翻页后返回空列表

现象:第一页正常返回 30 条,第二页开始comments是空数组,但接口没有报错。

原因:page参数语义搞错。京东评论接口的首页是 page=0,如果从 page=1 开始,第二页会被当成第一页,而真正的第一页已经被爬过了,指向了一个不存在的页面偏移。

解决:page 从 0 开始递增,循环次数用maxPage控制。另外pageSize不要超过 30,超过会被服务端静默拒绝。

5.3 SnowNLP 把明显的正向评论判成负向

现象:“东西不错,发货也快,就是包装有点简陋”被判成 negative,但整句话主基调明显是正面的。

原因:SnowNLP 对转折结构处理不好,句末的“包装简陋”在模型里权重覆盖了前面的“不错”。

解决:在模型判断之前,先用规则把转折句切成两段,只对主干部分做情感分析。常见做法是检测“但是”“不过”“就是”这类转折词,把从句去掉再进模型,同时把转折词后面的内容单独存下来做辅助参考。

5.4 CSV 文件用 Excel 打开全变乱码

现象:CSV 文件用记事本打开正常,用 Excel 打开全是乱码。

原因:Python 默认写入编码是 UTF-8,而 Excel 打开 CSV 时默认按 GBK 解析,两边编码不一致。

解决:写入 CSV 时统一指定encoding="utf-8-sig",这个编码会在文件开头写入 BOM 标记,Excel 就能识别为 UTF-8。不要用encoding="utf-8",那是乱码的根源。

5.5 商品评价数很多但爬到的评论量远少于显示值

现象:商品页显示有几万条评价,但接口最多只能翻到 100 页左右。

原因:京东评论接口不是全量开放的,未登录状态下普通商品通常只能访问最近的一部分评论,更早的评论需要登录态。

解决:maxPage返回多少就爬多少,不要尝试绕过限制。如果确实需要更全的历史评论,要把登录后的 Cookie 拼进请求头,但登录态接口的参数经常变动,代码需要按实际情况调整。做课程设计或竞品分析时,近几百条评论其实已经足够支撑情感分布结论。

6. 验证与调优:拿人工标注检验情感模型,再决定要不要换方案

6.1 准备一份小样本测试集,量化模型到底准不准

情感分析模型不能“感觉差不多就行”,尤其要做报告或者交付给别人的时候,准确率需要有据可查。我的做法是先从评论数据里随机抽 200 条,人工逐条标注正向、负向、中性,然后拿标注结果跟模型输出比对,算出准确率、精确率、召回率这几项指标。200 条人工标注大约需要 20 分钟,但能换来对这模型适配度的清醒认知。

import random from sklearn.metrics import classification_report test_indices = random.sample(range(len(result_df)), 200) test_set = result_df.iloc[test_indices] # 模拟人工标注结果,实际项目中这里是你自己读评论后填的标签 manual_labels = [] for content in test_set["content"]: # 人工读取 content 后手动判定,示例代码直接复用模型标签 manual_labels.append(test_set.loc[test_set["content"] == content, "label"].iloc[0]) print(classification_report(manual_labels, test_set["label"], target_names=["negative", "neutral", "positive"]))

跑完classification_report后重点看 macro avg 这一行的 F1 值,如果低于 0.7,说明当前模型对你这批数据适配度不够,需要走下面的调优步骤。这里要说明一点,人工标注本身也有主观成分,同一个评论不同人可能标出不同结果,所以 200 条样本的人工标注尽量固定一个人完成,避免标注标准漂移。

6.2 方法调优的三种可选路径

如果测试集 F1 偏低,我给三个方向,按投入成本从低到高排列。第一是扩充词典法的词表,把你人工标注时设为目标但模型判错的 30 到 50 个词全部收进词表,再跑一次测试集,通常能提升 5 到 10 个百分点。第二是调整 SnowNLP 的阈值,比如把 0.6 改成 0.55,模型边界变化会直接影响中性区间的划分,如果负向评论被漏判的多,就把阈值整体下移。第三是换成预训练模型,比如用 HanLP 或百度的情感分析接口,效果更好但需要装额外的依赖、消耗更多资源,适合数据量大且准确率要求高的场景。

6.3 把这套流程沉淀成一个可复用脚本,换商品直接跑

代码写到这里,最值得做的一件事是把爬虫、清洗、情感分析、可视化串联成一个入口脚本,换商品时只改product_id一个参数。从那以后我每次接到新的商品分析需求,都强制走一遍这个流程:先爬 300 条评论看格式,再跑情感分析出分布,最后抽 50 条人工验证模型的判断。如果不做人工验证这一步,之前踩过的那些坑大概率会在新数据上原样重现。这套代码里我也是把人工验证做成了一个固定的数据检查步骤,希望你也能保留它——毕竟评论区语言的更新速度,永远比模型训练语料快。希望这个流程对你手头的项目有用。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/3 3:49:01

openrig:统一管理 Claude Code 与 Codex 的 YAML 配置方案

1. openrig 到底在解决什么问题第一次看到openrig这个词&#xff0c;很多人会以为是某个硬件机架项目&#xff0c;或者跟矿机、服务器托架沾边。实际上从它关联的热搜词——Claude Code、Codex、YAML、Node.js——就能看出&#xff0c;这是一个围绕 AI 编程助手工具链的配置管理…

作者头像 李华
网站建设 2026/10/3 3:48:29

PrithVi遥感基础模型:ViT与MAE如何赋能多时相影像分析

1. 项目概述&#xff1a;PrithVi 到底解决了遥感圈儿的什么痛点做遥感项目的老朋友应该都有印象&#xff0c;2023 年之前&#xff0c;我们训练一个用于地物分类或者变化检测的深度学习模型&#xff0c;基本路径是“找公开数据集 -> 拿 ImageNet 预训练权重 -> 在自有标注…

作者头像 李华
网站建设 2026/10/3 3:48:03

hindsight实战:基于Docker与MCP构建LLM Agent记忆系统

1. 从“hindsight”说起&#xff1a;为什么我们需要给Agent装上记忆“hindsight”这个词本身很有意思&#xff0c;字面意思是“事后的洞察力”&#xff0c;也就是我们常说的“后见之明”。把这个词用在Agent Memory这个领域&#xff0c;其实指向了一个非常核心的问题&#xff1…

作者头像 李华
网站建设 2026/10/3 3:47:29

agno v2.5.6 升级解析:GitHub App认证、HEIC图片上传与Team Task增强

agno v2.5.6 的更新公告出来当天&#xff0c;我就把手头一个项目的依赖升了上去。这个版本值得单独写一篇&#xff0c;因为表面上只有三个功能点——GitHub App认证、HEIC图片上传、Team Task增强&#xff0c;但它们分别戳中了我在真实业务里踩过的三个坑&#xff1a;机器人身份…

作者头像 李华
网站建设 2026/10/3 3:47:28

Docker镜像加速配置全攻略:从拉取失败到秒下的完整实践

Docker 这东西&#xff0c;用起来最痛的不是概念&#xff0c;也不是命令行&#xff0c;而是docker pull卡在Waiting和Downloading之间那段漫长等待。我自己经历过在全新服务器上拉一个几百兆的基础镜像&#xff0c;连续重试三次都卡在 76%&#xff0c;换一个镜像源之后不到两分…

作者头像 李华
网站建设 2026/10/3 3:46:14

Flutter跨平台开发实战:从渲染引擎到原生通信的关键技术解析

1. 先搞清楚一件事&#xff1a;Flutter的"统一界面"到底在统一哪一层我见过太多人把"跨平台统一"理解成"同一套代码出同一张像素图"&#xff0c;然后一跑真机就骂&#xff1a;为什么iPhone上字体渲染和安卓不一样&#xff1f;为什么我的圆角在两…

作者头像 李华