简介:这份基于Python的淘宝、京东商品评价系统源码包,是为毕业设计或期末大作业场景量身打造的综合型项目。资源覆盖爬虫采集、数据清洗与商品评论情感分析完整链路,既适合计算机相关专业学生直接参考实现,也适合希望快速搭建电商数据分析demo的学习者,整体难度适中,评审得分达95分以上。压缩包共103个文件,约55.57MB,主要包含7个py核心源码、37个csv中文商品评论及电商数据、7个jpg与7个png可视化图表、4个xml配置、3个pt与lstmmodel模型文件、1个chromedriver及2个txt说明文档,目录分类清晰,便于按模块检索和二次开发。已有219人浏览学习。资料内含经过本地编译可运行的完整程序、京东与淘宝双平台真实评论数据、情感分类模型文件以及可视化展示脚本,可直接用于系统演示、论文实验或功能扩展,是毕设项目的高性价比参考素材。
1. 商品评价系统到底在做什么:用爬虫和情感分析解决什么问题
做电商运营或产品调研的朋友大多有过这种体验:想看一款商品到底值不值得推,鼠标一页页翻评论,翻到第 20 页眼睛已经花了,还是拿不准「大家到底是嫌贵还是嫌质量差」。这个标题把「淘宝京东爬虫」和「情感分析」拼在一起,本质就是一套能自动把几千条评论抓下来、再按好评/中评/差评情绪打分的评价分析系统。对毕设来说,它同时覆盖了爬虫技术、数据清洗、算法模型和 Web 展示四个模块,工作量饱满且技术栈完整;对从业者来说,它也是做竞品舆情监测、电商选品时最常用的低成本方案。全文我会按自己的实现习惯,从环境搭建讲到代码落地,最后把最磨人的反爬和情感分析翻车点一起交代清楚。
2. 爬虫框架选型与最小环境:requests、selenium 还是接口逆向
2.1 先分清淘宝和京东的反爬差异,再决定技术路线
很多初学者一上来就写requests.get('https://item.taobao.com/item.htm?id=xxx'),拿到一堆 HTML 后发现评论数据根本不在里面,这就是没有先做「数据位置调研」。淘宝和京东虽然都是电商平台,但评论数据的获取路径完全不同:
京东的商品评论走的是独立接口,productPageComments.action,返回的是结构化 JSON,字段清晰,连评分和追评都给好了。这个接口不需要登录就能访问,只要带上正确的 Referer 和 User-Agent,前几页基本能顺利拿到。
淘宝则复杂得多。商品详情页是异步加载的,评论数据在h5api.m.taobao.com/h5/mtop.taobao.rate.detail.get这个接口后面,但请求必须携带_sign签名参数,这个签名是前端 JS 加密生成的,每次都动态变化。想纯用 requests 模拟,得先逆向它的签名算法,对毕设来说周期太长。我的建议是:
- 京东评论:requests + 接口直连,简单高效,适合做主体功能;
- 淘宝评论:selenium 控制浏览器滚动加载,走「所见即所得」的路子,虽然慢但稳定,适合做补充数据源。
系统设计上,把两个平台的爬虫写成独立模块,统一输出成同一种数据格式,后面做情感分析和数据库存储就不用关心数据来自哪个平台了。这种「分层解耦」的思路也是答辩时容易加分的点。
2.2 最小环境配置:用 venv 隔离依赖,避免版本冲突
这个项目涉及的第三方库比较多,requests、selenium、pandas、snownlp、sqlalchemy、Flask,如果直接 pip 装到全局环境,很容易和系统自带的 Python 包打架。我习惯先用 venv 建一个独立环境:
python -m venv venv # Windows 激活 venv\Scripts\activate # macOS / Linux 激活 source venv/bin/activate pip install requests selenium pandas snownlp sqlalchemy flask提示:如果公司内网限制 PyPI 源,可以临时换用清华镜像源
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple 包名,但要注意这只是下载加速,不影响代码逻辑。
参数说明:python -m venv是 Python 3.3+ 自带的标准库,不需要额外安装;venv 目录建议放在项目根目录下,.gitignore里排除掉,不要提交到代码仓库。sqlalchemy是 ORM 框架,用来统一管理 MySQL 和 SQLite 的建表、插入操作,避免手写 SQL 的字符串拼接问题。
2.3 目录结构怎么摆:让毕设代码一眼能看懂
我见过很多毕设代码把爬虫、分析、可视化全塞进一个 app.py,几千行下来,导师打开就头疼。合理的目录结构应该让每个模块的职责一目了然:
product_review_system/ ├── crawlers/ │ ├── __init__.py │ ├── jd_crawler.py # 京东评论采集 │ └── tb_crawler.py # 淘宝评论采集(selenium) ├── analysis/ │ ├── __init__.py │ └── sentiment_analyzer.py # 情感分析模块 ├── storage/ │ ├── __init__.py │ ├── database.py # SQLAlchemy 连接与表结构 │ └── models.py # ORM 模型定义 ├── web/ │ ├── __init__.py │ └── app.py # Flask 展示层 ├── data/ # 原始 JSON 和 CSV 存放目录 ├── requirements.txt └── run.py # 主入口模块之间通过函数调用衔接,比如crawlers.jd_crawler.get_reviews(product_id, pages)返回一个list[dict],之后交给analysis.sentiment_analyzer去计算情感分。这样拆分的好处是:答辩时导师问「爬虫挂了会怎样」,你可以直接说「不影响已入库数据的分析」,因为数据落库和情感分析是两个独立流程。
3. 京东淘宝评论采集实战:从接口参数到数据入库
3.1 京东评论接口:一条 requests 直连的可靠路径
京东评论接口的基础 URL 是https://club.jd.com/comment/productPageComments.action,它是 GET 请求,关键参数如下:
| 参数名 | 含义 | 建议值 |
|---|---|---|
callback | JSONP 回调函数名 | fetchJSON_comment98 |
productId | 商品 ID | 从商品链接的100012043978中提取 |
score | 评论类型筛选 | 0表示全部,1表示好评,2中评,3差评 |
sortType | 排序方式 | 5按时间排序,6按热度排序 |
page | 页码 | 从0开始 |
pageSize | 每页条数 | 固定为10,不需要改 |
isShadowSku | 是否影子 SKU | 0 |
第一次请求时需要带上Referer: https://item.jd.com/{productId}.html,否则很容易触发风控。代码逻辑不复杂:
import requests import json import time def fetch_jd_reviews(product_id, max_pages=10): """ 抓取京东商品评论 :param product_id: 京东商品ID,从链接中提取 :param max_pages: 最大抓取页数,每页10条 :return: list[dict] """ base_url = "https://club.jd.com/comment/productPageComments.action" headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "Referer": f"https://item.jd.com/{product_id}.html", "Accept": "application/json, text/javascript, */*; q=0.01", } results = [] for page in range(max_pages): params = { "callback": "fetchJSON_comment98", "productId": product_id, "score": "0", "sortType": "5", "page": page, "pageSize": "10", "isShadowSku": "0", "fold": "1", } try: resp = requests.get(base_url, headers=headers, params=params, timeout=10) resp.raise_for_status() text = resp.text # 接口返回 JSONP 格式,需要去掉前缀 callback 和末尾的分号 json_str = text.replace("fetchJSON_comment98(", "").rstrip(");") data = json.loads(json_str) comments = data.get("comments", []) for c in comments: results.append({ "platform": "jd", "product_id": str(product_id), "user_name": c.get("nickname", ""), "content": c.get("content", ""), "score": c.get("score", 5), # 京东评分是1-5星 "comment_time": c.get("creationTime", ""), "like_count": c.get("usefulVoteCount", 0), }) print(f"第{page + 1}页抓取成功,获取{len(comments)}条评论") except requests.exceptions.RequestException as e: print(f"第{page + 1}页请求失败: {e}") break time.sleep(2) # 每页间隔2秒,给服务器喘息时间 return results逻辑说明:callback参数返回的是 JSONP 格式,不是标准 JSON,所以必须用字符串替换把前缀fetchJSON_comment98(和末尾的);去掉,再交给json.loads解析。score=0表示抓全部评价,如果你只想分析差评,可以改成 3,这样抓到的数据量更集中。time.sleep(2)很关键,京东对高频访问的风控阈值比想象中低,连续快速请求到第 5 页就可能返回{"code": "500"}。
参数调整建议:max_pages不要一上来就设 50。先用 3 页测试,确认接口字段解析正常后再逐步加大。京东某些自营商品评论超过 10 万条,全量抓取对毕设来说既没必要也不现实,抓前 10 页代表性样本即可。
3.2 淘宝评论采集:selenium 滚动加载的稳定方案
淘宝评论接口的_sign参数涉及 JS 逆向,对于商品评价系统这类偏应用的项目,我推荐用 selenium 走 UI 自动化,稳定而且代码量更少。核心思路是:打开商品详情页 → 点击「累计评价」Tab → 不断滚动页面触发懒加载 → 抓取评论节点文本。
import time from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.chrome.options import Options from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC def fetch_taobao_reviews(item_url, max_scrolls=15): """ 通过 selenium 滚动加载抓取淘宝评论 :param item_url: 淘宝商品完整链接 :param max_scrolls: 最大滚动次数 """ options = Options() options.add_argument("--disable-blink-features=AutomationControlled") options.add_experimental_option("excludeSwitches", ["enable-automation"]) options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36") driver = webdriver.Chrome(options=options) results = [] try: driver.get(item_url) wait = WebDriverWait(driver, 10) # 点击「累计评价」标签,触发评论 Tab 加载 wait.until(EC.element_to_be_clickable((By.XPATH, "//*[contains(text(), '累计评价')]"))).click() time.sleep(3) for _ in range(max_scrolls): driver.execute_script("window.scrollTo(0, document.body.scrollHeight)") time.sleep(1.5) # 滚动到页面底部的「查看更多」按钮并点击,加载更多评论 try: more_btn = driver.find_element(By.XPATH, "//*[contains(text(), '查看更多')]") if more_btn.is_displayed(): more_btn.click() time.sleep(2) except Exception: pass # 提取当前已加载的评论区内容 comment_items = driver.find_elements(By.CSS_SELECTOR, ".tm-m-item") for item in comment_items: content_el = item.find_element(By.CSS_SELECTOR, ".tm-m-item-comment-content") time_el = item.find_element(By.CSS_SELECTOR, ".tm-m-item-info .time") results.append({ "platform": "taobao", "product_id": item_url.split("id=")[1].split("&")[0], "content": content_el.text.strip(), "comment_time": time_el.text.strip(), }) print(f"共抓取淘宝评论 {len(results)} 条") except Exception as e: print(f"淘宝抓取异常: {e}") finally: driver.quit() return results逻辑说明:淘宝页面是懒加载模式,评论只在滚动到可视区域时才渲染。window.scrollTo(0, document.body.scrollHeight)把页面滚到底部,然后查找「查看更多」按钮继续加载,如此循环。excludeSwitches和disable-blink-features是为了降低 selenium 被检测到的概率,但淘宝风控策略经常更新,后面避坑章节我会细说。
注意:
tm-m-item这个 CSS 选择器是淘宝 PC 端当前版本的类名,平台改版后可能会失效。如果代码跑不通,优先用开发者工具检查评论节点的真实类名,不要死记硬背。
3.3 统一数据格式与入库:SQLAlchemy 建表写入
两个爬虫模块返回的字段不完全一致,京东有score和like_count,淘宝没有。为了让情感分析模块不必区分平台,入库前要先做字段对齐,缺失字段填默认值:
from sqlalchemy import create_engine, Column, String, Integer, Text, DateTime from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker Base = declarative_base() class Review(Base): __tablename__ = "reviews" id = Column(Integer, primary_key=True, autoincrement=True) platform = Column(String(20)) # jd / taobao product_id = Column(String(64), index=True) user_name = Column(String(100), default="") content = Column(Text) # 评论文本,用 Text 类型存放长文本 score = Column(Integer, default=5) # 京东星级 1-5,淘宝没有则默认5 comment_time = Column(DateTime) sentiment_score = Column(String(50), default="") # 情感分析后回填 # 使用 SQLite 起步,后续可无缝切换 MySQL engine = create_engine("sqlite:///reviews.db") Base.metadata.create_all(engine) Session = sessionmaker(bind=engine) def save_reviews(review_list): """批量写入评论数据""" session = Session() try: for r in review_list: review = Review(**r) session.add(review) session.commit() except Exception as e: session.rollback() print(f"数据写入失败: {e}") finally: session.close()逻辑说明:create_engine("sqlite:///reviews.db")表示用本地 SQLite 起步,毕设演示完全够用。如果以后要换 MySQL,只需要把连接串改成create_engine("mysql+pymysql://用户名:密码@localhost/库名?charset=utf8mb4"),ORM 模型不用动。这就是用 SQLAlchemy 而不是裸sqlite3的好处——数据库迁移成本几乎为零。
参数说明:content字段用Text而不是String(255),因为有的用户评论能写五六百字,String 长度不够会直接抛异常。sentiment_score字段先留空,等情感分析跑完再回填,这样数据表和算法逻辑解耦,别人读代码时更容易理解流程。
4. 情感分析落地:借助 SnowNLP 与词典修正把评论转成分数
4.1 为什么选 SnowNLP 而不是大模型:毕设场景的资源约束
很多人一听到情感分析就想到 BERT、多模态大模型,但对商品评价系统来说,大模型推理需要 GPU 或云 API,且标注语料成本高。SnowNLP 是纯 Python 实现的朴素贝叶斯情感分类库,默认训练语料偏社交评论领域,对电商评论准确率在 60% 左右,配合自定义词典修正后能提升到 75% 以上,对于毕设展示和中小规模数据分析完全够用。
如果追求更好的效果,常见做法是:先用 SnowNLP 跑粗粒度情感分,再引入一个简单规则层(针对「质量差」「物流慢」「客服没耐心」这类强负面词做加权修正)。这样既保留了算法的可解释性,又能让最终分数在业务上说得通。直接上 BERT 的话,光是数据标注就要花一周,答辩时导师可能更关心你「为什么选这个方案」,而不是你「跑了个多大的模型」。
4.2 情感打分实现:粗粒度分数 + 程度副词加权
SnowNLP 的sentiments属性返回一个 0 到 1 之间的概率值,越接近 0 表示越负面,越接近 1 表示越正面。但它对电商语料的判断经常过于乐观,比如「质量还行吧」能给你打出 0.8,实际上这句话是中性偏负。所以需要引入一个修正层:
from snownlp import SnowNLP import re # 自定义负面词表,按严重程度设置权重 NEGATIVE_WORDS = { "差": 1.2, "烂": 1.5, "垃圾": 2.0, "退货": 1.8, "售后": 1.3, "慢": 1.0, "破": 1.4, "假": 2.0, "异味": 1.5, "掉色": 1.5, "不推荐": 1.8, "别买": 2.0, "没用": 1.2, } POSITIVE_WORDS = { "好": 0.3, "不错": 0.4, "推荐": 0.5, "满意": 0.6, "耐用": 0.5, "快递快": 0.3, "好看": 0.4, } def analyze_sentiment(text): """对单条评论做情感分析,返回(情感分, 情感标签)""" if not text or len(text.strip()) < 4: return 0.5, "neutral" s = SnowNLP(text) base_score = s.sentiments # 原始概率 0~1 # 规则修正:扫描负面/正面词,调整分数 delta = 0.0 for word, weight in NEGATIVE_WORDS.items(): if word in text: delta -= weight * 0.1 for word, weight in POSITIVE_WORDS.items(): if word in text: delta += weight * 0.05 final_score = max(0.0, min(1.0, base_score + delta)) if final_score >= 0.6: label = "positive" elif final_score <= 0.4: label = "negative" else: label = "neutral" return round(final_score, 4), label逻辑说明:base_score是模型输出的原始概率,修正层通过遍历词表来调整分数。负面词的权重普遍大于正面词,原因是电商评论里负面表达的信息密度更高——「质量差」三个字基本决定了这条评论的态度,而「还行」「凑合」这类弱正面表达不应该把分数拉得太高。max(0.0, min(1.0, ...))保证分数始终在 0-1 区间内。
参数调整建议:NEGATIVE_WORDS的权重不要超过 2.0,不然一条包含「垃圾」的评论分数会直接跌到 0,失去区分度。不同品类的商品需要维护不同词表:手机类关注「卡顿」「电池」,服饰类关注「色差」「尺码」,可以在系统里做成可配置的 JSON 词表,每次分析时按品类加载对应词典。
4.3 把情感分回填数据库并做商品维度聚合
情感分析跑完后,需要把每条评论的sentiment_label回填到数据库,然后按商品 ID 聚合出好评率、中评率和差评率。这一步是评价系统从「一堆数据」变成「能看的结果」的关键。聚合逻辑用 pandas 处理最顺手:
import pandas as pd def aggregate_by_product(review_list): """ 从数据库读取评论并聚合出商品维度情感占比 :param review_list: 包含 sentiment_label 字段的评论列表 :return: DataFrame,每行一个商品的情感汇总 """ df = pd.DataFrame(review_list) if df.empty: return pd.DataFrame() # 用 pandas 交叉表统计每个商品的正/中/负评论数量 cross = pd.crosstab(df["product_id"], df["sentiment_label"]) for label in ["positive", "neutral", "negative"]: if label not in cross.columns: cross[label] = 0 cross["total"] = cross.sum(axis=1) cross["positive_ratio"] = cross["positive"] / cross["total"] cross["negative_ratio"] = cross["negative"] / cross["total"] cross = cross.sort_values("total", ascending=False) return cross逻辑说明:pd.crosstab生成的是「商品 ID × 情感标签」的频次表,total列用于计算占比。positive_ratio可以直观对比不同商品的用户满意度,比如 A 商品好评率 92%,B 商品只有 65%,选品时优先核心卖 A。这个结果既能展示在 Flask 界面里,也能导出成 CSV 交给运营。
整个过程跑下来,从爬虫到情感分析再到聚合展示是一条完整的流水线。但到这里只能说「能工作」,距离「稳定可靠」还差几步,下面把最容易让新手翻车的几个坑单独拿出来说。
5. 血泪避坑:反爬、字体加密、数据对齐那些最磨人的点
5.1 京东高频请求触发风控:返回 500 甚至直接封 IP
现象:按照上面的代码抓京东评论,前三页一切正常,到第 5 页开始返回{"code": "500"},再往下请求直接提示「请稍后再试」,甚至整个 IP 被临时限制访问。
原因:京东对/comment/这个路径的风控是动态的。短时间连续请求同一商品超过 30 次,或者同一个 IP 在 5 分钟内请求超过 50 次,就会触发风控。
解决:一是把max_pages控制在 10 页以内,每页间隔时间从 2 秒提高到 4-8 秒;二是引入random随机延时,不要让请求间隔呈现严格的等差数列;三是准备 3-5 个不同的 User-Agent 轮换使用。如果只是毕设演示,建议限制抓取总量在 2000 条以内,既够分析又能避免触发反爬。
5.2 淘宝评论数字是图片字体:中文能抓,销量和评分全是乱码
现象:用 selenium 抓淘宝商品评价统计区,发现「累计评价 1.2万+」变成了累计评价 1.2万+,或者点评赞数字在页面上正常,但用element.text取出来是乱码字符。
原因:淘宝使用了自定义字体映射,数字和部分汉字不是标准的 Unicode,而是通过@font-face动态加载的字体文件渲染出来的,爬虫抓取文案时只拿到了字体编码对应的私有码位,显示为异常字符。
解决:对于评论正文来说,基本不会遇到字体加密,因为正文是标准文本;出问题的多是「评价数量」「销量」这类统计数字。做法是直接跳过用文本提取这些数字,改为从页面 HTML 的>NEGATION_PATTERN = re.compile(r"(不|没|别|别买|没用)(好|行|错|推荐|满意|耐用|舒服|划算)") def _apply_negation_bonus(text, score): if NEGATION_PATTERN.search(text): return max(0.0, score - 0.3) return score
正则的逻辑是:匹配「不好」「不行」「没推荐」「别买」这类否定词组合,一旦命中,说明模型原始分数高概率是误判,直接减去 0.3 的修正值。这能在不显著影响正常评论的前提下,把「负面评论被当成正面」的比例降下来。我自己的测试数据里,加入这层修正后差评识别准确率从 61% 提升到了 74%。
5.6 同一商品评论重复入库:幂等性设计
现象:爬虫脚本跑了两轮,数据库里同一条评论出现了三次,情感聚合时评论总数虚高,好评率被稀释。
原因:没有做唯一性约束。京东评论没有全局 ID,但同一用户在同一商品下的评论内容是唯一的。
解决:在Review模型中加入联合唯一索引,以platform + product_id + user_name + content作为唯一标识:
from sqlalchemy import UniqueConstraint class Review(Base): __tablename__ = "reviews" __table_args__ = ( UniqueConstraint("platform", "product_id", "user_name", "content"), ) # 其余字段不变这样即使爬虫重复执行,数据库层面也会拦截重复写入。对于 MySQL 用户需要注意的是,Text类型字段不能直接建唯一索引,需要把content改成String(512)或者再增加一个content_hash字段存 MD5 值,用哈希值参加唯一约束,实际项目中我更推荐后者。
6. 让评价系统真正可用:验证技巧、可视化与下一步优化
6.1 先验证再扩展:用 50 条人工标注评估系统可靠性
情感分析的准确率不能拍脑袋说。最稳妥的办法是从两个平台各抽 50 条评论,自己人工打标签(positive / neutral / negative),然后用脚本对比模型预测结果,算一致率。这个过程可以用sklearn.metrics快速实现:
from sklearn.metrics import accuracy_score, classification_report y_true = ["positive", "negative", "positive", "neutral"] # 人工标注 y_pred = ["positive", "negative", "negative", "neutral"] # 模型预测 print(accuracy_score(y_true, y_pred)) print(classification_report(y_true, y_pred))accuracy_score给出整体准确率,classification_report分别展示三个类别的精确率和召回率。如果召回率明显偏低,说明正面评论的大量样本被模型误判为中性,这时候优先调整正向词表的权重,而不是盲目增加负面词。这个验证步骤在毕设文档中单独成节,是所有评委都认可的专业度体现。
6.2 用 Flask 快速搭一个可视化看板
爬虫和分析完成后,最后一步是把结果展示出来。用 Flask 写一个单页面,读取数据库聚合结果,用条形图对比不同商品的情感得分占比。如果你不想引入太重的前端图表库,直接在模板里用 CSS 画横向条形图就行,数据传递交给 Jinja2 模板引擎,整个可视化模块在 100 行代码内可以搞定。
6.3 进阶方向:多模态情感分析与商品画像
如果时间充裕,值得扩展的方向有:一是引入图片评论的情感分析,京东和淘宝都有晒图功能,一张差评配图的信息量往往大于背景在文字里,这就是近年来热门的「多模态情感分析」在电商场景里的实际应用;二是跨商品对比模式,不止看单个商品的好评率,还要看消费者对「价格」「质量」「物流」三个维度的评价占比,这需要把评论内容做关键词分类,再逐类做情感统计,本质上是从「这个商品好不好」升级为「这个商品具体哪里好/哪里差」。
我的习惯是把每日增量爬取任务挂到 cron 上,每周四跑一次增量数据,再重新聚合情感趋势,观察竞品好评率变化。这套系统从第一天起到现在,帮我把以前需要半天才能看完的评论数据压缩到了十分钟内的一个看板。做技术项目最重要的不是跑通,而是知道每一次「看起来玄学的失败」背后都有一个可以定位的原因——多抓几次,多抓几个平台,你对反爬和数据处理的体感就会完全不一样。希望这份踩坑笔记能帮到你,少走我当年走过的弯路。
本文还有配套的精品资源,点击获取