ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python外卖评价情感倾向性分析:从词典法到机器学习实战

Python外卖评价情感倾向性分析:从词典法到机器学习实战 简介基于Python的外卖用户评价情感倾向性分析资料包主要面向需要完成文本情感分类课设、毕业设计或入门自然语言处理的Python学习者。内容以外卖评论为数据对象讲解如何将评论文本按正向与负向分为两类并分别写入独立文件其中读取前4000条作为正向样本、后8000条作为负向样本实现思路与数据划分逻辑清晰。压缩包共12个文件包含docx设计思路报告、py可执行源码、csv原始评论数据、png正负向结果及高频词可视化图、txt候选词表与md说明文档整体仅3.23MB便于快速下载和按需查阅。目前已有852人学习浏览读者可依据源码和可视化图表复现完整流程也可替换自有数据做情感分类拓展是一份兼具课程参考与实战练习价值的数据分析资料。1. 外卖评价情感倾向性分析这个Python项目解决了什么实际问题打包成“基于Python的外卖用户评价情感倾向性分析.zip”这种形式通常是开发者把自己跑通的一整套代码、情感词典、样例数据和说明文档打包在一起而不是一个只有几十行脚本的玩具项目。它要解决的核心任务是把外卖用户写的一段评价文本“味道不错就是等了一个小时”自动换算成情感倾向标签或分数供运营做差评预警、店铺口碑排名、配送异常分析。这个方向对从业者很有吸引力因为不需要GPU不需要一开始就准备大量标注数据一个Python环境加几个开源库就能落地。它适合刚入门自然语言处理的Python学习者也适合本地生活行业的业务分析师以及想给评论区做自动化洞察的产品经理。接下来的章节我会按“先定路线再做工程再调参数”的顺序把这个标题拆成一套可以照做的落地流程。2. 情感分析的两种路线词典打分还是机器学习分类2.1 基于情感词典的极性打分先能跑再谈准情感词典法的思路很直接把评论文本拆成词每命中一个情感词就累加一个情绪分数。正面词加分负面词减分否定词反转程度副词加权。外卖场景里“好吃”“分量足”“出餐快”“划算”等是高频正面词“难吃”“慢”“凉了”“油腻”等是高频负面词。这个方法最大的优点是不需要标注数据只需要准备词典和分词工具一个小时就能跑通第一版。它适合作为所有更复杂模型的基线也适合在业务方要求“先说清楚怎么算出来的”时作为解释依据。下面是一个最简实现使用jieba分词和微型词典import jieba # 微型情感词典权重越大表示情绪越强烈 pos_dict { 好吃: 2.0, 满意: 2.0, 推荐: 1.5, 新鲜: 1.5, 快: 1.0, 划算: 1.5, 分量足: 2.0 } neg_dict { 难吃: -2.5, 慢: -1.5, 差: -2.0, 贵: -1.5, 凉了: -2.0, 油腻: -1.0, 不新鲜: -2.5 } negators set([不, 没, 没有, 别]) def lexical_score(text): words jieba.lcut(text.lower()) score 0.0 for i, w in enumerate(words): weight None if w in pos_dict: weight pos_dict[w] elif w in neg_dict: weight neg_dict[w] if weight is None: continue # 检查前一个词是否是否定词如果是则反转极性 if i 0 and words[i - 1] in negators: weight -weight score weight return score print(lexical_score(好吃但是出餐太慢))这段代码的逻辑是遍历分词结果每遇到一个情感词就取出对应权重如果这个词前一个位置是否定词就把权重取反。这里的参数有三个地方可以调词典权重2.0和-2.5表示情绪强度、否定词表大小、以及“否定词位置窗口”的长度。上面只检查前一个词实际评价里会出现“一点都不难吃”这种两词前置的否定结构更完整的做法是把窗口扩大到前两个位置并区分“不”和“不怎么样”这类弱化表达。需要提醒的是词典法得分是连续值但它不等同于概率。同一句话“好吃但是出餐太慢”词典法会给2.0 (-1.5) 0.5这并不意味着一半正面一半负面只说明两种情绪同时存在。后续做正负判断时往往会设置阈值比如得分大于等于1判断为正面小于等于-1判断为负面中间归为中性。这个阈值也需要根据店铺评价分布来调。2.2 基于机器学习的情感分类数据够了再上模型当手上积累了几千条历史评价并且有条件标注一批“正面/中性/负面”样本时就可以把情感分析当成一个三分类文本分类任务。常见做法是TF-IDF提取文本特征 逻辑回归分类器。选逻辑回归而不用深度网络原因有两个一是外卖评论文本短平均几十个字深度模型在短文本上的优势有限二是逻辑回归训练快几分钟就能出结果而且模型权重可以直接映射回词语方便反查“模型到底被哪些词带偏了”。下面的代码展示了从文本到模型的完整流程from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report # 假设 df 已经有 cleaned_text 和 label 两列label 取值为 0/1/2 texts df[cleaned_text].tolist() labels df[label].tolist() # 文本向量化限制特征数量并保留双词组合 vectorizer TfidfVectorizer(max_features5000, ngram_range(1, 2), min_df2) X vectorizer.fit_transform(texts) # 划分训练集和测试集随机种子固定便于复现 X_train, X_test, y_train, y_test train_test_split( X, labels, test_size0.2, random_state42, stratifylabels ) model LogisticRegression(C1.0, class_weightbalanced, max_iter1000) model.fit(X_train, y_train) print(classification_report(y_test, model.predict(X_test)))这里的核心参数有四个。max_features5000表示只保留TF-IDF值最高的前5000个词能有效降噪ngram_range(1, 2)同时保留单个词和相邻双词“不好吃”会被当成一个双词特征从而抓住否定结构min_df2表示出现次数少于2次的词直接丢弃避免某个评论的个性化表达干扰模型class_weightbalanced让模型根据类别数量自动调整权重用来缓解“差评数量远少于好评”带来的偏向问题。逻辑回归的C是正则化强度的倒数C越小正则越强模型越稳定外卖评价文本噪声大C在0.5到2.0之间通常都能工作具体需要结合交叉验证选择。如果在本地跑模型时报ConvergenceWarning可以调大max_iter或者对TF-IDF特征做norml2归一化。2.3 两条路线怎么选数据量决定起点解释性决定终点词典法和机器学习法并不是替代关系而是不同阶段的产物。下面的对比表可以帮你快速做判断对比项情感词典法机器学习法准备材料情感词典 分词器已标注评论数据数据量需求不需要标注数据每个类别至少200条冷启动速度1小时以内半天到一天可解释性高能定位到具体词中可通过特征权重观察维护成本新词需要持续补充新数据需要重新训练典型局限覆盖率有限固定表达识别弱依赖标注质量领域迁移会掉点我的经验是如果只是看店铺口碑的整体涨跌词典法已经够用如果要做实时差评拦截并且对每条评论的判断置信度要求高就必须上机器学习。真正高效的工程做法是“两条腿走路”先用词典法跑出伪标签再基于伪标签训练逻辑回归最后用人工抽检修正模型输出。这样既能省下从头标注几万条数据的时间又能让模型比单一词典法覆盖更多领域表达。3. 用Python搭建外卖评价情感分析流程从环境到Excel结果3.1 环境准备与数据读取版本、编码、字段缺一不可很多从zip包解压出来的项目第一道坎不是算法而是环境。我见过太多次因为Python解释器选错或包版本不对导致jieba或sklearn一导入就报错的场景。开始之前先建一个干净的虚拟环境不要直接在系统全局环境里慢慢补依赖。推荐用conda创建Python 3.9环境这个版本对旧代码和新库的兼容性都比较均衡。conda create -n sentiment python3.9 -y conda activate sentiment pip install pandas jieba scikit-learn openpyxl chardet这几条命令分别做了三件事创建名为sentiment的独立环境激活它安装处理数据、分词、机器学习、写Excel和识别编码所需的库。如果pip install下载很慢可以使用国内镜像源但注意不要把镜像源写死在项目说明里否则他人复现时可能被误导。环境激活后在VSCode里按CtrlShiftP选择 “Python: Select Interpreter”指定刚才创建的sentiment环境。这一步往往比安装包本身更容易翻车因为VSCode会默认选中全局解释器导致命令行里pip install装到了conda环境编辑器却用另一个Python。确认解释器路径里包含sentiment再继续下一步。数据读取是最容易忽略的地方。外卖平台导出的CSV文件经常是GBK或GB18030编码直接用pd.read_csv(waimai.csv)默认用UTF-8解码轻则乱码重则直接抛UnicodeDecodeError。建议先用chardet自动探测编码import pandas as pd import chardet with open(waimai.csv, rb) as f: encoding chardet.detect(f.read(10000))[encoding] df pd.read_csv(waimai.csv, encodingencoding) print(df.head()) print(df.columns.tolist())读进来后先看列名。通常一份外卖评价数据会包含user_id、shop_name、rating、comment、create_time等字段。注意rating是星评级不能直接当作情感标签因为用户可能打3星但文本写“还可以”也可能打4星但吐槽“份量少”。真正的情感分析应该基于评论文本星级只能用来做后续交叉验证。3.2 文本预处理清洗、分词、停用词一口气做完外卖评论文本很乱常见问题包括emoji、重复标点、中英文混写、错别字。先写一个统一的清洗函数把所有内容规整成“中文字母数字加空格”的形态。import re import jieba def clean_text(text): if not isinstance(text, str): return # 去掉常见emoji区域 text re.sub(r[\U0001F300-\U0001FAFF], , text) # 非中文、字母、数字、空白的字符统一替换成空格 text re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9\s], , text) # 多个空格合并成单个空格 return re.sub(r\s, , text).strip() def tokenize(text): clean clean_text(text) words jieba.lcut(clean) stopwords set([的, 了, 是, 我, 你, 他, 就, 都, 还, 也, 有]) # 去掉停用词和单字词 return [w for w in words if w not in stopwords and len(w) 1] df[clean_text] df[comment].apply(clean_text) df[tokens] df[clean_text].apply(tokenize) df[token_text] df[tokens].apply(lambda x: .join(x))这里的关键点是洗涤顺序。先删emoji再替换特殊符号最后合并空格。如果把替换符号放在删除emoji之前某些emoji会被拆成乱码字符反而增加噪声。停用词表要精简只去掉对情感判断没帮助的虚词不要做“去掉所有形容词”这种错误的去噪操作因为形容词是情感判断的主要载体。分词参数方面jieba.lcut返回list默认使用精确模式。外卖场景里建议给jieba增加食材和菜品词例如“麻辣烫”“小碗菜”“蛋炒饭”等否则“小碗菜”会被拆成“小碗”和“菜”影响后续情感词匹配。可以在分词前调用for word in [麻辣烫, 小碗菜, 蛋炒饭, 螺蛳粉]: jieba.add_word(word)3.3 计算情感得分并导出Excel让结果能直接发给业务方把第二章的词典打分函数应用到切分好的tokens列然后生成业务可读的标签列最后写入Excel。# 复用第二章的 pos_dict/neg_dict def score_row(tokens): score 0.0 for w in tokens: if w in pos_dict: score pos_dict[w] elif w in neg_dict: score neg_dict[w] return score df[sentiment_score] df[tokens].apply(score_row) df[sentiment_label] df[sentiment_score].apply( lambda s: 正面 if s 1 else (负面 if s -1 else 中性) ) # 按店铺聚合输出简表 summary df.groupby(shop_name)[sentiment_score].agg([mean, count]) summary.to_excel(waimai_sentiment_result.xlsx, indexTrue)写入Excel需要openpyxl库to_excel默认会创建新的excel文件。如果数据量大建议先写一份全量明细再写一份按店铺汇总的均值表。实际交付时业务方更在意的是“哪些店铺上周差评变多了”而不是每条的原始分数所以可以在此基础上继续做周环比变化。词典法跑出来的分数有一个隐藏问题分数绝对值大小并不能直接反映情绪强度。“好吃”给2分“超级好吃”如果“超级”不在程度副词表里也可能只给2分导致“超级好吃”和“好吃”被混为一谈。解决办法是在词典匹配前先处理程度副词比如“超级/太/很/非常”对后一个情感词权重乘1.5到2.0这样就能区分“满意”和“非常满意”。这一步可以在第五章节的否定词反转逻辑里一并实现。4. 模型训练与阈值调参让情感分析从“能用”到“好用”4.1 标注样本与训练划分不要直接在Excel里手工打标签到这一步你已经有了词典法产生的分数和标签。接下来要训练机器学习模型首先需要一份相对可靠的训练标签。很多人的第一反应是打开Excel一条条打正/中/负这样做效率低且容易前后标准不一致。更好的方式是用脚本先粗标再人工只修正“模型可能错的地方”。具体做法是用词典法的分数生成一个映射标签把分数分为0/1/2三档然后按类别分层抽样每类抽几十条人工复核。抽样代码里设置随机种子保证每次复现同一个样本集。df[weak_label] df[sentiment_score].apply( lambda s: 0 if s 0 else (2 if s 0 else 1) ) sample df.groupby(weak_label, group_keysFalse).apply( lambda x: x.sample(min(50, len(x)), random_state1) ) sample.to_csv(sample_for_human_check.csv, indexFalse)这个代码的逻辑是weak_label作为伪标签先用它做分层抽样。人工只需要抽出的几百条而不是几千条。人工修正后再用修正后的标签重新训练模型。这里有个参数值得注意groupby(...).apply在一些pandas版本中会有性能警告对于10万条以下的数据没有实际影响如果数据量更大可以改用sample参数结合StratifiedShuffleSplit。标注时最好三个人独立标然后取多数结果。外卖评价的“中性”判断本身很主观比如“一般般”有人标中立有人标负面。这类分歧样本单独拎出来要么丢弃要么单独设置一个“争议”类别不要混进训练集。4.2 必调参数TF-IDF窗口、正则强度、得分阈值模型从“能跑”到“好用”差别往往不在算法而在参数。下面这张表是我做外卖评价时最常调的几个参数参数作用常见范围max_features特征词数量上限3000~8000ngram_range保留单词/双词组合(1,2) 或 (1,3)min_df忽略出现次数过少的词2~5max_df忽略出现频率过高的词0.8~0.95C逻辑回归正则强度0.5~2.0class_weight控制类别权重balanced 或手动指定sentiment_threshold词典得分转标签的阈值0.5~1.0 之间以TfidfVectorizer为例max_features3000和min_df2的组合能有效去掉只在一条评论里出现的“一次性词语”避免模型学到无关噪声。ngram_range(1, 2)很重要因为外卖评价里“不辣”“太辣”都是双词表达只保留单字词会丢失关键情绪信息。用网格搜索找出最优组合from sklearn.pipeline import Pipeline from sklearn.model_selection import GridSearchCV pipe Pipeline([ (tfidf, TfidfVectorizer()), (clf, LogisticRegression(max_iter1000)) ]) param_grid { tfidf__max_features: [3000, 5000], tfidf__ngram_range: [(1, 2), (1, 3)], tfidf__min_df: [1, 2], clf__C: [0.5, 1.0, 2.0] } grid GridSearchCV(pipe, param_grid, cv5, scoringf1_macro) grid.fit(df[token_text], df[human_checked_label]) print(grid.best_params_)这段代码使用Pipeline把向量化和分类器串起来GridSearchCV会遍历参数组合并输出最优参数。需要注意两个点一是样本量超过1万条时网格搜索的组合数要控制住否则单机要跑很久二是评分指标用f1_macro而不是默认的accuracy因为外卖评价中差评比例明显偏低只看准确率会掩盖“差评全部没抓到”的问题。如果时间紧可以把GridSearchCV换成RandomizedSearchCV并设置n_iter20。词典法的得分阈值也需要调。默认阈值是正负1但不同业务对“负面”的定义不同。有的老板认为打4星也算不满有的认为只有出现“难吃”“差评”才值得介入。常见做法是遍历阈值看每个阈值下人工抽检错误率的变化然后选择错误率最低、且与星级一致性最高的点。比如统计每个阈值下“模型判断负面但用户打了4星”的比例这个比例越低阈值越可靠。4.3 评估别只看准确率重点看差评召回率模型训练完后最忌讳只打印一个accuracy。外卖场景下差评漏检的成本远高于误检——一条没被发现的“食物变质”差评可能导致客诉升级而把一条好评误判成差评顶多是运营多打一个回访电话。因此评估时至少要看混淆矩阵和分类报告。from sklearn.metrics import classification_report, confusion_matrix y_pred model.predict(X_test) print(classification_report(y_test, y_pred, target_names[负面, 中性, 正面])) print(confusion_matrix(y_test, y_pred))输出结果中需要重点看“负面”这一行的recall召回率。如果召回率低于0.7说明大量差评被模型放过了。改进手段有三种把class_weight调成{负面: 2.0, 中性: 1.0, 正面: 1.0}给差评更大的惩罚权重或者把Tfidf特征中ngram_range加到(1, 3)还有一种非常有效的方法是增加“差评关键词表”作为额外特征比如“变质”“拉肚子”“苍蝇”等词一旦出现直接给样本加一个强特征维度让模型更敏感。除了测试集指标还要做业务侧验证。把训练好的模型预测结果和用户打的星级交叉一下如果模型预测负面但用户打了5星优先查看这组样本。通常有两种解释要么是文本里的反讽例如“真棒汤全洒了”要么是模型出错。这种交叉验证不需要额外标注是最便宜的检验方法。5. 外卖评价情感分析避坑指南5个典型翻车场景5.1 现象好评全被识别成中性分数一直贴着零第一版词典法跑出来满屏都是中性尤其是“分量很足”“出餐快”“小哥态度好”这类外卖场景好评。原因很明确通用情感词典根本没收录这些领域表达模型感知不到情绪。解决方法是把外卖高频描述词补进去比如“足”“准时”“热情”“新鲜”“划算”。更系统化的做法是先统计历史评价里高频的形容词和动宾短语再人工标一轮。注意不要抄通用词典里所有词像“清淡”在轻食店是正面在重口味店铺可能代表不满意要按品类维护。5.2 现象否定句“没有不好吃”被算成负面用户写“没有不好吃”意思是“还挺好吃”但词典会把“不好吃”匹配成负面词结果得分变负。原因是“不好吃”是个固化词早期词典为了省事直接把它作为一个情感词收录而否定逻辑没有机会介入。解决方法是把固化词拆掉不要收录“不好吃”“不新鲜”这类组合让否定词检查窗口去处理。具体做法是遍历词典时如果情感词前一个词是否定词就把当前词权重取反如果词典里已经存在“不好吃”要从词典删除否则会和反转逻辑叠加造成“负负得正”或重复计算。5.3 现象“yyds”“无语子”这类网络用语让模型失灵年轻用户爱用网络梗“这家店yyds”表达极度认可“味道无语子”表达不满但词典和TF-IDF训练集里都没有这些词模型只能判成中性。原因是外卖评价内容更新快静态词典和定期训练都跟不上。解决方法是给项目增加一个“新词补充”步骤把新出现的网络词用jieba.add_word加进分词器并同时补进情感词典。机器学习路线更麻烦需要在训练前先用词向量查相似词扩充样本或者把这些词替换成语义一致的常规表达比如把“yyds”替换成“非常棒”再来打分。这个替换规则要单独维护避免每条评论都被改写导致原始表达丢失。5.4 现象代码换台电脑就报错环境版本完全对不上本地跑得好好的zip包发到同事电脑直接抛AttributeError或ImportError。原因几乎都是依赖包版本不匹配比如scikit-learn1.1版本把某个参数改名或者jieba版本差异导致分词结果变了。解决方法是先在项目根目录写死requirements.txt并明确执行安装时用锁定版本而不是。常见做法是pip install scikit-learn1.0.2 jieba0.42.1 pandas1.3.5 openpyxl3.0.10另外在VSCode里配置Python解释器时也很容易掉坑。命令行里pip list能看到包但VSCode仍报错“找不到模块”原因是解释器选错了。选到conda环境路径下的python.exe后再重启终端问题基本就解决了。如果还是不行用python -c import jieba验证不要只靠IDE提示。5.5 现象原始数据乱码和重复标点让分词彻底错乱CSV文件打开能看到乱码或者评论里“好吃”被分词器拆成“好吃”和四个感叹号情感词匹配直接失败。原因是文件编码可能是GB18030且导出时带了BOM重复标点没有被归一化。解决方法是先用chardet.detect识别编码再在清洗函数里把所有连续符号替换成单个空格。但要注意不要把所有感叹号都删干净因为“好吃”和“好吃”的情绪强度不一样。我的做法是在清洗时保留感叹号数量作为一个辅助特征比如新建一列exclaim_count模型输入里加上它差评识别精确度能提升几个点。6. 没有标注数据也能起步用伪标签和外场校验做增量如果没有一份现成的标注数据别急着花钱去人工标几千条。我推荐一个有效路径先把词典法打分结果当作弱标签用弱标签训练逻辑回归再让模型输出置信度只挑置信度中等比如概率在0.4到0.6之间的样本给人校验。这些样本恰恰是词典法犹豫不决、模型也拿不准的地方修正它们对模型提升最大。三轮迭代下来人工只需要处理几百条就能把差评召回率从60%多提到85%以上。另一个实用技巧是外场校验。把星级评分当作弱监督信号1星或2星的评价模型绝对不可以预测成正面4星或5星的评价基本不应该预测成负面。把冲突样本单独输出优先排查。这个校验方法不需要额外成本却能在模型上线前挡住最明显的错误。我自己就曾经迷信“通用预训练模型”直接拿一个通用情感分类器跑外卖评价结果“米饭太硬”“汤洒了”这类具体物理描述全部被归成中性因为预训练数据里根本没有这种场景。后来我重新调整流程先用词典法跑通全链路再用伪标签训练小模型最终按业务验收口径把准确率从72%拉到89%。那之后我的习惯是任何情感分析项目先把词典和规则做成一道“后悔药”再考虑要不要上复杂模型。这个方向真正值得投入的前提是你有持续回流的真实评价数据并且愿意每个月做一次样本复核。如果只是一锤子分析词典法加Excel足够如果能形成闭环伪标签训练才是把“情感倾向性分析”从工具做成产品的路径。希望帮到你。本文还有配套的精品资源点击获取
返回列表