外卖用户评价情感倾向性分析:Python与NLP实战指南
发布时间:2026/10/1 22:25:56来源:尧图网络
简介面向外卖用户评价情感倾向性分析需求这套Python实践资料提供了从数据读取、类别划分到可视化输出的完整方案适用于Python初学者、数据分析入门者以及正在准备相关课程设计或毕业设计的学生。压缩包共12个文件以设计思路报告docx、Python源码py、评论数据csv为核心附带正负向结果图及高频词图png、候选词列表txt和说明文档md整体仅3.23MB轻量便携。已有852人学习下载。资源中code.py按正向、负向两个类别将评论分别写入两个新文件明确采用前4000条作为正向评论、后8000条作为负向评论的数据划分方式设计思路报告word对整体流程作了说明简短思路.md便于快速回顾要点。配合可视化结果与候选词清单读者可以快速掌握评论数据的分类处理、结果导出及频次展示方法并在此基础上扩展自己的情感分析项目适合作为情感分析入门的参照模板。1. 外卖用户评价情感倾向性分析把“汤有点咸”变成一行能用的标签一条写着“送餐速度可以但汤有点咸希望改进”的评价到底是好评还是差评这个问题曾让做外卖运营的同学挠头。基于Python的外卖用户评价情感倾向性分析解决的就是把这类短文本自动判定为正向、负向或中性并把“有点咸”“等了四十分钟”这类隐式吐槽也识别出来。它能直接服务于商家口碑监控、平台投诉预警和消费者行为研究。对于有Python基础、想系统接触NLP情感分析的人来说这也是一个结构完整、周期可控的练手项目从数据清洗、中文分词到模型训练和结果导出一条链路全走通。下面按我自己跑通的做法、参数设置和踩过的坑完整讲一遍。2. 评论数据从哪来爬虫、公开数据集与手工标注怎么选外卖评价的数据有三个常见来源自己爬、找公开数据集、手工标注。先说结论如果只是为了跑通分析流程优先用公开数据集或手工标注如果想做一个完整作品再考虑爬虫。2.1 三种获取方式怎么选爬虫的代价不是技术而是标注各个外卖平台的评论数据接口这几年控制得越来越严常见的抓取思路是分析App或网页端返回的JSON接口参数里带上商户ID和分页游标然后逐页解析评价文本。但爬下来只是第一步后面还有两个麻烦一是字段里混着商家回复、追评、系统标签需要二次清洗二是爬到的数据本身没有情感标签你仍然要人工标一遍。所以爬虫的真正成本不在爬而在标注。网上能搜到不少“外卖情感分析源码”类的打包项目它们大多基于同一批公开数据改造直接拿过来会发现字段名和你的数据对不上反而是坑。公开数据集方面一些高校NLP课程和竞赛平台放出过外卖评论标注数据字段通常就是“评价文本 评分 情感标签”三列拿过来可以直接训练。对大多数入门者这是性价比最高的起点能让你把精力集中在建模而不是造数据上。手工标注的话一般要定义好三分类标签正向好评、负向差评、中性客观描述。常见做法是两个人分别标注不一致的样本交给第三人仲裁。这步最费时间但能让你对数据里的语言现象有直观体感——比如“一般”“还行”这类词标注时最容易引起分歧。2.2 用Pandas清洗脏评论去重、去空和类型转换拿到原始数据第一件事不是分词而是清洗。我用Pandas处理时总结了一套固定流程直接照着走就能把“脏表格”变成能训练的结构化数据import pandas as pd df pd.read_csv(waimai_comments.csv, encodingutf-8-sig) print(df.shape, df.dtypes) df df.drop_duplicates(subset[comment]) df df.dropna(subset[comment]) df[comment] df[comment].astype(str).str.strip() df df[df[comment].str.len() 2] df[label] df[label].astype(int) print(df[label].value_counts())逻辑说明drop_duplicates按评论内容去重同一用户复制粘贴的评价值得删掉dropna删掉空值astype(str)是类型转换避免后面把数字当文本处理时直接报TypeError长度过滤去掉只有一两个字的评论这种样本对模型学习几乎没有贡献label转成int是让sklearn能直接消费标签列。参数说明这里最容易被忽略的是encoding。Windows上存的CSV常用utf-8-sig否则Pandas读出来第一列可能带着不可见的\ufeff前缀导致列名对不上。清洗之后建议把每类标签的数量打印出来看一眼如果三大类比例差距超过3:1后面建模就必须处理类别不均衡这个问题第四节专门讲。2.3 中文分词与停用词为什么直接按空格切分必翻车英文评价按空格切分就能凑合跑中文不行。“送餐快服务好”按字符切会得到“送/餐/快/服/务/好”但真正的语义单元是“送餐”“服务好”。所以中文情感分析里jieba分词是标配import jieba def cut_text(text: str) - str: seg_list jieba.lcut(text.strip()) return .join(seg_list) df[cut] df[comment].apply(cut_text) print(df[cut].head())逻辑说明jieba.lcut返回切好的词列表用空格拼回去是为了让sklearn的文本向量化模块按空格切词即可。如果直接把列表传给TfidfVectorizer会报TypeError因为它默认把输入当字符串处理。这里如果想只保留前两个词看看效果可以用seg_list[:2]这是Python数组切片在预处理里的典型用法。参数说明jieba默认词典对“外卖”“骑手”这类词切得不错但对“蜜雪冰城”“黄焖鸡米饭”这种品牌词会切碎。常见做法是加载一个用户词典蜜雪冰城 100 nz 黄焖鸡米饭 50 nz 出餐慢 10 vjieba.load_userdict(waimai_dict.txt)每行格式是“词 词频 词性”词频和词性可以按示例随便填关键是让分词器把这些词当作一个整体。词典文件我放在项目根目录和数据集中放在一起方便复现。停用词表在这一步也要参与。中文停用词表流传版本很多建议用哈工大停用词表做基础再手工删除里面不该停的词——这个坑下一章重点讲因为“不”这类否定词一旦被停用情感分析直接废一半。3. 情感分类建模用TF-IDF加朴素贝叶斯跑通第一版数据清洗、分词完成之后进入核心环节。我一般不会一上来就上深度学习而是先用TF-IDF加朴素贝叶斯跑通一版拿到一个可复现的基准线。这个选择有三个原因训练快、可解释、参数少。3.1 为什么第一版必须是“TF-IDF 朴素贝叶斯”外卖评价语料短、词表不大TF-IDF能把“好吃”“难吃”“快”“慢”这类关键词的权重抬起来而朴素贝叶斯在短文本分类上一直是性价比极高的基线模型。相比之下Word2Vec需要足够的语料训练才稳定BERT需要GPU且推理慢。对一个几千条样本的外卖评价数据集朴素贝叶斯完全够用而且它给每个词算出的概率权重能直观解释“模型认为哪个词最像差评”。此外TF-IDF加朴素贝叶斯的组合对硬件要求极低。只要本地Python环境装好了scikit-learn和jieba两个库就能跑通不需要为这个项目准备GPU。环境配置上用PyCharm或VS Code都行核心是把sklearn和pandas装进同一个虚拟环境别让多个Python版本互相干扰。3.2 用Pipeline串起特征工程避免维度错位这里的关键是用sklearn.pipeline.Pipeline把向量化和分类器做成一个整体避免训练集和预测集各自fit导致特征维度不一致的低级错误from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.naive_bayes import MultinomialNB from sklearn.pipeline import Pipeline from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test train_test_split( df[cut], df[label], test_size0.2, random_state42, stratifydf[label] ) pipe Pipeline([ (tfidf, TfidfVectorizer(ngram_range(1, 2), max_features10000, min_df2)), (clf, MultinomialNB(alpha0.1)) ]) pipe.fit(X_train, y_train) acc pipe.score(X_test, y_test) print(accuracy:, acc)逻辑说明train_test_split里的stratifydf[label]按标签比例分层抽样保证训练集和测试集的正负样本比例一致避免“测试集里全是差评”这种偶然事件。TfidfVectorizer把分好词的文本转成稀疏矩阵MultinomialNB用alpha0.1做拉普拉斯平滑。参数说明三个最值得调的向量化参数是ngram_range、max_features、min_df。ngram_range(1,2)表示同时保留单个词和相邻两个词的组合“不/好吃”这种否定加形容词的词组会被识别为整体特征max_features10000把特征数上限卡住避免矩阵膨胀min_df2表示在少于2条文档里出现过的词直接丢弃减少低频噪音。提示把ngram_range拉到(1,3)准确率可能小幅提升但特征数量会明显增大训练耗时和内存都跟着涨。我一般用(1,2)起步跑通后再往上试。3.3 用交叉验证替random_state赌运气第一次跑通时我直接固定random_state42做单次划分得到86%的准确率开心了两秒。换成另一个随机种子就只有78%这说明单次划分的结果不够可靠。常见做法是用交叉验证对同一批数据做多次评估from sklearn.model_selection import cross_val_score scores cross_val_score(pipe, df[cut], df[label], cv5, scoringf1_macro) print(cv scores:, scores) print(mean:, scores.mean())逻辑说明交叉验证在五份数据上轮流做训练和验证取均值的稳定性远高于单次划分。scoringf1_macro是考虑到类别不均衡的评估指标三个类别的F1先各自计算再取平均能反映弱类别上的真实表现而不是被多数类的高准确率掩盖。到这里第一版模型就落地了。如果交叉验证的F1在0.75以上这个模型已经能支撑简单业务场景如果不到0.7先别急着换模型大概率是数据那侧的坑继续看第四章。4. 五个典型踩坑记录从准确率61%到86%的排查过程第一版模型跑出来的交叉验证F1只有0.61离谱的是直接“全部猜差评”也能有接近0.60的准确率。接下来这五个坑是我在排查过程中实际遇到并解决的每一条都可以复现。4.1 标签不均衡模型学会了偷懒现象训练集里差评占65%好评25%中性10%。预测结果几乎全是差评准确率虚高但毫无业务价值。原因朴素贝叶斯这类模型在训练时优化整体准确率不关心少数类。类别比例失衡时模型发现“全猜差评”也能拿高分就不再学习好评和中性样本的特征。解决朴素贝叶斯没有class_weight参数所以换成逻辑回归并设置class_weightbalancedfrom sklearn.linear_model import LogisticRegression pipe_lr Pipeline([ (tfidf, TfidfVectorizer(ngram_range(1, 2), max_features10000, min_df2)), (clf, LogisticRegression(class_weightbalanced, max_iter1000)) ])class_weightbalanced让sklearn自动按类别频率反比放大少数类的损失权重强迫模型重视好评和中性样本。换成逻辑回归后F1从0.61涨到0.74。4.2 emoji和叠词被切碎表情符号不是噪音是信号现象“烧烤好吃[赞]”被jieba切成“烧烤/好吃/[/]”[赞]这个关键正向信号直接丢了“好好好好吃”被切成一堆“好”字模型完全看不懂。原因jieba对emoji和连续叠词的处理一直是短板。而外卖评价里表情符号恰恰是用户情绪的强表达丢掉等于浪费了高价值特征。解决分词前把emoji先映射成占位符号让它作为特征参与训练emoji_map { [赞]: POS_EMOJI , [差评]: NEG_EMOJI , [大哭]: NEG_EMOJI , [微笑]: POS_EMOJI , } def normalize_emoji(text): for k, v in emoji_map.items(): text text.replace(k, v) return text df[comment] df[comment].apply(normalize_emoji) df[cut] df[comment].apply(cut_text)把POS_EMOJI、NEG_EMOJI当作文本里的特殊词向量化模块会为它单独生成特征。实测这一步在测试集上能带来2到3个百分点的F1提升。4.3 停用词表把否定词杀了“不新鲜”变成“新鲜”现象测试集里“不新鲜”“不好吃”被模型判成好评。排查预处理管道才发现公共停用词表里直接停掉了“不”字“味道不错”分词后变成“味道 不错”完全丢掉转折和否定。原因公共停用词表为了通用性把“不”“没”“别”这类高频否定词一并归入停用词对情感分析是致命伤。解决加载停用词表后手动把否定词和程度副词从表里移除stopwords set() with open(stopwords.txt, encodingutf-8) as f: for w in f: word w.strip() if word not in {不, 没, 别, 莫, 不怎么, 不太, 没有, 从未}: stopwords.add(word)保留“不”“没”这类否定词它们才能参与ngram组合“不/好吃”成为独立的强特征。这一步是情感分析项目的必修课漏掉的后果就是模型对否定句集体判断反向。4.4 训练和预测向量维度对不上重复fit惹的祸现象模型训练完对单条新评论预测时报错提示特征维度不一致。原因是每条新评论都单独调了fit_transform得到的词集合和训练语料完全不同。原因TfidfVectorizer的fit是在全部训练语料上建立词表之后用transform把新文本映射到同一张词表上。对新文本直接fit_transform得到的是另一套特征空间维度自然对不上。解决始终只对训练集fit之后一律transform。用Pipeline封装后基本不会再踩因为Pipeline保证训练和预测走同一条路径def predict_single(pipe, text): cut cut_text(text) return pipe.predict([cut])[0] print(predict_single(pipe_lr, 等了四十分钟还没送到))predict方法内部会自动调用训练阶段保存的向量化器做transform不需要手动构造特征。这个坑的教训是凡是用sklearn做文本分类训练和预测必须共用同一个向量化器实例。4.5 “默认好评”和超短评稀释模型现象数据里大量“此用户未填写评价内容”“默认好评”之类的模板文本还有“不错”“还行”这类两字短评模型在这些样本上来回震荡训练曲线忽高忽低。原因平台自动填充的评价没有真实情感属于噪声标签超短评信息量太少模型学不出稳定规律。解决清洗阶段就把这两类过滤掉而不是让模型去猜filtered df[ ~df[comment].str.contains(默认|未填写|系统默认, naFalse) (df[comment].str.len() 4) ]str.contains的naFalse让缺失值不报错长度过滤从2提到4把“等了”这类无意义废样本清出去。真实用户写的有信息量评价普遍在4个字以上。这五个坑按顺序排查完模型在五折交叉验证上从0.61稳定到0.86。准确率低先别怪模型先怀疑数据这是做NLP项目最值得记住的一条经验。5. 模型效果怎么看混淆矩阵、趋势图和业务报表三件套模型训练完了但“准确率86%”这句话对业务同事没有任何意义。他们要看到的是这个月差评集中在哪个环节、负面情绪在变多还是变少。所以这一章把评估指标和可视化一次补齐。5.1 准确率不足信用混淆矩阵和分类报告做体检外卖评价三分类里准确率高可能只是因为“差评类”做得好。让模型在测试集上预测然后输出混淆矩阵和分类报告from sklearn.metrics import confusion_matrix, classification_report y_pred pipe_lr.predict(X_test) cm confusion_matrix(y_test, y_pred) print(混淆矩阵:\n, cm) report classification_report(y_test, y_pred, target_names[negative, neutral, positive]) print(report)逻辑说明classification_report里每一行的precision、recall、f1-score分开展示。重点看neutral这个类——外卖评价里中性样本最少模型最容易把“还行”这类夹生评价随机分成好评或差评。如果neutral的recall低于0.5后面就要考虑给中性样本补充数据而不是继续调模型。参数说明target_names的顺序要和df[label]里的标签编码顺序一致否则矩阵和报告的行列对不上看结果时容易被误导。5.2 用matplotlib画负面趋势图和词云模型给每条评论打上标签后可以按周统计正负倾向的占比变化。这一步用pandas的Grouper和matplotlib完成属于数据分析与可视化的常规动作import matplotlib.pyplot as plt df[date] pd.to_datetime(df[date]) df[pred] pipe_lr.predict(df[cut]) weekly df.groupby([pd.Grouper(keydate, freqW), pred]).size().unstack(fill_value0) weekly[neg_ratio] weekly[0] / weekly[[0, 1, 2]].sum(axis1) ax weekly[neg_ratio].plot(figsize(10, 5), title外卖评价负面占比周趋势) ax.set_xlabel(日期) ax.set_ylabel(负面评价占比) plt.tight_layout() plt.savefig(neg_trend.png, dpi150)df[date]需要提前用pd.to_datetime转换否则Grouper报TypeError这是最常见的日期类型转换问题。unstack(fill_value0)把标签列拆成宽表neg_ratio计算每周负面占比。词云部分用wordcloud库对分好词且被预测为负面的文本做可视化from wordcloud import WordCloud neg_text .join(df[df[pred] 0][cut].tolist()) wc WordCloud( font_pathC:/Windows/Fonts/msyh.ttc, width800, height400, background_colorwhite, max_words200 ).generate(neg_text) wc.to_file(neg_wordcloud.png)font_path必须指向中文字体文件Windows上常见的是微软雅黑。不指定中文字体词云会生成一堆方框乱码这是wordcloud最经典的翻车现场。5.3 把预测结果导成Excel给运营同事看的是表不是模型模型预测完最终交付的是一份带预测标签和原始文本的表格。to_excel导出运营同事可以直接在Excel里筛选和排序df[pred_label] df[pred].map({0: 差评, 1: 中性, 2: 好评}) df[[comment, pred_label]].head(200).to_excel( waimai_pred_result.xlsx, indexFalse, sheet_name评价标签 )map把数字标签翻译成人能看懂的中文。to_excel需要openpyxl库pip install openpyxl装一下。这里只导出前200行是因为全量导出在Excel里打开太卡按时间窗口或分层抽样导出才是稳妥做法。6. 升级路线从朴素贝叶斯到预训练模型怎么走第一版跑通后如果数据量从几千条涨到几十万条或者要分析的不再是外卖而是多个品类朴素贝叶斯就不够用了。我的升级路线分四步。第一步保持TF-IDF特征不变把朴素贝叶斯换成LinearSVC或带class_weight的逻辑回归。这一步只改一行代码往往能带来3到5个百分点的F1提升训练耗时几乎不变。第二步引入Word2Vec词向量。用gensim在外卖语料上训练词向量把每条评价的全部词向量取平均作为模型输入from gensim.models import Word2Vec sentences [list(jieba.cut(s)) for s in df[comment]] model Word2Vec(sentences, vector_size128, window5, min_count3, workers4)vector_size128是常见选择太小表达力不够太大在小数据集上容易过拟合。window5表示每个词看前后5个词的上下文min_count3丢弃出现次数少于3次的词。第三步直接用预训练语言模型做微调。这是当前效果最好的路线但需要GPU。如果只有CPU跑蒸馏版BERT或TextCNN也能看。注意预训练模型对脏文本的容忍度反而更低清洗环节要更严格。第四步也是最后一步用五折交叉验证把每一步的F1、精确率、召回率记录成一张对比表用数字决定是否值得升级不凭感觉说话。做NLP项目至今我的个人习惯是保留好每次的预处理脚本、停用词表和用户词典这些才是真正有复用价值的东西。模型反而是一次性的换一个语料领域就要重新训练。这个外卖评价情感分析项目值得你从头到尾完整跑一遍因为它把数据清洗、中文分词、特征工程、模型训练、评估可视化全串在了一条链路上。希望这份记录能帮你少踩几个坑也希望能看到你在这个方向上做出自己的东西。本文还有配套的精品资源点击获取
网站建设高端定制企业官网