
简介面向计算机相关专业毕业设计或课程设计场景这是一套基于Scrapy、Flask、ECharts与Jieba的亚马逊商品评价采集与情感分析完整项目。系统划分为数据采集、数据分析与展示三大模块采集模块按页面元素规则抓取评价标题、用户、时间、内容、评分等字段并写入数据库存储分析模块使用分词工具统计词频借助深度学习模型训练好的权重文件对评论文本进行情感倾向判别展示模块提供Web页面绘制词云和图表直观呈现商品评价特征。同时针对反爬虫场景配置随机浏览器标识池以降低封禁风险并给出工程化设置方式。资源共140个文件主要包含21个Python源码、数据库脚本、网页前端文件、模型权重与词向量数据大小81.53MB。已有135人学习浏览适合具有一定Python基础的学生作为毕业设计起点或在此基础上二次开发数据库和模型文件均已提供便于快速复现实验。1. 为什么选爬取商品评价情感分析当毕设不是因为它简单是因为它完整打开任何一个代码下载站搜索“爬取商品评价并进行情感分析”返回的结果基本都是“毕业设计源代码文档说明数据库”这个配置。这类项目在毕设里属于常青树原因很朴素它把 Python 爬虫、文本处理、机器学习、数据库、可视化整条链路走了一遍工作量看得见答辩时也能讲出东西。我拆过好几个同类项目发现真正让学生卡住的地方不在地图或算法而在三个不起眼的位置——请求头伪装、编码清洗、情感模型的中性偏置。这篇笔记会把一套可落地的商品评论爬取与情感分析流程拆开细讲先拿到页面数据再清洗入库然后做情感判别最后把结果汇总成图表。适合正在做 Python 方向毕设、或者想把爬虫和 NLP 串起来的读者。我不会只贴代码会把每一步的选型理由和翻车点写出来照着走能省不少查资料的精力。2. 爬虫采集层requestsXPath 拿“评论内容评分时间”先把数据搞干净2.1 先看清页面结构再动手XPath 比正则更适合评论区块我拆过好几个评价采集项目第一版大多卡在同一个地方——拿正则去抠评论内容结果换一个商品页面就全废了。商品评价页的 DOM 结构相对规整每个评价块都挂在同一个 class 容器下这种场景用 XPath 定位比正则稳得多。项目里常见的做法是先抓一页把评价列表里反复出现的 class 名记录下来然后按块提取。以某电商平台商品评价页为例评价块通常长这样外层容器是div.comment-item里面的文本节点在div.comment-detail评分是span.comment-star的>import requests from lxml import etree import time def fetch_comments(page_url, headers, max_retries3): for attempt in range(max_retries): try: resp requests.get(page_url, headersheaders, timeout10) if resp.status_code ! 200: # 有些页面会返回 302 跳登录这里先打日志方便排查 print(f[retry {attempt1}] status{resp.status_code}, url{page_url}) time.sleep(2) continue tree etree.HTML(resp.text) items tree.xpath(//div[contains(class, comment-item)]) comments [] for item in items: content item.xpath(.//div[contains(class, comment-detail)]/text()) star item.xpath(.//span[contains(class, comment-star)]/data-star) date item.xpath(.//span[contains(class, comment-date)]/text()) comments.append({ content: content[0].strip() if content else , star: star[0] if star else , date: date[0].strip() if date else }) return comments except Exception as e: print(f[error] {e}, retry {attempt1}/{max_retries}) time.sleep(2) return [] headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Accept: text/html,application/xhtmlxml, Accept-Language: zh-CN,zh;q0.9, Referer: https://example.com/ } page_url https://example.com/product/comments?page1size20 comments fetch_comments(page_url, headers) print(f抓取到 {len(comments)} 条评论)爬取逻辑本身不复杂重点在于三件事请求头、重试、超时。User-Agent必须带上很多站点的反爬策略第一步就是拦截裸requests默认头timeout10是防止某个页面卡死拖慢整个采集max_retries应对偶发的 5xx 错误。2.2 翻页策略和请求间隔别把频率调得太激进商品评价页的翻页参数一般是page或pageNum每页评论数size也有讲究。写死page1只能拿到第一页后面全凭循环拼接 URL。常见做法是先抓第一页从返回的 HTML 里读出总页数。总页数通常在div.page-total里正则或 XPath 都能取到取到之后范围循环就行。这里有个大多数项目都会踩的坑控制请求间隔。商品评价页的数据量不小几百页开抓时如果 sleep 设成 0.1 秒很容易触发频控导致中途大量 503。我一般把间隔放在 23 秒抓 500 页大概 25 分钟这个速度在毕设场景里完全能接受。下载下来的数据先落 CSV不要直接进数据库——CSV 相当于一次快照备份后面清洗和入库可以反复重跑不需要重新抓页面。保存 CSV 时建议用utf-8-sig编码而不是utf-8。Windows 下 Excel 直接打开utf-8文件会乱码utf-8-sig带的 BOM 头能让 Excel 正确识别中文。这个坑在答辩演示时特别容易爆发数据明明没问题打开表格一片乱码解释起来很尴尬。import csv def save_to_csv(comments, filenamecomments.csv): with open(filename, a, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnames[content, star, date]) writer.writeheader() writer.writerows(comments) # 追加写入断点续跑时不会覆盖已抓数据 save_to_csv(comments)a模式是追加适合分页循环里每抓完一页调一次。如果你用的是w模式第二次循环会用新数据覆盖文件这点务必注意。断点续跑的需求在毕设项目里很常见评论数据量大以后中途频控断掉是常态能接着跑比重新抓一遍省太多时间。2.3 清洗阶段去掉空白评论、表情符和“此用户未填写评价”抓下来的评论不干净是常态。最常见的三种脏数据纯空格评论、默认占位文本“此用户未填写评价”这类、带大量 Emoji 的长文本。Emoji 在后续做情感分析时基本是噪声SVM 或朴素贝叶斯对单个字符级别的噪声很敏感保留反而拉低准确率。清洗时统一处理空内容直接丢弃长度小于 2 的丢弃Emoji 用正则去掉。import re def clean_comment(raw_text): if not raw_text: return None text raw_text.strip() if len(text) 2: return None if text in (此用户未填写评价, 该用户没有填写评价): return None # 去掉 emoji 和特殊符号保留中文、英文、数字、常见标点 text re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9。、\\ ], , text) return text.strip()清洗函数返回值用None标记“要丢弃”比直接返回空字符串更好判断。空字符串可能是合法数据None语义上就是“这条不要了”。批量清洗时统计一下清洗前后的条数差异如果过滤率超过 30%说明页面定位可能选错了容器把一些非评论节点也抓进来了需要回去检查 XPath。清洗完的数据建议再存一份comments_clean.csv原始抓取文件保留不动。后续训练情感模型需要人工标注数据干净版本直接用来标注原始版本留着追溯两个文件分开逻辑清晰也不容易误删。3. 情感分析模块从快速打分到自训练模型两条路线都给你写清楚3.1 先用 SnowNLP 跑一版为什么它经常把中性评论判成负面SnowNLP 在毕设项目里出镜率极高安装方便调用简单。它内置的模型基于电商购物评论训练和商品评价场景天然契合。基础用法一句话就能跑通from snownlp import SnowNLP text 快递很快包装严实但商品本身质量一般 s SnowNLP(text) print(s.sentiments) # 0.0 ~ 1.0越接近 1 越正面问题出在情感中性偏置上。SnowNLP 内置模型对否定句、转折句处理得比较粗糙。“包装严实但质量一般”这种句式经常被整体打成负面而且分值特别低。实际抓来的商品评价里中性和混合情感占比很高直接套用 SnowNLP 会让负面评价大量虚高。这一点在答辩时容易被问你的模型对“但/不过/就是”这种转折句的处理依据是什么如果答不上来整个模块的可信度就垮了。对没有标注数据的学生来说SnowNLP 作为快速基线是合格的。跑完输出一个sentiment_score字段0~0.4 归为负面、0.4~0.6 归为中性、0.6~1 归为正面先用这个结果把整个流程走通后面再迭代模型。代码里我会写清映射规则方便后面替换成自训练模型。3.2 用 TF-IDF朴素贝叶斯训练自己的分类器1000 条标注就能超过 SnowNLP如果想在答辩时讲出深度建议做一版自己的情感分类器。不需要上 BERT 或 LSTMTF-IDF 加朴素贝叶斯在商品评论这种短文本上完全够用。中文分词用 jieba向量化用scikit-learn的TfidfVectorizer分类器用MultinomialNB。整条链路都是教材级方案评委不会觉得看不懂但也足够展示你对文本分类流程有完整理解。第一步是造标注数据。从清洗后的评论里随机抽 10002000 条人工打上标签1 代表正面、0 代表负面、2 代表中性。实话实说这是个苦力活但不会白费。这份标注数据可以做三件事训练分类器、计算准确率、展示你对数据质量的把控。import jieba from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.naive_bayes import MultinomialNB from sklearn.model_selection import train_test_split # 假设 comments 是 [(text, label), ...] texts [c[0] for c in labeled_data] labels [c[1] for c in labeled_data] # 用 jieba 做分词再把分词结果用空格拼回去 def tokenize(text): return .join(jieba.cut(text)) corpus [tokenize(t) for t in texts] vectorizer TfidfVectorizer(max_features5000) X vectorizer.fit_transform(corpus) X_train, X_test, y_train, y_test train_test_split( X, labels, test_size0.2, random_state42, stratifylabels ) clf MultinomialNB(alpha0.1) clf.fit(X_train, y_train) print(f测试集准确率: {clf.score(X_test, y_test):.3f})几个参数说清楚。max_features5000限制特征维度中文评论去重后词表庞大全量特征会让矩阵过稀疏5000 词覆盖高频情感词足够。alpha0.1是拉普拉斯平滑系数调小一点能减少对高频无意义词的偏置默认1.0在评论分类场景有时会偏向把短评论判成中性。stratifyy_train保证训练集和测试集里正负中三类比例一致防止随机切分把某个类别全切到测试集里。训练完保存模型和向量化器后面预测新数据时不用重新训练。这里注意一个细节保存要用joblib或pickle且必须同时保存vectorizer和clf两个对象只存分类器不存向量化器预测时特征对不上直接报维度错误。import joblib joblib.dump(clf, sentiment_model.pkl) joblib.dump(vectorizer, tfidf_vectorizer.pkl)调用时按顺序加载vectorizer.transform()先做向量化再传给clf.predict()。很多同学在这个环节翻车加载模型后直接拿原始字符串去predict报错才想起来漏了向量化步骤。预测时同样要经过 jieba 分词再拼接和训练时的预处理保持一致差一步结果都会偏。我拆过的项目里多数是用 1500 条左右标注数据测试集准确率能跑到 0.820.88。SnowNLP 在这个量级的数据上大概 0.7 上下。自训练模型的核心优势不是准确率高那十几个点而是你能解释每个参数为什么这么设、数据怎么标、误差从哪里来这些内容在答辩 PPT 里每一页都能展开讲。4. 数据持久化与结果展示从 CSV 到 MySQL再到图表面板4.1 建表设计评论表字段别贪多够用就行数据库这层在毕设里主要承担两个功能证明你懂 MySQL 基本操作以及给可视化模块提供结构化数据。设计三张表就够了product商品表、comment评论表、sentiment_result情感结果表。如果项目简介里提到数据库附件通常是把这三张表的建表 SQL 和少量样例数据一起放在db/目录下。评论表是核心字段设计遵循“够用就行”原则不要一开始就留十几个字段。我见过一版建了comment_id, product_id, user_id, content, star, publish_time, is_purchase, reply_count, like_count, sentiment实际用到后半程的不超过六个。字段太多反而让插入代码变得啰嗦。下面给出精简单表结构CREATE DATABASE IF NOT EXISTS comment_sentiment DEFAULT CHARSET utf8mb4; CREATE TABLE IF NOT EXISTS comment ( id INT AUTO_INCREMENT PRIMARY KEY, product_id INT NOT NULL, content TEXT NOT NULL, star TINYINT DEFAULT 0, publish_time DATETIME, sentiment TINYINT DEFAULT -1, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_product (product_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;utf8mb4必须用因为评价内容里可能出现生僻字。utf8在 MySQL 里只支持三个字节部分中文和大部分特殊字符存不进去插入时会直接报错。sentiment字段存 0/1/2 三个值分别对应负/正/中用TINYINT比VARCHAR省空间查询也快。INDEX idx_product是为后面按商品维度统计情感分布准备的。4.2 pymysql 批量写入把插入速度从“龟速”提上来写完表结构接下来是数据接入。用pymysql的executemany做批量插入比循环单条插入快一个量级。5000 条评论实测execute循环插入要 30 多秒executemany批量插入不到 1 秒差距明显。毕设答辩时如果被问“数据量大了怎么优化”这个点可以直接答出来。import pymysql connection pymysql.connect( hostlocalhost, userroot, passwordyour_password, databasecomment_sentiment, charsetutf8mb4 ) sql INSERT INTO comment (product_id, content, star, publish_time, sentiment) VALUES (%s, %s, %s, %s, %s) batch_data [ (1001, row[content], row[star], row[date], row[sentiment]) for row in cleaned_comments ] with connection.cursor() as cursor: cursor.executemany(sql, batch_data) connection.commit() connection.close()这里有个日常写毕设极易忽略的点connection.commit()千万别漏。pymysql默认不自动提交事务不执行 commit 数据不会真正落库。很多同学跑完代码没报错打开 Navicat 一看一张空表回头排查半天最后发现是少写一行 commit。去重逻辑建议放在插入前处理。给content字段加唯一索引需要谨慎因为不同用户可能发完全相同的文本比如都评论“不错”加唯一索引会误杀合法数据。更可靠的做法是先SELECT判断这个商品下是否已存在相同内容存在就跳过不存在才插。数据量在万级以下这个方案性能完全够。4.3 可视化展示情感分布饼图加趋势折线覆盖两个提问点可视化是毕设演示的加分环节。答辩时大概率被问“你的数据结果怎么呈现”一个带交互的 HTML 页面比 Jupyter Notebook 里画个图更有说服力。最常见的方案是Flask提供接口前端用ECharts渲染。这里给一个情感分布统计的接口示例from flask import Flask, jsonify app Flask(__name__) app.route(/api/sentiment_stats) def sentiment_stats(): sql SELECT sentiment, COUNT(*) FROM comment GROUP BY sentiment # 连接数据库执行查询 result {negative: 0, positive: 0, neutral: 0} for sentiment, cnt in query(sql): if sentiment 0: result[negative] cnt elif sentiment 1: result[positive] cnt elif sentiment 2: result[neutral] cnt return jsonify(result)前端拿到这个 JSON 后用 ECharts 的pie系列画一个情感占比饼图再用line系列画每天新增评论的情感趋势两个图就能撑起整个可视化模块。时间字段publish_time按DAY(publish_time)分组能得到每日评价量和情感得分均值。这两个图一个回答“整体口碑怎么样”一个回答“口碑随时间怎么变化”覆盖了答辩最常见的两个提问角度。5. 避坑指南写毕设最容易翻车的五个环节5.1 现象爬虫跑着跑着被 403/503 拦截数据中断这是爬虫项目里出现频率最高的问题。现象是前几十页正常之后连续报错返回状态码 403 或 503。原因通常是请求频率太高、同一 IP 短时间访问次数超过了站点阈值。解决方法是加请求间隔同时每次请求随机换一个 User-Agent。准备 20 个常用浏览器 UA 字符串每次随机挑一个放进headers能显著降低被识别为爬虫的概率。另外建议把抓到的数据尽快落 CSV就算中途被封已抓部分不会丢。5.2 现象CSV 打开中文乱码导师截图后非常尴尬现象是 Excel 打开comments.csv中文全部显示成乱码。原因是文件用utf-8编码保存Windows 环境下 Excel 默认按ANSIGBK解析。解决方法是保存成utf-8-sig编码它带的 BOM 头会让 Excel 自动识别为 UTF-8。这个坑不影响程序运行但影响演示效果属于看起来很严重但半分钟就能修好的问题。5.3 现象情感分析结果里负面评价特别多跟直觉明显不符现象是人工翻了几十条评论感觉大部分是好评但模型输出的负面占比超过一半。原因大概率是 SnowNLP 对中性表达和转折句的误判典型错误是把“还行”“一般般”“没有想象中好”全部归为负面。解决方式有两种如果时间允许标注数据训练自己的分类器这是根治方案如果赶进度至少把 0.40.6 的区间划分成中性不要只设单一阈值能缓解一部分误判。5.4 现象MySQL 插入报错Incorrect string value现象是插入评论内容时报错信息里有Incorrect string value数据完全没写进表里。原因是表或连接用了utf8字符集。MySQL 的utf8不是完整的 Unicode四个字节的特殊字符比如某些生僻字存不了。解决方法是建表时统一用utf8mb4连接时charsetutf8mb4也必须保持两张配置缺一个都会出问题。5.5 现象程序没报错但数据库里一张空表现象是爬虫跑完没有异常SELECT COUNT(*)却是 0。原因是很可能漏了事务提交。pymysql默认不开自动提交executemany之后没有connection.commit()数据停留在事务缓冲里进程结束后一切消失。解决方法是插入完成后显式调用connection.commit()再用connection.close()释放连接。每次操作数据库前先检查这两个调用是否存在能避免大量类似问题。6. 进阶校验用人工标注集算准确率再上词云看高频词让结果可解释模型训练完很多人直接拿全部评论跑一遍预测出个饼图就算收工。这在功能上是完整的但在答辩时容易被追问一个问题“你这个模型准确率多少”如果答不出来整个情感分析模块都会被打上问号。所以最后一个环节我会强制自己留出一部分人工标注数据作为测试集计算准确率顺便看一眼分错的样例集中在哪些句子上。from sklearn.metrics import classification_report, confusion_matrix y_pred clf.predict(X_test) print(classification_report(y_test, y_pred, target_names[负面, 正面, 中性])) print(confusion_matrix(y_test, y_pred))classification_report输出每个类别的精确率、召回率、F1 值。这三项指标比总准确率更能说明问题——比如正面评论识别得很好但中性评论被大量误判为负面从混淆矩阵一眼就能看出来。我自己的项目出现过类似情况总准确率 0.83 看起来不错但看混淆矩阵发现中性样本一半被分到负面说明阈值或特征还需要调整。另一个值得做的是高频词统计。把正负面评论分开分别做jieba分词统计词频后画词云。正面词云里高频词是“质量好”“速度快”“性价比”负面词云里是“质量差”“客服”“退货”这个结果能让情感分析模块显得更有说服力评委看到后基本不会再追问“你觉得你的模型靠不靠谱”。from collections import Counter def top_keywords(texts, top_n20): counter Counter() for t in texts: words jieba.lcut(t) counter.update([w for w in words if len(w) 2]) return counter.most_common(top_n) positive_texts [c[content] for c in comment_data if c[sentiment] 1] negative_texts [c[content] for c in comment_data if c[sentiment] 0] print(高频正面词:, top_keywords(positive_texts)) print(高频负面词:, top_keywords(negative_texts))词云做出来以后整个项目就从“黑匣子式的情感打分”变成了“可解释的文本分析闭环”采集端有清洗逻辑、模型端有准确率指标、展示端有高频词支撑。答辩时按这条线讲每一步都能往下追问每一步都有对应数据。这套流程走到最后我发现最有价值的并不是模型准确率有多高而是整个工程链路没有多余的盲区。从那以后我每做一个类似的毕设项目都会强制自己留出测试集跑一次classification_report哪怕只是看一眼混淆矩阵也不让“模型效果一般”这种话出现在答辩现场。这个习惯希望也能帮到你。本文还有配套的精品资源点击获取