
简介一份面向外卖平台用户评价情感分析的Python实战资源包适合具备基础Python语法、希望掌握文本分类与情感倾向分析流程的初学者或课程项目使用者。资源包含设计思路报告word、可直接运行的源码、评论数据集csv以及正负向候选词表报告完整描述了将读取前4000条评论归为正向、后8000条归为负向并分别写入文件的情感倾向分类思路。压缩包共12个文件主要涵盖py脚本、csv数据、docx报告、md说明、txt词表及png结果图其中png展示正向/负向高频词与分类结果整体大小约3.23MB结构紧凑便于下载学习。目前已获得852人学习浏览适合用于外卖评论情感分析实践、毕业设计参考或数据分析入门练习读者可参照源码快速复现完整流程并借助设计报告理解特征选择与分类器评价细节。1. 外卖用户评价情感倾向性分析拿到这份Python资源后我能做什么外卖用户评价情感倾向性分析说白了就是把“味道不错”“送得太慢”这类评论文本自动归类成正向和负向而不是靠人一条条去看。这份Python资源把完整链路打包成了一个zip里面有设计思路报告docx、可直接运行的code.py、评论数据review.csv还有正负向候选词和高频词结果图。你拿到手能做的不只是跑通代码还能看懂样本怎么切分、情感分类器怎么训练、词云怎么生成。它适合正在做课程设计、毕业设计或者想用Python完成“数据清洗—文本分类—可视化”全流程的人。下面的拆解按我实际复现的顺序来文档、代码、数据分别怎么用以及哪里容易翻车都会讲到。2. 拆解zip包文件结构、设计报告与实际运行逻辑2.1 压缩包里都有什么从设计报告到数据文件解压后先把文件分成四类文档、代码、数据、中间产物。文档类有“emotionaltendencies 设计思路报告.docx”、“README.md”、“简短思路.md”代码类是“code.py”数据是“review.csv”中间产物是“pos_candi.txt”和“neg_candi.txt”外加“res”文件夹里的四张图片。对于一个课程设计级别的项目这个结构非常标准比很多只丢一个大文件夹的压缩包装得清楚。我习惯先看“简短思路.md”因为它最短能快速告诉我项目主线。看完之后再看“设计思路报告.docx”里面一般会写需求分析、流程图、算法选型、测试结果这部分可以直接改成自己的实验报告省掉大量从零写文档的时间。不过要注意报告里的代码截图大概率是旧版本真正运行还是要以自己的环境为准。“review.csv”是原始评论数据格式不外乎评论内容、评分、店铺名、用户ID。最让我意外的是“pos_candi.txt”和“neg_candi.txt”这两个文件它们不是结果输出而是候选情感词表作用类似词典把“绝绝子”“拉胯”这样的外卖圈口语词提前喂给分词器避免被拆碎。打开看里面都是一行一个词编码是UTF-8。如果你的记事本打开是乱码说明系统默认编码不是UTF-8后面会讲怎么处理。“res”目录里的“正向结果.png”“负向结果.png”是分类结果的可视化“正向高频.png”“负向高频.png”是词云图。整个项目用到的第三方库基本是pandas、jieba、scikit-learn、wordcloud、matplotlib这一套不需要GPU笔记本就能跑。对新手来说最花时间的往往是环境配置比如在PyCharm里配置Python解释器或者在VSCode里配置Python环境都得保证用的是同一个解释器否则在终端里pip安装的库到IDE里import会找不到。我一般先把命令行切到项目根目录再执行下面的命令pip install pandas jieba scikit-learn wordcloud matplotlib逻辑说明这一行把数据读取、分词、建模、画图需要用到的库一次装齐。如果安装时提示“No module named pip”说明Python没有加入PATH需要在安装Python时勾选“Add Python to PATH”或者直接用python -m pip install。参数说明pip install后面可以跟多个包名空格隔开可以一次装完。如果网络慢可以换成阿里云镜像源但不建议用全局代理之类的方式容易引入奇怪的问题。下面用一个表格汇总文件位置和用途文件类型我的理解emotionaltendencies 设计思路报告.docxWord完整实验报告可直接改写成课程设计提交物README.md / 简短思路.mdMarkdown项目说明和主线流程先读后者code.pyPython主程序负责读取、切分、分类、可视化输出review.csvCSV待分析的原始外卖评论pos_candi.txt / neg_candi.txt纯文本正向/负向候选情感词用于分词与词典打分res/正向结果.png图片正向样本分类结果图res/负向结果.png图片负向样本分类结果图res/正向高频.png图片正向高频词词云res/负向高频.png图片负向高频词词云这里有个细节pos_candi.txt和neg_candi.txt可能同时服务于两个阶段。第一阶段作为jieba的自定义词典让口语词不被拆开第二阶段作为情感词典给每句评论打正负分。如果你的数据里出现了“包装严实”“骑手态度好”这种明显的外卖评论词但候选词表里没有可以用记事本追加每行一个保存为UTF-8。2.2 review.csv怎么变成正负样本读取前4000条和后8000条的逻辑根据“简短思路.md”和code.py的运行行为这个资源的核心切分逻辑非常直接把review.csv按行号切前4000条写成正向样本文件后8000条写成负向样本文件。也就是说原始数据很可能已经预先分成两个部分前面是好评后面是差评中间没有单独标label列。这样做的好处是代码简单课程设计演示足够坏处是一旦数据顺序不是这样后续训练全崩。我在复现时会把这一步单独拆出来跑方便观察中间结果import pandas as pd # 读取原始评论数据如果提示中文乱码把 encoding 换成 gbk df pd.read_csv(review.csv, encodingutf-8, header0) # 前4000条视为正向后续8000条视为负向假设数据本身有序 pos_df df.iloc[:4000].copy() neg_df df.iloc[4000:4000 8000].copy() print(pos_df.shape, neg_df.shape, df.shape)这段代码的逻辑是iloc[:4000]取的是行索引0到3999一共4000条iloc[4000:40008000]取的是第4000条到第11999条。如果review.csv总行数等于12000那么neg_df其实可以用更简单的df.iloc[4000:]写法。我故意写成40008000是因为原描述里明确说了“后8000条”这样即使前面还有其它数据也能按这个区间切。参数说明header0表示第一行是列名如果CSV没有表头这里会直接把第一条评论当成列名必须改成headerNone。encodingutf-8是大多数数据的默认值但如果文件是Windows下用Excel另存的通常是GBK读进来会报UnicodeDecodeError这时候改成encodinggbk即可。切分完这只是正负样本数组还需要给它们加上标签才能训练模型。常见做法是直接构造一列pos_df[label] 1 # 正向 neg_df[label] 0 # 负向 data pd.concat([pos_df, neg_df], axis0) data data.sample(frac1, random_state42).reset_index(dropTrue)这里concat是把两个DataFrame上下合并sample(frac1)是把所有行重新打乱random_state42让打乱结果可复现。如果不打乱模型训练时前面全是正向后面全是负向交叉验证结果会非常不稳定。reset_index(dropTrue)是为了让行索引从0重新连起来避免后续train_test_split时索引残留。我一般会顺手把切分结果保存成两个csv做数据备份pos_df.to_csv(positive_comments.csv, indexFalse, encodingutf-8-sig) neg_df.to_csv(negative_comments.csv, indexFalse, encodingutf-8-sig)注意encodingutf-8-sig和utf-8的区别前者会在文件开头写入BOMExcel打开不会乱码而pandas再读的时候也不需要改配置所以这个写法更适合给不熟悉编码的人看。到这里数据集的基本形态就有了一列是评论文本一列是0/1标签。如果你拿着这份资源只是为了完成课程设计跑到这一步其实已经可以交差一半。但这里要踩一下刹车直接按行号切分对原始数据有很强假设一旦review.csv被重新排序或者换了一批数据这个逻辑立刻失效。我自己的习惯是先跑df[star].value_counts()如果有评分列就按评分阈值切后面第4章会专门讲这个坑。另外运行code.py时如果直接从终端执行python code.py程序会先打印正负样本数量再打印分类报告最后在res目录生成图片。我第一次跑的时候没有在项目根目录打开终端导致文件路径找不到报FileNotFoundError。所以建议所有相对路径都以项目根目录为基准或在IDE里把Working directory设置为项目根目录。这个习惯能帮你省掉很多莫名其妙的路径报错。3. 情感分类链路分词、TF-IDF与朴素贝叶斯的参数调法3.1 外卖评论的分词jieba库与自定义候选情感词中文评论没有空格直接套英文的split不现实。外卖评论又特别口语化“绝绝子”“踩雷”“送餐贼慢”这些词通用模型不一定认识。所以需要jieba分词并且把pos_candi.txt、neg_candi.txt里的候选词加载进来让“绝绝子”被当成整体而不是被拆成“绝/绝/子”。如果分词错误后续不管是TF-IDF还是情感词典打分都会被带偏。先看最基础的分词代码import jieba # 加载自定义候选词每行一个词 jieba.load_userdict(pos_candi.txt) jieba.load_userdict(neg_candi.txt) def cut_comment(text: str) - list: # 精确模式分词适合短文本 return jieba.lcut(text.strip())逻辑说明load_userdict会把自定义词加入词典并保证这些词在分词时优先被识别。比如“真香”不会被拆成“真/香”“拉胯”不会被拆成“拉/胯”。lcut是jieba的精确模式切分函数返回一个词列表。这里用strip()去掉首尾空格避免把换行符带进特征。参数说明load_userdict要求文件每行一个词不能有逗号或分号文件编码必须是UTF-8否则会报UnicodeDecodeError。如果你在Windows下用记事本另存记得编码选UTF-8而不是默认的ANSI。美团、饿了么评论里常有“骑手态度好”“包装严实”“漏撒”这些场景词。如果这些词不在候选词表里也建议追加进去。我第一次跑的时候没有加载自定义词结果“不好吃”被分成“不好/吃”情感倾向直接判断反了。这是血泪经验外卖评论的情感词往往藏在连续的名词形容词里不提前喂词典分词质量会非常难看。顺便说一下停用词问题。分词之后词表里大量是“的”“了”“就”“都”这类功能词它们对情感分类没有贡献。我一般会准备一个停用词集合在特征提取前过滤stopwords set([的, 了, 是, 我, 你, 它, 很, 非常])逻辑说明这个集合可以根据数据自己扩充。注意“很”和“非常”其实是程度词在某些表情感分析里应该保留但在二分类“好评/差评”任务里它们几乎不携带正负倾向所以我会先过滤避免它们占据TF-IDF的有效维度。3.2 特征工程TF-IDF与朴素贝叶斯的搭配分词之后得到的是文本但模型只认数字。常见做法是把词列表转成TF-IDF向量。TF-IDF的核心思想是一个词如果在少数几个类别里频繁出现它的权重就高如果所有评论里都出现权重就被压低。外卖评论属于短文本词表不会太大用TF-IDF加朴素贝叶斯是成熟组合比直接上深度学习稳定得多。特征工程代码from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.model_selection import train_test_split from sklearn.naive_bayes import MultinomialNB from sklearn.metrics import classification_report # 切分训练集和测试集stratify保证正负比例一致 X_train, X_test, y_train, y_test train_test_split( data[comment], data[label], test_size0.2, random_state42, stratifydata[label] ) # 中文TF-IDFmax_features5000避免维度爆炸ngram_range(1,2)保留“不新鲜” vectorizer TfidfVectorizer(max_features5000, ngram_range(1, 2), min_df2) X_train_vec vectorizer.fit_transform(X_train) X_test_vec vectorizer.transform(X_test) # 多项式朴素贝叶斯适合词频/词袋类特征 model MultinomialNB(alpha1.0) model.fit(X_train_vec, y_train) y_pred model.predict(X_test_vec) print(classification_report(y_test, y_pred, target_names[负向, 正向]))逻辑说明先做train_test_split再把训练集和测试集分别向量化。注意fit_transform和transform的区别fit_transform会从训练集学习词表transform只做转换测试集只能用transform绝不能再用fit或fit_transform否则会把测试集里的词混进词典导致评估结果虚高。参数说明max_features5000是一个折中值如果评论总量小于10000设2000也可以ngram_range(1,2)能捕捉“不太行”这类否定组合但维度会变高内存不够时可以去掉min_df2表示只有出现在至少2条评论里的词才会被保留用来过滤只在一条评论里出现的噪音名词。alpha1.0是拉普拉斯平滑系数一般保持默认。如果数据量特别小比如只有几百条评论这时候max_features要降到1000甚至直接改用情感词典打分。我见过有人拿50条样本硬跑朴素贝叶斯结果训练集F1是0.9测试集只有0.5区别就在于词表太大导致过拟合。建议先用classification_report看宏平均F1而不是盯准确率。3.3 评估结果可视化res目录里的PNG是怎么来的资源里“正向结果.png”“负向结果.png”不是截图而是程序跑出来的图表。最常用的是混淆矩阵它能清楚看出“正向被误判成负向”的数量。对于课程设计这张图是报告里的核心证据。画图代码import matplotlib.pyplot as plt from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay cm confusion_matrix(y_test, y_pred) disp ConfusionMatrixDisplay(confusion_matrixcm, display_labels[负向, 正向]) disp.plot(cmapBlues) plt.savefig(res/confusion_matrix.png, dpi150) plt.show()逻辑说明confusion_matrix把真实标签和预测标签交叉统计四个格子分别代表TN、FP、FN、TP。ConfusionMatrixDisplay负责把矩阵画成带色块的热力图。保存时dpi150打印到报告里不会发虚。如果只想看预测概率分布可以用model.predict_proba画直方图观察正负向样本的得分重叠程度。参数说明cmap可以换成Blues、Greens等display_labels顺序要和分类报告里的顺序一致。保存路径最好先判断目录存在用os.makedirs(res, exist_okTrue)否则首次运行会报找不到res目录。这里提一句zip包里的“正向结果.png”“负向结果.png”可能来自旧数据你跑新数据后应该用新图片替换保留旧图反而容易被导师看出拼凑痕迹。交叉验证也是验证模型稳定性的常用手段。常见做法是直接用cross_val_score把5折平均F1作为最终指标from sklearn.model_selection import cross_val_score scores cross_val_score(model, X_train_vec, y_train, cv5, scoringf1_macro) print(scores.mean())逻辑说明这里用的X_train_vec是fit_transform完的训练集cross_val_score会在内部再划分5份轮流当验证集。scoringf1_macro把正负两个F1做平均比准确率更能反映分类器在小类上的表现。如果得分低于0.7就该检查样本切分或特征参数。4. 避坑与常见问题五个让评价分析翻车的细节4.1 数据读取阶段的两个坑坑一是CSV乱码。现象用Excel打开review.csv一切正常用pandas读进来之后所有中文变成“锟斤拷”或“”。原因文件实际编码是GBK或GB2312read_csv却用了默认的utf-8。解决先确认文件编码再读。我一般这样兜底try: df pd.read_csv(review.csv, encodingutf-8) except UnicodeDecodeError: df pd.read_csv(review.csv, encodinggbk, errorsignore)逻辑说明先试UTF-8失败就换GBK。errorsignore会跳过非法字节但可能丢字只适合临时排查。更稳妥的是把数据转成标准编码df.to_csv(review_utf8.csv, indexFalse, encodingutf-8-sig)参数说明utf-8-sig带BOMExcel打开不会乱码pandas读它也不需要额外指定。从那以后我拿到任何csv第一件事就是查编码而不是急着打开。坑二是pandas把第一行评论当成列名。现象df.head()显示的第一行是一条评论“这家店味道不错”而不是“user_id,comment,star”这类字段。原因review.csv本身没有表头read_csv默认把第一行当列名。解决手动指定headerNone再按列数给namesdf pd.read_csv(review.csv, headerNone, names[user_id, shop, comment, star])逻辑说明names的列表长度必须和实际列数一致否则pandas会报错。如果不知道列数先不写names读一次用df.shape[1]看有几列再补齐。4.2 模型训练与表现阶段的三个坑坑三是直接按前4000条切正负样本结果模型非常偏。现象classification_report里负向F1很高正向F1不到0.3。原因review.csv的顺序未必是“好评在前、差评在后”可能原始数据按时间或用户ID排序导致前4000条里混着大量差评。解决先看评分分布再按评分切print(df[star].value_counts()) pos_df df[df[star] 4] neg_df df[df[star] 2]逻辑说明评分4到5星归正向1到2星归负向3星中性弃掉。如果review.csv里确实没有评分列只能靠关键字规则切那我不建议直接上分类器老老实实用词典打分更稳妥。判断标准很简单凡是让你“赌顺序”的切分方式都有翻车风险。坑四是分词后高频词全是停用词。现象生成的词云里最大的是“的”“了”“很”完全看不出外卖特征。原因统计词频前没有过滤停用词也没有过滤单字。解决在统计之前加过滤条件word_list [w for w in cut_comment(text) if w not in stopwords and len(w) 1]逻辑说明len(w)1会把“香”“慢”这类单字情感词也过滤掉。如果是要做情感分析单字词要谨慎我一般只在词云里过滤在模型特征里保留因为“慢”在配送场景里可能是负向信号。如果是要做词云展示这个过滤会让图更干净。坑五是词云里的中文全部变成小方块。现象程序不报错但生成的PNG里全是方框。原因WordCloud默认字体不支持中文。解决指定中文字体路径Windows下用simhei.ttfLinux下用文泉驿微米黑wc WordCloud(font_pathC:/Windows/Fonts/simhei.ttf, background_colorwhite)逻辑说明font_path不指定时PIL用默认字体渲染无法覆盖中文编码。指定黑体后中文才能正常显示。如果在Linux服务器上跑先执行fc-list :langzh查看有哪些中文字体再选一个路径填进去。这个问题不算难但几乎每个人都会遇到一次。5. 高频词与词云把分析结果变成好看的图进阶技巧5.1 从分类结果到高频词统计正向和负向评论的关键词模型训练完之后除了分类准确率我还习惯单独统计正负向的高频词这是报告里最有说服力的部分。比如正向评论里出现最多的是“好吃”“快”“新鲜”负向评论里出现最多的是“难吃”“漏”“慢”一眼就能看出外卖用户最关心什么。from collections import Counter def get_top_words(comments, stopwords, top_n30): words [] for c in comments: words.extend([w for w in jieba.lcut(c) if w not in stopwords and len(w) 1]) return Counter(words).most_common(top_n) pos_words get_top_words(pos_df[comment], stopwords, top_n30)逻辑说明get_top_words把每一条评论切词过滤停用词再用Counter统计频次most_common(30)返回频次最高的30个词。注意这里用的是切分后的正向评论集pos_df不是原始数据。如果你把pos_words直接写成txt建议用别名pos_top_words.txt避免覆盖原始候选词表pos_candi.txt。5.2 用WordCloud生成正向/负向高频词图生成词云时我通常直接传入Counter对象而不是传原文文本因为这样能严格控制词频来源from wordcloud import WordCloud wc WordCloud( font_pathC:/Windows/Fonts/simhei.ttf, background_colorwhite, width800, height600, max_words100, colormapReds ) wc.generate_from_frequencies(dict(pos_words)) wc.to_file(res/正向高频.png)逻辑说明generate_from_frequencies接收一个字典键是词值是频次。width和height控制画布尺寸max_words限制最多显示100个词避免词云太拥挤。负向图可以把colormap换成Blues这样正负向的图在风格上一目了然。我也试过用matplotlib的barh画横向条形图把Top 20词列出来效果比词云更精确import matplotlib.pyplot as plt words, counts zip(*pos_words[:20]) plt.figure(figsize(8, 6)) plt.barh(list(reversed(words)), list(reversed(counts))) plt.xlabel(频次) plt.tight_layout() plt.savefig(res/正向高频_bar.png, dpi150)逻辑说明barh画横向条形reversed是为了让频次最高的词显示在顶部。这个图在课程设计报告里比词云更好解释因为数字是精确的。词云适合封面展示条形图适合正文论证。从那以后我每次拿到新的评论数据都会先跑一次df.head()和value_counts()确认编码、列名和标签分布再决定是否按行号切分。词云也先检查字体路径不然图再好看也是白费。希望这份拆解能帮到你少走一点弯路。本文还有配套的精品资源点击获取