☰
商品评价情感分析系统实战:从爬虫采集到数据库落地
2026/10/3 3:35:35 网站建设 项目流程

简介:这套基于Scrapy+Flask+ECharts+Jieba的亚马逊商品评价采集分析系统,是面向计算机相关专业学生及毕业设计人群的完整课题方案,覆盖数据采集、情感分析和可视化展示全流程。资源共140个文件,以Python后端代码(21个py)、前端样式与交互(16个css、26个js、10个html)、数据库脚本(sql)为核心,另含LSTM模型文件(h5、pkl)及说明文档等,压缩包约81.53MB,目录结构清晰,便于按模块查阅。已有135人浏览学习。系统通过Scrapy框架按Xpath规则爬取评价标题、内容、评分等字段并持久化至MySQL,利用Jieba分词完成词频分析,结合LSTM模型进行情感判断,最终以ECharts词云等图表直观呈现商品特征与受众分布,同时内置随机UserAgent策略应对反爬限制。下载后可直接运行调试,适合作为毕业设计、课程设计或项目立项的参考实现。

1. 爬取商品评价并进行情感分析:这套毕业设计到底在解决什么问题

围绕“爬取商品评价并进行情感分析”这套毕业设计,真正难的不是某一个算法,而是把链路完整走通:你先得稳定拿到评价原文,再让模型读出评论的情绪,最后把中间产物落进数据库,做到导师打开就能看、能查、能复现。很多人一上来就套BERT,结果数据只有两三百条,反而不如一个可解释的词典规则模型好用;也有人爬虫写得顺,却因为数据库字符集不对,入库全变成问号。这篇文章就把一套可落地的方案讲透,覆盖爬虫采集、情感建模、数据库设计与避坑记录,适合正在做电商评论方向毕设的同学,也适合想快速攒一个分析demo的从业者。

这套项目同时涉及网络爬虫、自然语言处理和数据库三块,每一块单独拿出来都不深,但在一个毕设里组合起来,恰恰是答辩时的亮点。读完你应该能回答一个问题:拿到“商品评价情感分析”这个题目,从需求分析到代码交付,每一步怎么选、怎么实现、遇到问题看哪里。

2. 商品评价如何落地采集:从页面结构分析到稳定入库的爬虫方案

2.1 先搞清楚评价数据从哪里来:静态页面还是异步接口

第一步必须先判断目标站点的评价数据是直接渲染在HTML里,还是通过XHR接口异步加载。这决定了整个采集模块的技术路线。判断方法很直接:打开浏览器的开发者工具,切到Network面板,刷新评价列表页,如果看到评价内容出现在初始HTML响应里,就是静态渲染,用requests加BeautifulSoup就能搞定;如果页面先显示骨架屏,评价是滚动或翻页后才出现的,那大概率是异步接口。

对异步接口的处理要优先于模拟浏览器。理由很简单,XHR接口返回的通常是JSON,结构规则,字段齐全,解析成本远低于在HTML里用CSS选择器猜测。找到那个返回评价列表的XHR请求,记下它的URL、请求头和翻页参数,后面写脚本时直接模拟这个请求。只有在接口加密严重、参数动态变化的情况下,才考虑退而求其次用浏览器渲染方案,但这类站点要提前评估好工作量。

存储设计上,评价数据至少要覆盖以下字段:评价内容、评分、评价时间、点赞数、是否追评。评分字段在后面的情感分析中非常重要,它会作为弱监督标签直接参与训练集构造。

2.2 用 requests + BeautifulSoup 写出第一个稳定抓取脚本

这里以一个结构脱敏过的商城评价列表页为例,它的评价是静态渲染,翻页靠URL末尾的page参数控制。抓取脚本核心代码如下:

import requests import time import random from bs4 import BeautifulSoup import pandas as pd HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ... Chrome/120.0.0.0", "Accept-Language": "zh-CN,zh;q=0.9", } def fetch_comment_page(product_id: str, page: int) -> list: """抓取某个商品某页评论,返回评论字典的列表""" url = f"https://example-mall.com/product/{product_id}/comment?page={page}" try: resp = requests.get(url, headers=HEADERS, timeout=10) resp.encoding = "utf-8" if resp.status_code != 200: print(f"第{page}页状态码异常: {resp.status_code}") return [] except requests.RequestException as e: print(f"第{page}页请求失败: {e}") return [] soup = BeautifulSoup(resp.text, "html.parser") comments = [] for item in soup.select(".comment-item"): # 评价条目的CSS选择器,按实际页面调整 content_el = item.select_one(".comment-content") rating_el = item.select_one(".comment-rating") time_el = item.select_one(".comment-time") comments.append({ "product_id": product_id, "content": content_el.get_text(strip=True) if content_el else "", "rating": rating_el.get("data-score") if rating_el else None, "comment_time": time_el.get_text(strip=True) if time_el else "", }) return comments if __name__ == "__main__": all_data = [] product_id = "1005001" for page in range(1, 6): # 先抓5页验证 data = fetch_comment_page(product_id, page) if not data: break all_data.extend(data) time.sleep(random.uniform(1.5, 3.5)) # 随机延时是基本礼貌 df = pd.DataFrame(all_data) df.to_csv("raw_comments.csv", index=False, encoding="utf-8-sig") print(f"共抓取 {len(df)} 条评价,保存到 raw_comments.csv")

这段代码有三处值得细说。第一是异常处理,网络请求必须捕获RequestException,评价页的HTML结构偶尔会变,如果某个字段解析不到也要保证能跳过而不是让整个脚本崩溃。第二是随机延时,random.uniform(1.5, 3.5)的意思是每次请求之间随机等1.5到3.5秒,这不仅是为了降低目标服务器压力,也是防止请求频率过高直接被封掉。第三是保存格式,utf-8-sig编码是为了让生成的CSV文件用Excel打开时不乱码,这个细节很多第一次写爬虫的人会踩坑。

2.3 遇到动态加载怎么处理:异步接口与模拟浏览器的选择

如果你的数据源是异步加载,第一步绝不模拟浏览器,而是先找接口。打开Network面板,清空请求记录,点击下一页或下拉加载,找到名类似getCommentList的XHR请求,直接看它的响应内容。如果响应是JSON且包含评价文本,那你只需要把上面代码里的URL换成接口地址,参数里加上page、productId即可。

# 异步接口抓取示例:JSON响应解析 def fetch_comment_api(product_id: str, page: int) -> list: url = "https://example-mall.com/api/comment/list" params = {"productId": product_id, "page": page, "pageSize": 20} resp = requests.get(url, params=params, headers=HEADERS, timeout=10) data = resp.json() items = data.get("data", {}).get("list", []) return [{ "content": item["content"], "rating": item.get("score"), "comment_time": item.get("createTime"), "likes": item.get("likeCount", 0), } for item in items]

接口路径和字段名显然要做对应修改,但抓取结构就是“带参数请求接口、解析JSON、抽字段”三步。相比解析HTML,JSON的字段定位明确,不易被页面样式变动连累,所以优先程度很高。如果目标接口有签名参数,比如token、sign,那要先分析它的JS生成逻辑,这一步可以反向挑战“看JS、补参数”。确认自己就是搞不定签名,再退到浏览器渲染路线。

注意:模拟浏览器不是万能的。它会显著增加内存占用,而且页面改版时要同步改代码。我的习惯是:能从接口拿数据绝不开浏览器,只有接口彻底无解才用Selenium或Playwright。

2.4 字段设计:一个评价记录最少要留下哪几个字段

爬虫最怕只盯着“评价内容”抓,结果后面做分析和答辩时才想起来缺字段。我的建议是字段宁多勿少,原始评价至少保存:商品ID、商品标题、评价内容、评分、评价时间、点赞数、是否追评、用户等级或购买属性。其中商品ID是关联商品表和评价表的外键,评分和评价时间这两个字段,在情感分析环节是用来构造弱监督标签和时间趋势图的核心依据。

字段的格式也要先想好。评分如果是“5分”这种字符串,入库前要转成整数;时间如果带时区或中文格式,最好统一走ISO标准格式存到数据库。这类清洗看似琐碎,但统一做在前面,后面情感分析、可视化阶段会非常省心。我一般会在爬虫里顺便把score字段转成int,把时间字段用pandas的to_datetime处理一下,避免入库时报错。

# 字段清洗示例:评分、时间的规范化 df["rating"] = pd.to_numeric(df["rating"], errors="coerce") df["comment_time"] = pd.to_datetime(df["comment_time"], errors="coerce") df = df.dropna(subset=["content", "comment_time"])

3. 情感分析模型怎么选:从词典法到预训练模型的完整落地路径

3.1 为什么先搭情感词典基线:可解释且能当答辩素材

情感分析模型的选型经常让人纠结,但毕业设计有个天然的评价尺度:可解释性强、能说清楚每一步在干什么。所以我建议先用词典法搭一个最简基线,而不是直接上深度学习。词典法的核心逻辑是给每个情感词打正负分,把评论里所有词的分值累加,分数大于零判为正向,小于零判为负向。

# 基于情感词典 + 否定词/程度副词的打分函数 import jieba pos_words = set(open("pos_words.txt", encoding="utf-8").read().splitlines()) neg_words = set(open("neg_words.txt", encoding="utf-8").read().splitlines()) negation_words = {"不", "没", "无", "没有", "并非"} degree_words = {"很": 1.5, "太": 1.8, "非常": 2.0, "比较": 1.2, "有点": 0.8} def simple_sentiment_score(text: str) -> float: words = jieba.lcut(text) score = 0.0 negate = False degree = 1.0 for w in words: if w in negation_words: negate = True elif w in degree_words: degree = degree_words[w] elif w in pos_words or w in neg_words: base = 1.0 if w in pos_words else -1.0 if negate: base = -base negate = False score += base * degree degree = 1.0 return score

这段代码里有两个容易忽略的点。第一个是“不”和“好”连续出现时,如果不反转“好”的符号,“不好”会被算成正向分;加了negate标记后,单字级别的反转逻辑就能覆盖大部分否定场景。第二个是程度副词的乘数,“很不满意”和“不满意”的情绪强度明显不同,degree_word把“很”的系数提到1.5,分数就拉开了。词典文件本身可以用开源的通用情感词典做底,再手动补充电商高频词汇,比如“物流贼慢”、“客服垃圾”里的“贼慢”、“垃圾”,这种领域词是通用词典覆盖不到的。

基线跑通之后,你已经能拿到每个商品的正负向占比,能画出最基础的情感分布饼图。这时候哪怕后面不往下做,论文里也已经有了“基于情感词典的规则方法”这一节。这个基线的价值,是用最少的计算量换一份可解释的对照结果。

3.2 基于 jieba 分词 + TF-IDF + 朴素贝叶斯的分类实现

词典法的短板很明显,它不识别句法结构,也没法捕捉“包装精美但质量不行”这种转折。所以第二步建议升级到机器学习方案。先把爬到的评论做人工标注,做法是利用评分字段弱标注,4到5分标正向,1到2分标负向,3分先放弃不要。这个弱标注不需要太干净,够训练模型就行。

# 构造训练集:评分弱标注 + 人工抽检 train_df = df[df["rating"].notna()].copy() train_df["label"] = train_df["rating"].apply(lambda x: 1 if x >= 4 else (0 if x <= 2 else None)) train_df = train_df.dropna(subset=["label"]) train_df["label"] = train_df["label"].astype(int)

然后进入特征工程与训练。TF-IDF把每条评论转成向量,朴素贝叶斯或者逻辑回归做分类,这一套在几千条样本上已经能稳定跑出还行准确率。

from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.naive_bayes import MultinomialNB from sklearn.model_selection import cross_val_score, train_test_split # jieba作为tokenizer传入TF-IDF,max_features控制特征大小 tfidf = TfidfVectorizer(tokenizer=jieba.lcut, max_features=5000, ngram_range=(1, 2)) X = tfidf.fit_transform(train_df["content"]) y = train_df["label"] # 交叉验证看真实效果,千万不能只跑一次train_test_split就下结论 scores = cross_val_score(MultinomialNB(), X, y, cv=5, scoring="accuracy") print(f"5折交叉验证准确率: {scores.mean():.4f} (±{scores.std():.4f})")

参数上比较关键的是ngram_range=(1, 2),它表示同时使用单个词和相邻两个词作为特征,这样可以捕获“效率低”“态度差”这类二元组合。max_features设为5000指的是只保留词频最高的前5000个特征,避免维度太高导致训练时间拉长。如果你发现交叉验证均值偏低,可以先看是不是把3分评价直接扔掉后正负样本数量差距过大,再做下一步处理。

3.3 进阶方案:预训练模型的微调思路与参数设置

如果还想往上走,第三种方案是直接微调一个预训练模型。这一步不是必须的,但如果你做的题目里恰好有导师偏好的深度学习路线,就可以用transformers库在中文BERT或国产预训练模型上做文本分类微调。一个实用建议是数据集不足5000条时不建议去跑完整BERT微调,更建议用TextCNN或改一下基座为蒸馏过的小型中文模型。

# 基于transformers的微调伪代码骨架,实际训练参数需要按显存调整 from transformers import AutoTokenizer, AutoModelForSequenceClassification, Trainer, TrainingArguments model_name = "bert-base-chinese" # 小数据集可换成蒸馏版本 tokenizer = AutoTokenizer.from_pretrained(model_name) model = AutoModelForSequenceClassification.from_pretrained(model_name, num_labels=2) train_encodings = tokenizer(list(train_df["content"]), truncation=True, padding=True, max_length=128) training_args = TrainingArguments( output_dir="./checkpoints", learning_rate=2e-5, # 微调任务的常用起始学习率,过大容易灾难性遗忘 per_device_train_batch_size=16, num_train_epochs=3, # 样本小时轮次不宜过高,2-3轮即可 weight_decay=0.01, logging_steps=100, )

这一段里最重要的参数是learning_rate和max_length。2e-5是CV领域中常见微调起点,比正常训练低一个数量级,是为了保留预训练权重中已经学到的语言知识。max_length=128意味着超过128个字的部分会被截断,电商评价一般不长,这个值够用;如果你爬的数据里包含大量长评论,再上调到256或512,但训练时间会同步上涨。

3.4 模型效果怎么比较:一份可持续复用的评测脚本

有了词典基线、传统机器学习和预训练模型三种方案,评测脚本必须写成可复用的结构,方便在答辩时直接对比表格。下面的代码用一个字典记录模型名和准确率,输出完整的对比结果:

import numpy as np from sklearn.metrics import confusion_matrix, classification_report import joblib def evaluate_model(model, X_test, y_test, model_name="model"): y_pred = model.predict(X_test) acc = (y_pred == y_test).mean() print(f"{model_name} 准确率: {acc:.4f}") print(confusion_matrix(y_test, y_pred)) print(classification_report(y_test, y_pred, digits=4)) joblib.dump(model, f"{model_name}.pkl") # 保存模型,方便后续直接加载 return acc

eval脚本在项目里建议固定放在evaluate.py里,每次调参后只改参数文件,不动评测逻辑。这样论文里的表格就可以写“三种模型在相同训练集和测试集下的指标对比”,数据一致、逻辑严谨,答辩时很有说服力。

4. 评价数据入库:MySQL 表设计与清洗流程

4.1 三张表的结构设计:商品、评价、情感标签

数据库不是附属品,在毕设里它承担“数据管理”和“功能完整度”两个作用。我建议用MySQL建三张表:商品表product、评价表comment、情感结果表sentiment_result。其中product存商品维度信息,comment存爬到的原始评价,sentiment_result存情感分析的结果标签、分数和模型版本。三张表用product_id关联。

-- 建库:务必指定utf8mb4,这是中文不乱码的核心 CREATE DATABASE IF NOT EXISTS comment_analysis DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE comment_analysis; CREATE TABLE product ( id BIGINT AUTO_INCREMENT PRIMARY KEY, product_id VARCHAR(64) NOT NULL COMMENT '平台商品ID', product_name VARCHAR(255) NOT NULL COMMENT '商品标题', shop_name VARCHAR(255) DEFAULT '' COMMENT '店铺名', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_product_id (product_id) ) COMMENT '商品信息表'; CREATE TABLE comment ( id BIGINT AUTO_INCREMENT PRIMARY KEY, product_id VARCHAR(64) NOT NULL COMMENT '关联product.product_id', content TEXT NOT NULL COMMENT '评价内容', rating TINYINT DEFAULT 3 COMMENT '用户打分1-5', comment_time DATETIME DEFAULT NULL COMMENT '评价时间', likes INT DEFAULT 0 COMMENT '点赞数', is_followup TINYINT DEFAULT 0 COMMENT '是否追评', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_comment (product_id, content(200)) ) COMMENT '原始评价表'; CREATE TABLE sentiment_result ( id BIGINT AUTO_INCREMENT PRIMARY KEY, comment_id BIGINT NOT NULL COMMENT '关联comment.id', sentiment_label TINYINT DEFAULT 0 COMMENT '0负向 1正向', sentiment_score FLOAT DEFAULT 0.0 COMMENT '模型输出置信分', model_version VARCHAR(64) DEFAULT 'tfidf_nb_v1' COMMENT '模型标识', analyzed_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_comment (comment_id) ) COMMENT '情感分析结果表';

三张表的核心设计考虑有两点。字符集整体选用utf8mb4,比utf8更稳妥,因为utf8在MySQL中是utf8mb3,遇到表情符号会直接报错或乱码,评论数据里“😂”这类字符出现的频率比想象高得多。另一是comment表加了UNIQUE KEY uk_comment (product_id, content(200)),因为爬虫断点续跑时最容易出现重复数据,唯一索引可以把重复拦截在数据库层,比代码里去重更可靠。content(200)表示对TEXT字段的前200个字符建索引,既能保证唯一约束,又不会让索引过大。

4.2 数据清洗与入库:从 CSV 到 MySQL 的完整管道

爬虫落地的是CSV,入库需要一个清洗和写入管道。先做去重、去掉空内容,再把时间字段标准化,最后用executemany批量写入。

import pymysql import pandas as pd from sqlalchemy import create_engine df = pd.read_csv("raw_comments.csv", encoding="utf-8-sig") df = df.drop_duplicates(subset=["content"]).dropna(subset=["content"]) df = df[df["content"].str.strip() != ""] df["rating"] = pd.to_numeric(df["rating"], errors="coerce") df["comment_time"] = pd.to_datetime(df["comment_time"], errors="coerce").dt.strftime("%Y-%m-%d %H:%M:%S") # 方式一:pymysql + executemany 批量写入 conn = pymysql.connect( host="localhost", user="root", password="你的密码", database="comment_analysis", charset="utf8mb4" ) cur = conn.cursor() product_sql = "INSERT IGNORE INTO product (product_id, product_name) VALUES (%s, %s)" comment_sql = "INSERT IGNORE INTO comment (product_id, content, rating, comment_time, likes) VALUES (%s, %s, %s, %s, %s)" for _, row in df.iterrows(): cur.execute(product_sql, (row["product_id"], row.get("product_name", ""))) cur.execute(comment_sql, (row["product_id"], row["content"], row["rating"], row["comment_time"], row.get("likes", 0))) conn.commit() cur.close() conn.close() print("数据入库完成,进货comment表条数:", len(df))

逐行execute在数据量几千条时完全够用,写法直白、日志清楚。如果评论量到十万级,可以换成executemany一次性传入多条参数,或者直接用SQLAlchemy的df.to_sql,但毕设场景里从SQL原始到手写能达到更好的答辩展示效果。写入用的INSERT IGNORE配合前面的唯一索引,遇到完全重复的评论会自动跳过,不会中断执行。

4.3 数据库连接参数:字符集、批量写入与常见的翻车点

连接数据库时最容易翻车的不是SQL语句,而是连接参数。charset="utf8mb4"这个参数必须显式写在连接串里,否则即使建库时指定了utf8mb4,写入时还是会按连接的默认字符集解释,中文照样变问号。另一个是Unix套接字问题,Mac上经常遇到Access denied for user 'root'@'localhost',排查顺序是:密码是否对、授权表是否localhost授权、MySQL服务是否监听在3306端口。

批量写入的性能也值得提一下。同样是1万条评论,逐条commit大约要跑十几秒,批处理能压到一两秒。这背后的差异在于提交次数,每commit一次,MySQL要刷一次事务日志。所以我一般会攒满500条或1000条才commit一次,时间消耗立刻降下来。

# 批量提交优化示例 BATCH_SIZE = 500 count = 0 for _, row in df.iterrows(): cur.execute(comment_sql, (...)) count += 1 if count % BATCH_SIZE == 0: conn.commit() print(f"已写入 {count} 条") conn.commit() # 最后一批提交

5. 避坑指南:爬虫被反爬、模型效果差、数据对不上的几类典型问题

5.1 现象:爬到一半被要求登录

爬虫运行到几十页后突然跳转登录页,或者响应里出现滑块验证,是最常见的中途中断原因。原因是请求频率太高,或者请求头里缺少浏览器指纹特征,被对方的风控方案识别成了机器行为。解决思路分三条:第一把延时从固定1秒改成1到3秒随机,模拟人类阅读时间;第二把请求头里的User-Agent、Accept、Sec-Fetch相关头补完整;第三加Cookie维持会话,第一次手动登录后把请求Cookie复制到脚本里。如果这三招都不奏效,大概率是风控升级了,那时候就要考虑降低量级、分时段抓取,而不是硬碰硬。

5.2 现象:分词不准导致情感词典基本失效

词典法跑出来的分数和肉眼判断差很多,打开中间结果一看,发现“客服”被拆成“客服”没问题,而“说不清”被拆成“说”“不清”,“不满意”却拆成了“不”“满意”,评分自然乱掉。原因是jieba默认词典没有收录电商领域词组,也没有正确识别人名、口语化短词。解决方法是加载自定义词典,把高频领域词和商品词放进去,再考虑增加负面词表覆盖“割韭菜”“踩雷”这类新兴表达。分词是情感分析的地基,这个环节翻车,后面模型再高级也补不回来。

5.3 现象:数据库中文变问号

写入后select一看全是“???”,或者中文变成“锟斤拷”。原因基本在两个位置:建库时指定了utf8但没指定utf8mb4,或者连接字符串没有带上charset参数。解决方法是统一三处字符集:建库用DEFAULT CHARACTER SET utf8mb4,连接时charset="utf8mb4",如果是在Windows下还要检查终端本身的编码。这个坑特别适合放在毕设文档的“常见问题”一节,属于典型的经验型知识,答辩时讲到会显得你踩过坑、有积累。

5.4 现象:标注样本太少,模型偏向全部预测为好评

弱标注之后发现正样本2000条、负样本300条,模型训练出来对所有新的评论全预测为正向,准确率看着不低,但实际毫无区分能力。原因是负样本严重不足,模型只需要把一切都预测成多数类就能拿到很高的准确率。解决方法是先做类别平衡,最简单的做法是欠采样:随机丢掉部分正样本,让正负比接近1比1;或者给少数类加大惩罚权重。模型评估也不能只看准确率,要看precision和recall,尤其是负向类的recall,因为这个指标对虚假的好评检测才更重要。

5.5 现象:评分和情感标签对不上

有人会理所当然地用评分训练情感模型,但爬下来的数据里,打1星的评价写的是“物流慢但商品还行”,打5星的评价写的是“被忽悠了,货不行”。评分标的是整体体验,而情感分析标的更多是文本情绪。解决方法是不要把评分直接当标签,弱标注之后一定加一个人工抽检环节,随机抽两三百条人工复核,修掉明显不一致的样本。这个修正过程本身也是毕设里“数据标注策略”章节的好素材。

6. 让毕业答辩更有说服力:效果验证与可视化呈现的实用笔记

6.1 用混淆矩阵和K折交叉验证撑起结论

答辩时导师最常问的一句话是“怎么证明你的模型是有效的”。只给一个准确率数字远远不够,请把混淆矩阵和K折交叉验证一起放进结论。混淆矩阵能看出模型在哪一类上犯错更多,对商品评价这种场景,漏判负向比误判正向更值得关注,因为你爬评价做分析的核心诉求是发现问题而不是讨好商家。交叉验证则用标准差说明稳定性,只跑一次train_test_split,结果受随机划分影响太大,3次和5次交叉验证的平均值才有说服力。

四张图建议提前准备好:第一张是评论情感分布饼图,正向、负向、中性的占比一眼看清;第二张是高频词词云图,正向词和负向词分开画,视觉冲击力最强;第三张是情感趋势折线图,按月统计正负向占比变化;第四张是负向评价关键词TOP 10柱状图,这是整个项目最能落到业务价值的图。四张图不是花架子,每张都能在答辩时引出一个具体结论,比如“从趋势图可以看出某商品在3月之后负向占比明显抬升,结合Top 10关键词‘客服’‘发货慢’可以定位到物流环节”。

6.2 一套让代码仓库更职业化的目录组织方式

完整交付的毕业设计包含源代码、文档说明和数据库,目录别全部堆在主目录里。我习惯这样组织:

comment_sentiment/ ├── crawler/ # 爬虫模块 │ ├── spider.py │ └── config.py ├── models/ # 情感分析模型 │ ├── baseline.py # 词典基线 │ ├── tfidf_nb.py # 传统机器学习 │ └── bert_finetune.py ├── db/ # 数据库脚本 │ ├── init.sql # 建库建表 │ └── insert_data.py ├── docs/ # 文档说明 │ ├── 开题报告.md │ ├── 需求分析.md │ └── 答辩提纲.md └── requirements.txt

6.3 把baseline做到自动化:一条命令跑通全流程

最后分享一个让答辩加分的技巧:写一个run_all.py,把爬取、清洗、训练、预测、入库全流程串起来,运行后自动输出报告。你只需要在演示前改好配置,答辩现场敲一条命令就能展示整套链路,而不是挨个脚本去运行。

# run_all.py 一键运行示例 import subprocess steps = [ "python crawler/spider.py", "python db/insert_data.py", "python models/tfidf_nb.py", "python evaluate.py" ] for step in steps: print(f"--> 执行 {step}") subprocess.run(step, shell=True, check=True) print("全流程执行完成,报告已生成")

这段代码虽然简单,但体现了工程化意识。你可以在注释里写明每个子进程失败时如何定位日志,这就从“写算法的学生”变成了“搭系统的工程师”,答辩的主动权完全在自己手里。

最后给你一个我的习惯:整个项目跑通后,关机重启一次,再跑一遍从头到尾的流程。很多毕设翻车翻在“我从来没从头到尾完整执行过”,结果演示当天初始化数据库失败。把这个干跑测试养成习惯,现场演示通常就不会出问题。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询