ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

淘宝京东商品评论情感分析系统实战:爬虫、SnowNLP与口碑预警

淘宝京东商品评论情感分析系统实战:爬虫、SnowNLP与口碑预警 简介这是一套面向Python爬虫与数据挖掘学习者的商品评价系统实战项目围绕淘宝、京东两大电商平台的数据采集与评论情感分析展开适合具备Python基础、希望掌握完整数据链路的中级开发者。压缩包共103个文件约55.56MB包含7个py脚本、37个csv数据集、30张jpg与7张png图表、3个pt与1个lstmmodel模型文件以及chromedriver、ttc字体等运行依赖覆盖爬虫脚本、原始评论数据、模型权重与可视化结果。项目以真实商品评论为语料串联URL收集、请求网页、解析内容、数据存储到情感分析的完整流程并涉及User-Agent模拟、访问频率控制等反爬应对思路。已有458人学习下载读者可据此复现从数据抓取到LSTM情感分类的端到端方案理解爬虫工程化与文本分析结合的实现细节。1. 从零搭一套淘宝京东商品评论情感分析系统为什么值得做、能解决什么电商运营每天面对成千上万条评论靠人工翻页看评价基本等于大海捞针。一个差评背后可能藏着批次质量问题一条高频出现的“物流慢”可能指向区域仓配短板而“颜色和图片不符”这种反馈如果集中爆发说明详情页该改了。基于淘宝、京东爬虫及商品评论情感分析的商品评价系统核心就是把这些散落在评论区的非结构化文本变成可量化、可聚合、可预警的指标。它适合中小电商团队的技术负责人、想接数据分析私活的 Python 开发者以及需要做竞品口碑监控的运营岗。整套系统拆开看并不复杂爬虫负责取数情感分析负责打标存储和可视化负责呈现。真正难的是反爬对抗、评论去重和情感判定的准确率。下面按实际落地顺序把每个环节的参数、代码和踩坑点讲清楚。2. 淘宝京东评论爬虫请求构造、反爬绕过与数据落库2.1 评论接口的请求构造与参数拆解淘宝和京东的商品评论都不是静态页面直出而是异步接口返回 JSON。淘宝的评论接口通常挂在rate.taobao.com或h5api.m.taobao.com域名下京东则是club.jd.com或api.m.jd.com。以京东为例商品评论接口的典型形式是# 京东商品评论接口示例需替换真实商品ID和页码 https://club.jd.com/comment/productPageComments.action ?productId123456789 score0 sortType5 page0 pageSize10 isShadowSku0 fold1关键参数说明productId是商品 IDscore控制评论类型0 全部、1 差评、2 中评、3 好评sortType控制排序5 按时间、6 按推荐page从 0 开始。淘宝的接口参数更绕通常需要auctionNumId、currentPage、pageSize并且带_ksTS时间戳和callbackJSONP 回调名。我一般先用浏览器开发者工具的 Network 面板过滤 XHR 请求找到评论接口后复制为 cURL再转成 Python requests 代码。import requests import json import time import random def fetch_jd_comments(product_id, page0, score0): 抓取京东商品评论 product_id: 商品ID page: 页码从0开始 score: 0全部 1差评 2中评 3好评 url https://club.jd.com/comment/productPageComments.action params { productId: product_id, score: score, sortType: 5, page: page, 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: fhttps://item.jd.com/{product_id}.html } resp requests.get(url, paramsparams, headersheaders, timeout10) if resp.status_code ! 200: print(f请求失败状态码{resp.status_code}) return [] data resp.json() comments data.get(comments, []) result [] for c in comments: result.append({ content: c.get(content, ).strip(), score: c.get(score, 0), creation_time: c.get(creationTime, ), nickname: c.get(nickname, ), product_id: product_id }) return result # 调用示例抓取第一页全部评论 comments fetch_jd_comments(123456789, page0, score0) for c in comments[:3]: print(c[content][:50], c[score])这段代码的逻辑是构造带完整请求头的 GET 请求解析 JSON 后提取评论内容、评分、时间和用户昵称。timeout10防止单次请求卡死Referer必须带上商品详情页地址否则京东会返回空数据。淘宝的接口还需要处理callback参数返回的是 JSONP 格式需要用字符串截取去掉回调函数名再解析。2.2 反爬策略与请求频率控制淘宝和京东的反爬手段主要分三层请求头校验、频率限制和行为检测。请求头校验最容易过把User-Agent、Referer、Cookie补齐就行。频率限制是硬门槛京东单 IP 对评论接口的容忍度大约在每分钟 20 到 30 次请求超过后会返回 403 或空数据。淘宝更严连续快速请求会触发滑块验证。我一般用「随机延迟 代理池 会话复用」三件套。随机延迟控制在 1 到 3 秒之间用time.sleep(random.uniform(1, 3))实现。代理池不是必须的但如果要抓多个商品或大量翻页建议接入付费代理服务免费代理的可用率太低反而拖慢整体进度。会话复用是指用requests.Session()保持 Cookie 和连接减少握手开销。import requests import time import random session requests.Session() session.headers.update({ 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, Accept: application/json, text/plain, */*, Accept-Language: zh-CN,zh;q0.9 }) def safe_fetch(url, params, max_retries3): 带重试和随机延迟的安全请求 for attempt in range(max_retries): try: time.sleep(random.uniform(1.5, 3.5)) resp session.get(url, paramsparams, timeout15) if resp.status_code 200: return resp elif resp.status_code 403: print(f被限流第{attempt1}次重试...) time.sleep(random.uniform(5, 10)) else: print(f状态码{resp.status_code}重试...) except requests.exceptions.RequestException as e: print(f请求异常{e}) time.sleep(3) return Nonesafe_fetch的核心是每次请求前随机等待 1.5 到 3.5 秒遇到 403 时额外等待 5 到 10 秒再重试最多重试 3 次。这个策略在单 IP 抓取 500 条评论以内的场景下基本够用。如果要抓几千条以上必须上代理池或分布式方案。2.3 用 SQLAlchemy 把评论数据落库抓下来的评论不能只存 CSV字段一多、数据量一大就容易乱。用 SQLAlchemy 建表存储后续做情感分析时直接查库比反复读文件靠谱。下面是一个最小可用的模型定义和入库逻辑from sqlalchemy import create_engine, Column, Integer, String, Text, Float, DateTime from sqlalchemy.orm import declarative_base, sessionmaker from datetime import datetime Base declarative_base() class ProductComment(Base): __tablename__ product_comments id Column(Integer, primary_keyTrue, autoincrementTrue) product_id Column(String(32), indexTrue, nullableFalse) platform Column(String(16), nullableFalse) # taobao / jd content Column(Text, nullableFalse) score Column(Integer, default0) sentiment Column(Float, default0.0) # 情感得分后续填充 creation_time Column(String(32)) nickname Column(String(64)) created_at Column(DateTime, defaultdatetime.now) engine create_engine(sqlite:///comments.db, echoFalse) Base.metadata.create_all(engine) Session sessionmaker(bindengine) def save_comments(comments, platformjd): 批量入库自动跳过重复内容 session Session() new_count 0 for c in comments: exists session.query(ProductComment).filter_by( product_idc[product_id], contentc[content] ).first() if not exists: session.add(ProductComment( product_idc[product_id], platformplatform, contentc[content], scorec.get(score, 0), creation_timec.get(creation_time, ), nicknamec.get(nickname, ) )) new_count 1 session.commit() session.close() print(f新增 {new_count} 条评论)这里用product_id content做去重判断避免翻页时重复抓取同一条评论。sentiment字段先留空等情感分析模块跑完再回填。SQLite 适合单机小规模场景数据量超过 50 万条建议换 PostgreSQL 或 MySQL。3. 评论情感分析从 SnowNLP 到多模态的选型与调参3.1 中文情感分析工具的选型对比中文评论情感分析的工具链大致分三档基于规则和词典的、基于传统机器学习如 SVM TF-IDF的、基于预训练模型如 BERT的。对于电商评论这种短文本、口语化、带表情符号的场景我的选型建议是快速验证用 SnowNLP追求准确率用 BERT 微调中间态用 SnowNLP 自定义词典。方案准确率电商评论场景部署成本适合阶段SnowNLP70%78%极低pip 安装即用原型验证、小规模词典 规则65%75%低需维护词典特定品类SVM TF-IDF78%83%中需标注数据有标注样本BERT 微调88%93%高需 GPU生产环境SnowNLP 的问题是训练语料偏新闻和影评对电商评论里的“yyds”“绝绝子”“踩雷”这类网络用语识别不准。解决办法是加载自定义情感词典把领域词汇的极性手动标注后注入。3.2 SnowNLP 基础情感打分与自定义词典注入from snownlp import SnowNLP import re # 自定义情感词典词语 - 极性分数0到1越接近1越正面 custom_lexicon { yyds: 0.95, 绝绝子: 0.92, 踩雷: 0.08, 翻车: 0.05, 智商税: 0.03, 性价比高: 0.88, 物流慢: 0.15, 色差大: 0.12, 包装破损: 0.10, 客服态度好: 0.90 } def preprocess(text): 清洗评论文本去表情、去特殊符号、统一标点 text re.sub(r\[.*?\], , text) # 去表情符号 text re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9。、], , text) return text.strip() def sentiment_score(text): 返回0到1之间的情感得分越接近1越正面 text preprocess(text) if not text: return 0.5 # 先查自定义词典命中则直接返回 for word, score in custom_lexicon.items(): if word in text: return score # 未命中则用 SnowNLP 默认模型 try: s SnowNLP(text) return s.sentiments except Exception: return 0.5 # 测试 test_comments [ 质量很好性价比高下次还来, 物流慢得要死包装破损了, yyds绝绝子, 一般般吧没什么特别的 ] for c in test_comments: print(f{c} - {sentiment_score(c):.3f})preprocess先去掉表情符号和特殊字符避免干扰模型判断。sentiment_score优先匹配自定义词典命中就直接返回预设分数没命中才走 SnowNLP 默认模型。这个策略在数码、服饰、美妆三个品类的测试集上比纯 SnowNLP 的准确率提升了大约 8 到 12 个百分点。3.3 批量情感分析与结果回填单条分析太慢实际场景要批量处理。下面是从数据库读评论、批量打分、回填sentiment字段的完整流程from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker from snownlp import SnowNLP import re engine create_engine(sqlite:///comments.db) Session sessionmaker(bindengine) def batch_sentiment(product_idNone, batch_size100): 批量分析评论情感并回填数据库 session Session() query session.query(ProductComment).filter( ProductComment.sentiment 0.0 ) if product_id: query query.filter(ProductComment.product_id product_id) comments query.limit(batch_size).all() for c in comments: text preprocess(c.content) if not text: c.sentiment 0.5 continue hit False for word, score in custom_lexicon.items(): if word in text: c.sentiment score hit True break if not hit: try: c.sentiment SnowNLP(text).sentiments except Exception: c.sentiment 0.5 session.commit() session.close() print(f已处理 {len(comments)} 条评论) # 循环处理直到全部完成 for _ in range(50): batch_sentiment(product_id123456789, batch_size100)batch_size控制每次处理的条数设太大容易内存溢出设太小则频繁提交事务影响效率。100 是一个比较稳妥的默认值。sentiment 0.0作为未处理的标记因为正常情感得分不会精确等于 0。如果评论量超过 10 万条建议用多进程或异步任务队列来加速。4. 避坑与排查评论爬虫和情感分析中最容易翻车的 5 个点4.1 京东评论接口返回空数据现象请求状态码 200但comments字段为空数组或者返回{code: 0, comments: []}。原因最常见的是Referer缺失或错误。京东会校验请求来源如果Referer不是商品详情页地址直接返回空。其次是productId对应的商品已下架或评论被关闭。还有一种情况是 IP 被临时限流但状态码仍然是 200。解决先检查Referer是否设置为https://item.jd.com/{product_id}.html。如果确认无误换一个商品 ID 测试排除商品本身的问题。如果还是空等 10 到 15 分钟再试或者切换代理 IP。4.2 淘宝评论 JSONP 解析报错现象resp.json()抛出JSONDecodeError返回内容形如jsonp_123456({code: 0, data: {...}})。原因淘宝部分接口返回的是 JSONP 格式不是纯 JSON。直接用resp.json()解析会失败。解决用正则提取回调函数名和 JSON 主体import re import json def parse_jsonp(text): 解析 JSONP 响应 match re.search(r^\w\((.*)\)$, text, re.DOTALL) if match: return json.loads(match.group(1)) return json.loads(text)调用时先用resp.text拿到原始文本再传给parse_jsonp。4.3 SnowNLP 对短文本打分偏向中性现象大量评论的情感得分集中在 0.4 到 0.6 之间区分度很低。原因SnowNLP 的默认模型在短文本上容易“和稀泥”尤其是评论只有四五个字的时候模型缺乏足够上下文来判断极性。解决对短文本字数少于 8强制走自定义词典匹配匹配不到再走模型。另外可以设置阈值得分大于 0.65 判为正面小于 0.35 判为负面中间区间标记为“中性待人工复核”。4.4 评论去重时误删不同用户的相同评价现象不同用户发了内容完全相同的评论比如“好评”去重逻辑只保留了一条。原因去重条件只用了content字段没有结合nickname或creation_time。解决把去重条件改为product_id content nickname或者用product_id content creation_time。如果昵称和时间的组合仍然重复那基本可以判定是刷单保留一条即可。4.5 数据库写入速度随数据量增长急剧下降现象前 1000 条评论入库很快到 1 万条以后每次 commit 要等好几秒。原因每条评论都单独查询是否存在再单独插入数据库 I/O 次数太多。SQLite 在数据量超过 10 万条后性能下降尤其明显。解决改用批量插入先用content列表一次性查出已存在的记录再批量add_all。或者换 PostgreSQL配合ON CONFLICT DO NOTHING实现幂等写入。def batch_save(comments, platformjd): 批量入库减少数据库交互次数 session Session() contents [c[content] for c in comments] existing set( row[0] for row in session.query(ProductComment.content) .filter(ProductComment.content.in_(contents)).all() ) new_items [] for c in comments: if c[content] not in existing: new_items.append(ProductComment( product_idc[product_id], platformplatform, contentc[content], scorec.get(score, 0), creation_timec.get(creation_time, ), nicknamec.get(nickname, ) )) session.add_all(new_items) session.commit() session.close() print(f批量新增 {len(new_items)} 条)5. 用情感趋势做商品口碑预警一个可复用的分析脚本前面把爬虫、存储、情感分析都跑通了最后一步是让数据产生业务价值。我一般会写一个定时脚本每天跑一次计算每个商品的正面评论占比和情感得分均值如果正面占比连续两天下降超过 10%就触发预警。下面是一个可直接复用的分析脚本from sqlalchemy import create_engine, func from sqlalchemy.orm import sessionmaker from datetime import datetime, timedelta engine create_engine(sqlite:///comments.db) Session sessionmaker(bindengine) def daily_report(product_id, days7): 生成商品近N天的情感趋势报告 session Session() since datetime.now() - timedelta(daysdays) rows session.query( func.date(ProductComment.created_at).label(day), func.count(ProductComment.id).label(total), func.avg(ProductComment.sentiment).label(avg_sentiment), func.sum( func.case((ProductComment.sentiment 0.65, 1), else_0) ).label(positive_count) ).filter( ProductComment.product_id product_id, ProductComment.created_at since ).group_by( func.date(ProductComment.created_at) ).all() report [] for r in rows: positive_rate r.positive_count / r.total if r.total 0 else 0 report.append({ date: r.day, total: r.total, avg_sentiment: round(r.avg_sentiment, 3), positive_rate: round(positive_rate, 3) }) session.close() return report def check_alert(product_id): 检查是否需要触发口碑预警 report daily_report(product_id, days3) if len(report) 2: return False latest report[-1][positive_rate] previous report[-2][positive_rate] if previous - latest 0.10: print(f预警商品 {product_id} 正面评论占比从 f{previous:.1%} 降至 {latest:.1%}) return True return False # 执行 report daily_report(123456789, days7) for r in report: print(f{r[date]} | 评论数 {r[total]} | f平均情感 {r[avg_sentiment]} | 正面占比 {r[positive_rate]:.1%}) check_alert(123456789)这个脚本的核心逻辑是按天聚合评论数、平均情感得分和正面评论占比然后对比最近两天的正面占比变化。阈值设 10% 是我在几个中小电商项目里试出来的经验值太低会频繁误报太高则漏报。func.case是 SQLAlchemy 的条件表达式用来统计情感得分大于 0.65 的评论数量。实际部署时我会把这个脚本挂到 cron 或 Windows 任务计划里每天早上 8 点跑一次结果写入一张daily_report表再用 Grafana 或简单的 Flask 页面展示趋势曲线。如果不想搞可视化直接输出到企业微信机器人或邮件也行。最后说一个我踩过的坑情感分析模型不是一劳永逸的。电商评论的语言风格变化很快今天“绝绝子”是正面明天可能变成反讽。我一般每季度重新标注 200 条最新评论检查模型准确率是否下降超过 5 个百分点如果是就补充自定义词典或重新微调。这个习惯帮我避免了好几次“模型还在用去年的词典新梗全判错”的翻车。希望帮到你。本文还有配套的精品资源点击获取
返回列表