Python机器学习实现淘宝评论情感分析:从数据清洗到模型部署
简介:面向计算机相关专业学生与毕业设计者的电商淘宝商品评论情感分析完整项目,涵盖从数据采集到模型对比的全流程。项目基于Selenium模拟真实登录行为爬取淘宝评论,完成文本去噪、标点清理、jieba分词、词向量化及词语索引构建等预处理,分别训练SVM与LSTM模型并对比分类效果,同时提供训练、测试、可视化及GUI界面脚本,可完整呈现从数据清洗到模型评估的每个环节。资源共43个文件,以14个Python源码为主,辅以CSV/XLS数据集、pickle与h5格式的预训练模型、YAML配置及Selenium驱动等,涵盖完整数据与模型文件,压缩包整体约66.68MB。已有382人学习下载,适合用作毕业设计、课程设计或期末大作业参考,目录结构清晰且含README说明,可快速定位爬虫、预处理、模型训练与评估模块,直接复现对比结果。
1. 为什么毕业设计首选Python机器学习做淘宝评论情感分析
毕业设计拿到一份“Python 基于机器学习的电商淘宝商品评论情感分析项目源码+数据”,很多人第一反应是先跑通模型,但真正卡住进度的往往是数据清洗和标签梳理。淘宝评论里既有表情符号又有“物流快、包装薄”这类转折表达,直接套英文情感分析流程准确率会很难看。这个题目的核心不是“用哪个模型”,而是怎么把中文口语评论变成机器学习能消费的特征,再做二分类情感判断。
这类项目适合正在做文本挖掘方向毕设的学生,也适合刚接触中文NLP的工程师。下面是常见做法:先清洗数据,再分词和特征提取,对比几个机器学习模型,最后一个Web页面做答辩演示。整个过程不依赖GPU,单机CPU就能跑完。
2. 评论数据从哪来:清洗流程与情感标签的三种方案
2.1 先理顺数据字段:CSV里到底有什么
淘宝评论相关的毕设数据压缩包里,最常见的数据格式是CSV或Excel,字段一般包括:评论ID、用户ID、商品ID、评分、评论正文、评论时间,有时还带着追评内容。我一般会先把原表读进来做一次字段体检,而不是直接跑模型。
import pandas as pd
df = pd.read_csv("data/taobao_comments.csv", encoding="utf-8")
print(df.head(10))
print(f"总样本数: {len(df)}")
print(df["rating"].value_counts())
先确认列名是否和后面代码里用的一致,rating字段是字符串还是数字;如果数据来自不同来源汇总,列名可能叫“score”或“star”。 value_counts() 能一眼看出评分分布是否倾斜,比如1星太少,后面的标签映射就要处理长尾问题。
如果需要自己补充数据,优先考虑自己账号可导出的订单评价,或者使用公开的数据集。不太建议花大量时间做页面采集,淘宝的页面结构和反爬策略变化很快,容易把毕设时间耗在无关环节上。
2.2 文本清洗的四个必做步骤
评论文本不能直接进入机器学习模型,至少要做四件事:
- 去HTML标签。淘宝评论导出的数据里经常残留
<br/>或样式片段。 - 去表情和特殊符号。表情符号对情感有一定作用,但TF-IDF会把它们拆得很难看,所以先去掉。
- 统一大小写和繁体简体。全转成小写,繁转简用
opencc的convert。 - 去停用词。像“的、了、呢”这些词没有情感区分度,保留只会增加维度。
一个参考实现如下:
import re
import pandas as pd
import jieba
# 1) 去HTML
df["comment"] = df["comment"].str.replace(r"<[^>]+>", "", regex=True)
# 2) 只保留中文、英文、数字,其他符号替换为空格
df["comment"] = df["comment"].str.replace(r"[^\u4e00-\u9fa5a-zA-Z0-9]", " ", regex=True)
# 3) 统一小写
df["comment"] = df["comment"].str.lower()
# 4) 加载停用词
stopwords = set()
with open("data/stopwords.txt", encoding="utf-8") as f:
for line in f:
stopwords.add(line.strip())
def cut_and_filter(text):
words = jieba.lcut(text)
return " ".join(w for w in words if w not in stopwords and len(w.strip()) > 1)
df["cutted"] = df["comment"].apply(cut_and_filter)
正则表达式 [^\u4e00-\u9fa5a-zA-Z0-9] 的意思是保留中文、大小写字母和数字,其余字符替换为空格。为什么不是直接删除?因为分词依赖词语边界,用空格隔开可以避免“物流快”和“包装薄”被拼在一起。 len(w.strip()) > 1 过滤单字是为了去掉“差、好、快”这类保留价值有限的小词,但注意这个阈值会丢掉“爽、稳”这类单字情感词,如果数据里这类词多,就改成 > 0 。
给一个清洗前后的对比:
| 阶段 | 文本内容 |
|---|---|
| 原始评论 | 苹果发货速度很快!!!<br/>包装严实,客服态度也好😊 |
| 清洗后 | 苹果 发货 速度 很快 包装 严实 客服 态度 好 |
另外,淘宝评论里会出现“此用户没有填写评价呢”这类默认文案,应该在清洗后做一次频数统计,把这类无意义文本整条删除。判断方法是按 cutted 分组计数,看Top 20里有没有和商品无关的句子。
2.3 情感标签:二分类、三分类还是五分类
数据里一般有评分字段,常见做法是用评分映射标签。淘宝是1到5星,比较自然的方案有三种:
| 方案 | 标签定义 | 适用情况 |
|---|---|---|
| 二分类 | 1-2星负向,4-5星正向,3星删除 | 模型最简单,答辩时容易讲清 |
| 三分类 | 1-2负向,3中性,4-5正向 | 更接近真实业务,但中性类波动大 |
| 五分类 | 每一星一个标签 | 很少用,因为情感强度标注主观,数据量需求大 |
对应代码:
def rating_to_label(rating):
if rating <= 2:
return "negative"
if rating >= 4:
return "positive"
return "neutral"
df["label"] = df["rating"].apply(rating_to_label)
# 二分类任务:删除中性样本
df = df[df["label"] != "neutral"].copy()
如果做三分类,中性样本往往只有不到10%,而且“一般”这种评价边界模糊,模型会学得很难受。我的建议是:毕设里先用二分类跑通流程,然后在论文里讨论“如果扩展到三分类,中性样本应该如何重新采样”。这个讨论本身是加分项。
标签映射前,先检查rating字段是否有缺失值和异常值,比如0分或者空字符串。否则函数会报错或者把脏数据当成负向。这类数据直接删掉,不要用均值填充,因为评分是离散的。
3. 特征工程与机器学习算法选型:从TF-IDF到四模型对比
3.1 为什么机器学习模型比情感词典稳
早期文本情感分析常用做法是整理一份情感词典,把评论里出现的积极词和消极词分别加分求和。问题是淘宝评论里的口语表达更新太快,“绝绝子”“蹲一个链接”这类新词,词典永远慢半拍。机器学习模型则把分词后的文本转成高维特征,再通过训练数据学习权重,不需要人工维护词典。这也是为什么这个毕设题目要点名“基于机器学习”——不是不用情感词典,而是以特征学习为主,情感词典最多用来做辅助特征。
3.2 TF-IDF特征构造与三个常用参数
特征工程最常见的是TF-IDF。它衡量一个词在单条评论中的重要程度,同时压掉“的、是”这类高频无意义词。用sklearn实现:
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.model_selection import train_test_split
vectorizer = TfidfVectorizer(
max_features=5000,
ngram_range=(1, 2),
sublinear_tf=True,
min_df=2
)
X = vectorizer.fit_transform(df["cutted"])
y = df["label"]
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, stratify=y, random_state=42
)
print("训练集形状:", X_train.shape)
参数说明: max_features=5000 保留词频最高5000个特征,淘宝长尾词太多,不限制维度会膨胀到几万维; ngram_range=(1,2) 让特征同时包含单个词和两个相邻词,能抓住“不 错”和“不 好”这种偏正结构; sublinear_tf=True 把词频从“2次”变成 1+log(2) ,避免“客服”这类到处出现的词主导相似度; min_df=2 删除只在1条评论里出现的词,这些词没有统计意义,还容易过拟合。
这里要注意: fit_transform 只应该在训练集上调用,测试集要用 transform 。上面的代码放在切分前主要是为了演示,实际实验时应该先切分再 fit_transform :
X_train_raw, X_test_raw, y_train, y_test = train_test_split(
df["cutted"], y, test_size=0.2, stratify=y, random_state=42
)
X_train = vectorizer.fit_transform(X_train_raw)
X_test = vectorizer.transform(X_test_raw)
使用 transform 而不是 fit_transform ,能保证测试集使用训练集的词表,不引入新词特征。
3.3 四模型基准对比与参数预设
有了TF-IDF矩阵,接下来跑一组常见机器学习模型。我一般会固定随机种子,用同样的数据跑四个模型:
from sklearn.linear_model import LogisticRegression
from sklearn.naive_bayes import MultinomialNB
from sklearn.svm import LinearSVC
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report
models = {
"NB": MultinomialNB(alpha=0.5),
"LR": LogisticRegression(C=1.0, max_iter=1000),
"SVM": LinearSVC(C=1.0),
"RF": RandomForestClassifier(n_estimators=200, random_state=42),
}
for name, model in models.items():
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
print(f"--- {name} ---")
print(classification_report(y_test, y_pred, target_names=["negative", "positive"], digits=3))
各模型关注点说明:
-
MultinomialNB(alpha=0.5):多项式朴素贝叶斯适合离散特征,alpha是拉普拉斯平滑系数。alpha小对训练集拟合更强,但容易在罕见词上给过高概率,一般从0.1到1.0网格搜索。 -
LogisticRegression(C=1.0, max_iter=1000):C是正则化强度的倒数,C越小正则化越强。max_iter=1000避免默认的100次迭代不收敛。 -
LinearSVC(C=1.0):线性SVM在短文本上通常和LR持平,但训练速度快。LinearSVC本身不输出概率,后续Web演示如果想返回置信度,建议换成SVC(kernel='linear', probability=True),代价是训练变慢。 -
RandomForestClassifier(n_estimators=200):需要调n_estimators和max_depth,否则容易过拟合。高维稀疏TF-IDF下随机森林的优势不大,它的强项是非线性特征组合。
在相同数据上的典型趋势是:NB虽然朴素,但在5000维特征下往往不差;LR和SVM的F1值接近,通常高于NB;RF最容易出现过拟合,表现为训练集F1接近1、测试集明显下降。下面给一个记录格式的示例:
| 模型 | 特征 | 参数 | 准确率 | F1 | 训练时间 |
|---|---|---|---|---|---|
| NB | TF-IDF(5000,1-2gram) | alpha=0.5 | 0.823 | 0.814 | 2s |
| LR | TF-IDF(5000,1-2gram) | C=1.0 | 0.861 | 0.855 | 8s |
| SVM | TF-IDF(5000,1-2gram) | C=1.0 | 0.868 | 0.860 | 15s |
| RF | TF-IDF(5000,1-2gram) | n_est=200 | 0.841 | 0.829 | 30s |
注意,上表数字是常见数量级的示意,不是某个固定版本的真实结果。你跑出来的结果只要趋势类似就正常。
3.4 Word2vec和情感词典的扩展思路
如果还想在毕业设计中体现更多工作,可以用Word2vec在语料上训练词向量,然后把一条评论的所有词向量取平均,再丢给LR或SVM。这个做法的特点是不依赖稀疏词表,对新词更友好,但短评只有几句话,平均池化会稀释关键词。另一个扩展是把情感词典里的正面词和负面词计数作为额外特征,拼到TF-IDF矩阵后面。这两种方案都可以放在论文里和TF-IDF做对比实验。
4. 模型评估与可视化:混淆矩阵、词云和Flask演示
4.1 准确率会骗人:先看F1和混淆矩阵
情感分析常见的数据分布是正样本占70%以上,如果所有评论都预测成“正向”,准确率也有70%。所以毕设答辩不要只说准确率,至少要给出每个类别的precision、recall和F1。用一个代码片段输出混淆矩阵:
from sklearn.metrics import confusion_matrix, classification_report
import matplotlib.pyplot as plt
# model是上一章训练好的LR或SVM模型
y_pred = model.predict(X_test)
cm = confusion_matrix(y_test, y_pred)
print("混淆矩阵:")
print(cm)
labels = ["negative", "positive"]
fig, ax = plt.subplots(figsize=(5, 4))
im = ax.imshow(cm, cmap="Blues")
ax.set_xticks([0, 1])
ax.set_yticks([0, 1])
ax.set_xticklabels(labels)
ax.set_yticklabels(labels)
for i in range(2):
for j in range(2):
ax.text(j, i, cm[i, j], ha="center", va="center")
plt.savefig("images/confusion_matrix.png", dpi=150)
plt.show()
从混淆矩阵里能看出两类错误:把负向误判成正向(假正例多),以及把正向误判成负向(假负例多)。对电商场景,假正例更严重——一条差评被显示成好评会影响购买决策。如果假正例多,可以调整LR的 class_weight='balanced' 或者降低分类阈值。
4.2 中文词云:字体是第一个坑
词云图是毕业设计报告里最直观的展示方式。常见坑是画出来全是方框,原因是系统里没有中文字体。要指定 font_path 。一个例子:
from wordcloud import WordCloud
positive_text = " ".join(df[df["label"] == "positive"]["cutted"])
negative_text = " ".join(df[df["label"] == "negative"]["cutted"])
def generate_wordcloud(text, save_path):
wc = WordCloud(
font_path="C:/Windows/Fonts/simhei.ttf", # Windows常见黑体
width=800,
height=400,
background_color="white",
max_words=150,
).generate(text)
wc.to_file(save_path)
generate_wordcloud(positive_text, "images/wordcloud_positive.png")
generate_wordcloud(negative_text, "images/wordcloud_negative.png")
如果运行时提示找不到字体,先确认simhei.ttf路径存在;Linux环境可以安装 fonts-wqy-microhei ,然后把字体路径换成 /usr/share/fonts/truetype/wqy/wqy-microhei.ttc 。词云适合看整体高频词,不适合证明模型效果,只能算可视化补充。
4.3 用Flask把模型包装成本地Web接口
毕业设计答辩时,现场演示一个输入框比只跑notebook更有冲击力。最省事的方式是保存pipeline并用Flask提供API。这里假设你已经用sklearn的Pipeline把TfidfVectorizer和分类器打包,并用 joblib.dump 保存。
import joblib
from flask import Flask, request, jsonify
app = Flask(__name__)
pipe = joblib.load("models/sentiment_pipeline.joblib")
@app.route("/predict", methods=["POST"])
def predict():
data = request.get_json(force=True)
text = data.get("text", "").strip()
if not text:
return jsonify({"error": "empty text"}), 400
label = pipe.predict([text])[0]
if hasattr(pipe, "predict_proba"):
proba = pipe.predict_proba([text])[0]
score = float(proba.max())
confidence = {pipe.classes_[i]: float(proba[i]) for i in range(len(proba))}
else:
score = None
confidence = None
return jsonify({"label": label, "score": score, "confidence": confidence})
if __name__ == "__main__":
app.run(host="127.0.0.1", port=5000, debug=False)
逻辑说明: joblib.load 把TF-IDF向量器和模型整体加载进来,所以预测时只需要传入原始文本。 predict_proba 对于LR、NB、RF都可用,但LinearSVC没有这个方法,所以前面建议用LR或SVC。返回json里带置信度,答辩时可以展示“这条评论有多大把握被判断为正向”。
启动后用curl验证:
curl -X POST http://127.0.0.1:5000/predict \
-H "Content-Type: application/json" \
-d "{\"text\": \"发货速度快,包装也很严实\"}"
返回 {"label":"positive","score":0.93...} 说明接口正常。
4.4 训练与预测不一致的经典错误
| 错误现象 | 可能原因 | 解决方法 |
|---|---|---|
| 预测结果明显不对 | 训练时传入的是已经分词的“cutted”列,API却接收原始文本 | 把清洗+分词放进一个函数,再封装到pipeline里 |
| 接口报维度不一致 | 训练时重新fit了vectorizer,预测时又fit一次 | 训练好vectorizer后统一保存,预测只加载不重fit |
| 输出类别总是positive | 数据不平衡严重且没有处理 | 切分时 stratify=y ,训练时设置 class_weight='balanced' |
| 中文词云乱码 | 没指定中文字体 | 指定 font_path ,例如simhei.ttf |
这几种错误几乎每次重置项目都会遇到。只要记住一句话:训练和预测只能共用一个“文本到特征”的流程,绝不能在预测时临时再造一个vectorizer。
5. 让“高分项目”从能跑变成规范:源码目录、实验记录与网格搜索
5.1 源码目录怎么组织不扣分
拿到压缩包后先把目录整理成下面这种结构,答辩老师打开就能找到关键文件:
.
├── data/
│ ├── raw/ # 原始评论数据
│ └── processed/ # 清洗后的cutted语料
├── src/
│ ├── clean.py # 清洗与分词
│ ├── train.py # 特征工程与模型训练
│ └── predict.py # 加载pipeline做单条预测
├── app.py # Flask演示接口
├── images/ # 混淆矩阵、词云
├── requirements.txt
└── README.md
注意 requirements.txt 要固定主版本,比如 scikit-learn==1.3.2 。答辩现场换环境装最新版可能导致API变动,固定版本能避免演示翻车。
5.2 实验记录表:答辩老师问模型选型时看这个
不要只保存最终模型,建议在项目里放一张实验记录表。下面是一个可复制的模板:
| 实验日期 | 模型 | 特征 | 参数 | 测试集准确率 | F1 | 备注 |
|---|---|---|---|---|---|---|
| 2025-04-01 | NB | TF-IDF | alpha=0.5 | 0.823 | 0.814 | 基线 |
| 2025-04-01 | LR | TF-IDF | C=1.0 | 0.861 | 0.855 | 推荐 |
| 2025-04-02 | SVM | TF-IDF | C=1.0 | 0.868 | 0.860 | 无置信度 |
| 2025-04-02 | RF | TF-IDF | n_est=200 | 0.841 | 0.829 | 略过拟合 |
答辩时直接打开表,说明为什么最终选LR而不是F1最高的SVM:LinearSVC没有 predict_proba ,Flask演示里拿不到置信度;换成 SVC(probability=True) 训练慢一倍。这个取舍逻辑比“哪个分高选哪个”可信得多。
5.3 用Pipeline和GridSearchCV一次跑出参数网格
为了不手动调十遍参数,把TF-IDF和分类器封装进Pipeline,再用GridSearchCV做交叉验证:
from sklearn.pipeline import Pipeline
from sklearn.model_selection import GridSearchCV
pipeline = Pipeline([
("tfidf", TfidfVectorizer()),
("clf", LogisticRegression(max_iter=1000)),
])
params = {
"tfidf__max_features": [3000, 5000],
"tfidf__ngram_range": [(1, 1), (1, 2)],
"clf__C": [0.1, 1.0, 10.0],
}
grid = GridSearchCV(
pipeline,
params,
cv=5,
scoring="f1_macro",
n_jobs=-1,
verbose=1,
)
grid.fit(df["cutted"], y)
print(grid.best_params_)
print(grid.best_score_)
GridSearchCV 训练时内部会切交叉验证集,所以传入的是完整的 cutted 列。得到最优参数后,再用最优参数在完整训练集上重新fit一次并保存。把 grid.best_params_ 和测试集得分一起写进5.2的实验记录表,答辩时直接讲参数调整过程,比口头解释更有说服力。
更多推荐
所有评论(0)