简介:基于Python的电商买家评论情感分析项目包,专为毕业设计、期末大作业和课程设计场景打造,代码注释详尽,即使完全没有项目经验的新手也能看懂每一步实现,曾获98分且深受导师认可。整个压缩包约54MB,内含1380个文件,以py源码、csv数据集、pth模型和ipynb分析笔记为主,辅以HTML可视化页面、txt说明、DLL依赖库及配置文件等,目录层次清晰,简单配置环境即可运行。已有246人浏览学习,适合作为课设参考或毕设基础。项目完整覆盖评论爬取、文本清洗、情感特征构建、模型训练到结果可视化全流程,附带数万条真实电商评论数据,涵盖多个品牌并已按正负情感标注,可直接复现实验或作为二次开发基准;下载部署后既能深入理解自然语言处理在电商场景的应用,也能高效完成高分开课设或毕业设计成果,实用价值突出。
1. 电商买家评论情感分析:一套能直接跑通全流程的Python项目
做毕业设计或者课程设计的朋友,最怕的不是不会写代码,而是拿到一个题目之后不知道从哪下手。尤其“情感分析”这种题目,听起来高大上,真做起来要处理数据采集、文本清洗、特征提取、模型训练、结果评估一整条链路,任何一个环节卡住都让人抓狂。这个基于Python的电商买家评论数据情感分析项目,就是一套已经把全流程打通、而且带着完整数据集和模型文件的资源,用到的是真实的美的小家电评论数据,不是网上随便找的玩具数据集。
我拆完这套代码之后最大的感受是:它不是为了炫技写的,而是每一行都在回答“这个功能怎么实现”的问题。代码里注释给得很细,新手跟着看不会迷路。它有现成的csv评论数据、有训练好的模型文件,还带了venv虚拟环境配置文件,下载下来简单配置就能跑。对于需要交期末大作业、毕设,或者只是想自己动手体验一次完整NLP流程的人来说,这套东西能帮你省下至少一周的弯路。
2. 数据集构成与采集逻辑:先搞清楚你手上有什么
2.1 六份数据文件分别是什么
解压之后数据目录里有六份csv文件,先别急着跑代码,花五分钟把数据文件捋清楚,后面所有操作都是建立在“你了解数据”这个前提上的。这六份文件分别是:
| 文件名 | 内容定位 | 大概规模 |
|---|---|---|
| spider_comments.csv | 爬虫采集的原始评论数据 | 通用采集结果 |
| spider_comments_34000.csv | 清洗合并后的大规模评论集 | 约34000条 |
| spider_meidi_comments2020.csv | 美的品牌2020年评论 | 年度数据 |
| spider_meidi_comments2019.csv | 美的品牌2019年评论 | 年度数据 |
| 美的(Midea)_正面.csv | 正面情感标注样本 | 标注数据 |
| 美的(Midea)_负面.csv(对应正面文件) | 负面情感标注样本 | 标注数据 |
从文件命名能看出来,这套数据的采集是分两条线的:一条是通用爬虫抓取的全量评论,一条是针对美的品牌的分年度数据。这种组织方式在实际项目里很常见——先用宽口径抓数据,再做品牌筛选和年度切分,最后抽出一部分人工标注正负面,作为训练集。
提示:正面和负面csv是训练模型的关键标注数据。如果你拿到手的压缩包里只有正面csv没看到负面文件,去原始数据目录再找一遍,或者在代码里找读取路径的常量定义。
2.2 爬虫采集的常见字段结构
打开spider_comments_34000.csv看一眼,典型的电商评论数据至少包含这几个字段:评论内容(comment_content)、评分(rating)、评论时间(comment_time)、商品ID或SKU ID。这个结构对应的是大多数电商平台的评论区JSON接口返回字段。
以抓取京东评论为例子,接口返回的数据一般长这样:
{ "comments": [ { "id": "123456789", "content": "冰箱制冷效果很好,噪音也不大", "score": 5, "creationTime": "2024-06-15 10:23:45", "productId": "100012345" } ] }代码里对应的解析逻辑一般是这样处理的:
import json import csv import time import requests def fetch_comments(url, headers, page_num): """ 拉取某一页的评论JSON数据 url: 评论接口地址 headers: 请求头,必须带上User-Agent和Referer page_num: 页码参数 """ params = { 'page': page_num, 'pageSize': 20, 'sortType': 0 } resp = requests.get(url, params=params, headers=headers, timeout=10) if resp.status_code == 200: data = resp.json() return data.get('comments', []) return [] def save_to_csv(comments, filename): """把评论列表写入csv文件""" with open(filename, 'a', encoding='utf-8', newline='') as f: writer = csv.DictWriter(f, fieldnames=['id', 'content', 'score', 'creationTime']) writer.writerows(comments)这一段有两个细节值得注意。第一是headers里必须带Referer,电商平台的评论接口会校验来源,不带Referer直接请求大概率返回403。第二是写入csv时用newline='',不然在Windows上每写一行都会多一个空行,最后csv里全是空白行,处理的时候烦得很。
2.3 数据拼接与标签构建的逻辑
如果你打算自己重新构建一份训练数据,常见做法是把爬下来的评论按评分映射成标签:评分4星和5星归为正面,1星和2星归为负面,3星直接丢弃。因为3星评论往往表达的是“还行”“凑合”,情感倾向模糊,硬塞进训练集只会增加噪声。
import pandas as pd df = pd.read_csv('spider_comments_34000.csv', encoding='utf-8') # 评分映射:4-5星为正面(1),1-2星为负面(0),3星丢弃 df['label'] = df['score'].apply(lambda x: 1 if x >= 4 else (0 if x <= 2 else None)) df = df.dropna(subset=['label']).reset_index(drop=True) # 抽样均衡:让正负样本数量接近,防止模型偏向多数类 positive_samples = df[df['label'] == 1].sample(n=5000, random_state=42) negative_samples = df[df['label'] == 0].sample(n=5000, random_state=42) balanced_df = pd.concat([positive_samples, negative_samples], axis=0) balanced_df.to_csv('balanced_comments.csv', index=False, encoding='utf-8')说实话,样本均衡这一步很多新手会忽略,直接拿原始数据去训练。结果是模型把所有评论都预测成正面,准确率还特别高,因为负面样本太少了,模型只需要“无脑输出正面”就能拿到高分。这个坑后面避坑章节会细说。这里用random_state=42固定随机种子,是为了保证结果可复现,你跑出来的训练集和我跑出来的训练集是同一批数据,调参的时候才有可比性。
2.4 数据质量检查清单
拿到数据之后别急着训练模型,先做一遍质量检查。我一般会查三件事:第一,评论内容有没有大量重复——用df['content'].duplicated().sum()看一眼,重复比例超过10%说明采集时有重复翻页或接口重试机制没写好;第二,评论文本长度分布——太短的评论(比如只有一两个字)要么是刷单要么是无意义灌水,可以考虑过滤;第三,编码是否统一——csv文件打开如果出现乱码,多半是文件本身是gbk编码但代码用utf-8读取了。
# 快速体检:重复率、空值、文本长度分布 import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv('spider_comments_34000.csv', encoding='utf-8') print(f"总评论数: {len(df)}") print(f"重复评论数: {df['content'].duplicated().sum()}") print(f"空评论数: {df['content'].isna().sum()}") df['length'] = df['content'].str.len() print(df['length'].describe()) # 过滤掉长度小于2的无效评论 df = df[df['length'] >= 2]这一步结束之后,数据才算真正进入可用的状态。很多教程直接跳过这个环节,上来就分词建模,但实际工程项目里,数据质量决定了模型性能的上限——你用脏数据训练,调参调得再勤快也没什么用。
3. 中文文本预处理与特征工程:情感分析的核心环节
3.1 分词、去停用词与自定义词典
中文和英文不一样,英文单词天生用空格分隔,中文没有天然的分隔符,所以分词是绕不开的第一步。这个项目用的是jieba分词,这也是中文NLP里最普及的工具库。但直接jieba.lcut(text)就完事了吗?不是的——电商评论领域有大量品牌名、产品型号、口语化表达,默认词典往往切不对。
import jieba import re # 加载自定义词典,保证"美的""变频""风冷无霜"这类词不被切碎 jieba.load_userdict('userdict.txt') # 停用词表:加载后过滤无意义词 stopwords = set() with open('stopwords.txt', 'r', encoding='utf-8') as f: for line in f: stopwords.add(line.strip()) def clean_text(text): """清洗文本:去特殊字符、去数字、去英文""" text = re.sub(r'[a-zA-Z0-9]', '', text) text = re.sub(r'[\u3000\xa0]', '', text) # 去全角空格 return text.strip() def tokenize(text): """分词并过滤停用词""" text = clean_text(str(text)) words = jieba.lcut(text) words = [w.strip() for w in words if w.strip()] words = [w for w in words if w not in stopwords and len(w) > 1] return ' '.join(words)这里有两个参数值得根据你的数据情况调:第一是len(w) > 1——单个汉字如“好”“差”其实是有情感含义的,但如果你的停用词表已经包含了常见单字,这里过滤掉可以降低特征维度;第二是load_userdict的自定义词典——你如果处理的是手机评论,“小米”“华为”这些品牌词必须加进词典,否则会被拆成“小/米”,语义就丢了。
3.2 TF-IDF向量化的核心参数
分词完成之后,文本从字符串变成了空格分隔的词序列,下一步就是把它转成机器能读懂的数字。项目用的是TF-IDF(词频-逆文档频率),相比单纯的词袋模型(CountVectorizer),TF-IDF能降低“的”“了”“是”这类高频但无意义词汇的权重。
from sklearn.feature_extraction.text import TfidfVectorizer # max_features限制特征维度,防止维数爆炸 # ngram_range让模型能捕捉"不"+"好吃"这样的组合语义 vectorizer = TfidfVectorizer( max_features=8000, ngram_range=(1, 2), min_df=2, max_df=0.8, sublinear_tf=True ) X = vectorizer.fit_transform(corpus) # corpus是分词后的文本列表这几个参数是TF-IDF调优的关键。max_features=8000控制特征数量——电商评论语料里词的总量可能超过5万,但很多词只出现一两次,属于噪声,截断到8000维能在性能和准确率之间取得平衡。ngram_range=(1,2)表示同时使用单词和相邻双词作为特征,这样“不好”会被当做一个整体特征,而不是拆成“不”和“好”让模型自己去组合。min_df=2过滤掉只在一条评论里出现的词(这种词没有统计意义),max_df=0.8过滤掉出现在超过80%评论里的词(这种词基本是停用词级别的噪声)。
# 单独查看特征词,确认向量化结果是否符合预期 feature_names = vectorizer.get_feature_names_out() print(f"特征维度: {len(feature_names)}") # 输出前20个特征词,人工检查是否有异常 print(feature_names[:20])每次做向量化之后,我习惯打印一下特征词列表。如果看到一堆“我们”“你们”“这个”这种词混在特征里,说明停用词表不够全,需要补充。反过来,如果看不到任何跟产品相关的词(比如“制冷”“静音”“容量”),说明分词环节出了问题,得回去查自定义词典。
3.3 训练集与测试集的划分顺序
特征工程做完之后就是划分训练集和测试集。这里有一个新手常犯的错误:先对整个数据集做向量化,再切分训练测试集。正确做法是先切分再向量化,而且测试集必须用训练集拟合好的vectorizer去transform,不能重新fit。
from sklearn.model_selection import train_test_split # 先切分后向量化!顺序不能反 X_train, X_test, y_train, y_test = train_test_split( df['tokens'], df['label'], test_size=0.2, random_state=42, stratify=df['label'] # 分层采样,保证正负比例一致 ) # 在训练集上fit,在训练集和测试集上分别transform X_train_vec = vectorizer.fit_transform(X_train) X_test_vec = vectorizer.transform(X_test)stratify=df['label']是为了让切分之后训练集和测试集里的正负样本比例跟原始数据保持一致。如果你不做分层采样,纯粹随机切分,遇到不平衡数据可能测试集里全是正面样本,模型评估结果虚高。这个参数看起来简单,实际影响很大。
4. 模型训练与评估:从朴素贝叶斯到逻辑回归的对比
4.1 朴素贝叶斯作为基线模型
情感分析本质上是一个二分类问题,所有分类算法都能用。这个项目的主流做法是用朴素贝叶斯或者逻辑回归。朴素贝叶斯尤其适合文本分类——它假设特征之间相互独立,虽然这个假设在真实文本里站不住脚(“制冷”和“效果好”明显有关联),但实际效果往往出奇地好,而且训练速度极快,适合做基线。
from sklearn.naive_bayes import MultinomialNB from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, classification_report # 多项式朴素贝叶斯适合TF-IDF这种稀疏计数特征 nb_model = MultinomialNB(alpha=1.0, fit_prior=True) nb_model.fit(X_train_vec, y_train) y_pred = nb_model.predict(X_test_vec) # 多维度评估,不要只盯着准确率 accuracy = accuracy_score(y_test, y_pred) precision = precision_score(y_test, y_pred, pos_label=1) recall = recall_score(y_test, y_pred, pos_label=1) f1 = f1_score(y_test, y_pred, pos_label=1) print(f"准确率: {accuracy:.4f}") print(f"精确率: {precision:.4f}") print(f"召回率: {recall:.4f}") print(f"F1: {f1:.4f}") print(classification_report(y_test, y_pred, target_names=['负面', '正面']))alpha是拉普拉斯平滑参数,默认1.0,它的作用是防止某个词在训练集中没出现过就导致概率为0。如果语料足够大,可以把alpha调小到0.5甚至0.1试试,有时候能提升一点精度。fit_prior=True表示让模型根据训练数据学习类别先验概率——如果正负样本均衡,这个参数影响不大;如果样本不均衡,最好设成True。
4.2 逻辑回归做精细调优
逻辑回归的效果上限比朴素贝叶斯高,它对特征权重的拟合更精细,而且可以通过正则化参数控制过拟合。
from sklearn.linear_model import LogisticRegression # 逻辑回归 + L2正则 + 高迭代次数 lr_model = LogisticRegression( C=1.0, penalty='l2', solver='liblinear', max_iter=1000, random_state=42 ) lr_model.fit(X_train_vec, y_train) lr_y_pred = lr_model.predict(X_test_vec) print(f"逻辑回归准确率: {accuracy_score(y_test, lr_y_pred):.4f}") print(f"逻辑回归F1: {f1_score(y_test, lr_y_pred, pos_label=1):.4f}")C是正则化强度的倒数——C越小正则化越强,越不容易过拟合但可能欠拟合;C越大越贴合训练集但风险是过拟合。我一般先设1.0跑一遍,然后按0.1、0.5、1、5、10这个序列做网格搜索。solver='liblinear'适合中小型数据集,用坐标下降法优化,速度比默认的lbfgs快,而且对文本这种稀疏特征矩阵支持更好。max_iter=1000是因为文本特征维度高,默认的100次迭代有时候到不了收敛点就停了,日志里会出现警告。
4.3 模型对比与选择策略
拿同一份数据跑两种模型,结果一般会是逻辑回归的准确率和F1都略高于朴素贝叶斯,差距大概在1到3个百分点。但朴素贝叶斯的训练时间只有逻辑回归的十分之一不到。
| 模型 | 训练速度 | 准确率 | 可解释性 | 适用场景 |
|---|---|---|---|---|
| 多项式朴素贝叶斯 | 极快 | 中上 | 概率计算直观 | 基线验证、海量数据 |
| 逻辑回归 | 快 | 高 | 权重向量可直接分析 | 正式模型、特征重要性分析 |
| SVM | 中 | 高 | 一般 | 中小数据集 |
| 深度学习 | 慢 | 更高 | 差 | 大语料、工业级精度 |
在做毕设的时候,最好的策略是两个模型都跑一遍,把评估指标做成表格放进论文里做对比。这既展示了工作量,也说明了选型理由。项目里如果模型文件已经保存好了,一般指的是逻辑回归或贝叶斯的joblib或pkl文件,直接加载做预测就行。
4.4 模型持久化:保存与加载
模型训练完之后,不能每次预测都重新训练一遍,要把模型和向量化器都保存下来。这里有一个关键点容易被忽略:向量化器必须跟模型一起存。
import joblib # 同时保存模型和向量化器,两个文件都不能丢 joblib.dump(lr_model, 'sentiment_model.pkl') joblib.dump(vectorizer, 'tfidf_vectorizer.pkl') # 加载时两个都要加载,顺序不能乱 loaded_model = joblib.load('sentiment_model.pkl') loaded_vectorizer = joblib.load('tfidf_vectorizer.pkl') def predict_sentiment(text): """输入一条评论,返回情感预测结果""" tokens = tokenize(text) vec = loaded_vectorizer.transform([tokens]) label = loaded_model.predict(vec)[0] prob = loaded_model.predict_proba(vec)[0] return '正面' if label == 1 else '负面', prob如果不保存向量化器,预测新评论时得重新对全量语料做fit——先不说这个操作有多慢,最关键的是你想fit也没有了原始语料。所以模型和向量化器必须成对保存、成对加载,这是血泪经验。
5. 避坑指南:情感分析项目最常见的五个翻车点
5.1 CSV编码混乱:gbk和utf-8互相打架
现象:打开下载的数据集,中文全是乱码;或者代码运行时报UnicodeDecodeError: 'utf-8' codec can't decode byte 0xc8。
原因:电商数据是爬虫采集的,很多爬虫代码在Windows下默认用gbk写文件,但分析代码统一用utf-8读取,两边编码对不上。
解决:先用文本编辑器或Python探测文件编码,再统一转成utf-8。用一句话判断编码:
with open('spider_comments_34000.csv', 'rb') as f: raw = f.read() # 判断是否包含gbk中文字节范围 print(raw[:100])或者在读取时指定encoding='gbk'试试,errors='ignore'参数也可以暂时跳过非法字符,但这是治标不治本。
5.2 样本不均衡导致模型“无脑输出正面”
现象:模型训练完准确率高达90%以上,但打开混淆矩阵一看,测试集里的负面样本几乎全预测错了。F1值惨不忍睹。
原因:采集的评论数据天然偏向正面——绝大多数买家只有满意了才愿意写评论,不满意的直接退货不太会留言。原始数据里正负比例可能达到8:2,模型只需要全预测为正面就能拿80%准确率。
解决:训练前用stratify分层采样控制正负比例,或者用class_weight='balanced'让模型自动调整类别权重,或者对少样本类别过采样(SMOTE)。
lr_model = LogisticRegression(class_weight='balanced', C=1.0)5.3 jieba分词把品牌名切碎
现象:查看特征词时发现“美的”被切成了“美”“的”,“变频”被切成了“变”“频”,这些词完全失去了语义含义。
原因:jieba默认词典是通用语料训练的,不认识电商领域的品牌词和行业术语。
解决:把品牌名、产品型号、行业术语逐行写进userdict.txt自定义词典。另外加一个操作:把分词结果里相邻的、在自定义词典里的词强制合并。
# 自定义词表示例,每行一个词 # 美的 # 变频 # 风冷无霜 # 双开门5.4 评估只看准确率,忽略精确率和召回率
现象:论文里只写了准确率一个指标,评委一问“负面评论的召回率是多少”就答不上来。
原因:准确率这个指标在分类问题里是最直观的,但它掩盖了类别内部的表现差异。
解决:至少看precision、recall、F1三个指标,并且以F1作为主要调优目标。F1是精确率和召回率的调和平均,数值高说明模型在“尽量不漏报负面”和“不误报正面”之间取得了平衡。
5.5 虚拟环境版本不一致,复现环境翻车
现象:在你自己电脑上跑得好好的,换一台电脑跑报错,ImportError: No module named 'sklearn'或者jieba版本不同导致分词结果差异。
原因:Python环境不一致,依赖库版本漂移。
解决:用项目自带的venv虚拟环境。项目压缩包里那些activate.bat、pyvenv.cfg文件就是干这个用的——在项目根目录执行venv\Scripts\activate激活环境,然后pip install -r requirements.txt。这样依赖都会装到项目自己的环境里,不影响系统全局Python。
6. 进阶玩法:把训练好的模型用起来
模型文件躺在本地没意思,来一个实际可用的预测脚本。除了单条预测,你还可以写一个批量预测脚本,拿一份新的评论csv文件,跑一遍预测,把情感标签追加到最后一列输出。
import pandas as pd import joblib model = joblib.load('sentiment_model.pkl') vectorizer = joblib.load('tfidf_vectorizer.pkl') def batch_predict(input_csv, output_csv): """ 批量预测:读取csv,新增sentiment列 输入csv至少包含content列 输出csv保留所有原字段并追加预测结果 """ df = pd.read_csv(input_csv, encoding='utf-8') df['tokens'] = df['content'].apply(tokenize) X_vec = vectorizer.transform(df['tokens']) df['predict_label'] = model.predict(X_vec) df['predict_proba'] = model.predict_proba(X_vec).max(axis=1) df.to_csv(output_csv, index=False, encoding='utf-8-sig') batch_predict('new_comments.csv', 'new_comments_labeled.csv')编码用了utf-8-sig是另一个细节——加BOM头之后生成的csv用Excel直接打开不会乱码,不然明明是对的utf-8,Excel偏偏要按ANSI解码,又是一堆乱码。
还有一个小技巧是看模型的系数,找出逻辑回归里权重最大的正面词和负面词。sklearn的逻辑回归在二分类里coef_向量直接对应每个特征的权重。
import numpy as np # 提取系数 coef = model.coef_[0] feature_names = vectorizer.get_feature_names_out() # 按权重排序,取最正面的10个词和最负面的10个词 top_positive_idx = np.argsort(coef)[-10:] top_negative_idx = np.argsort(coef)[:10] print("最正面的10个词:", [feature_names[i] for i in top_positive_idx]) print("最负面的10个词:", [feature_names[i] for i in top_negative_idx])这个操作对毕设答辩特别有用。当你展示出“静音、制冷、漂亮、喜欢、性价比”是模型认为最正面的词汇时,评委一眼就能看出模型学到了什么,比任何指标都有说服力。
从那以后我每次做完一个文本分类模型,都会强制走一遍“打印top特征词”这个流程——不只是看指标,还要看模型到底学到了什么。如果top正面词里混进一个明显中性的词,说明数据里有问题,宁可回去修数据也别急着调参。这套资源里从数据到模型的链路是完整的,但你拿到之后千万别只当“能跑就行”的作业交差,把每一环节的代码读一遍、改一遍参数跑一遍,你学到的东西绝对比分数值钱。希望帮到你。
本文还有配套的精品资源,点击获取