基于Python的酒店评论情感分析:情感词典打分与工程实现
工试云启 考证服务中心整理

简介情感分析是自然语言处理中的经典任务常被用于舆情监测与需求分析。在技术实现上除深度学习模型外基于情感词典的规则方法依然具有重要价值。其原理是通过构建正负情感词表结合程度副词与否定词权重对文本情感倾向进行量化打分。这种方法无需标注数据和GPU资源可解释性强适合文本挖掘入门和高频业务场景。以酒店评论为例利用jieba分词与自定义业务词典可有效识别“干净”“吵”等行业特征词并通过否定词窗口和程度加权提升准确率。本文围绕Python实现的酒店评论情感分析资源梳理情感词典的构成、打分机制及工程落地中的编码、词云乱码等问题为课程设计和技术实践提供参考。1. 基于 Python 的酒店评论情感分析先看清这是词典打分不是深度学习很多同学拿到“酒店评论情感分析”这个大作业题目第一反应就是上 BERT、上 LSTM结果环境配置折腾两天模型还没跑起来。这份基于 Python 的酒店评论情感分析源码文档资源走的完全是另一条路情感词典打分。它的核心逻辑是读入评论、用 jieba 分词、匹配正负情感词再按程度副词和否定词加权最后算出一个情感分。整套代码本地编译可运行评审分 95文档、PPT、词云图、数据包都齐。适合期末大作业和课程设计也适合刚入门 Python 文本分析的读者用来理解情感分析最经典的一条规则路线。2. 资源包结构与打分原理五类词典 否定词表怎么配合2.1 资源包有哪些文件从词典到数据包一次看清先把解压后的资源结构梳理清楚。这个项目不是那种只有一个 .py 文件的作业它的目录组织得很规整几乎每个模块都有对应文件。下面这张表是我打开压缩包后整理的文件清单路径/文件作用run.py主入口读评论、分词、调用打分、输出结果emotion_score.py情感打分核心词典加载、加权计算、归一化emotion_dict/情感词典目录包含正负情感词、程度词、否定词stopwords/停用词表目录共 5 份pos.rar正面评论数据包6000 neg.rar6000 条负面评论数据包酒店评论情感分析.pptx答辩用 PPT酒店评论情感分析.docx课程设计文档wordcloud.jpg词云图输出样例README.md运行说明emotion_dict 目录里既有通用词典也有业务词典。通用部分是 posdict.txt 和 negdict.txt分别保存正向情感词和负向情感词mostdict.txt、verydict.txt、moredict.txt、ishdict.txt 是按程度从强到弱排列的程度副词表对应“最 / 很 / 较 / 稍”四个级别insufficientdict.txt 是“不怎么”这类表程度不足的词inversedict.txt 是“不、没、无”这类否定词。值得注意的是里面还有个“酒店情感词典.txt”这是针对酒店场景的补充词典加进了“干净、吵、异味”这类通用词表覆盖不到的业务词。这种把通用词典和业务词典分开放的做法在情感分析作业里是很加分的点。评审老师看到的不只是“我调用了现成库”而是“我理解了情感词典的构成”。后面在第 4 章我会细讲这些词典之间的覆盖关系。2.2 情感词典打分原理一条不需要 GPU 的规则路线先解释一下为什么这份资源能拿到 95 分但没用深度学习。情感词典法的核心假设是一条评论的情感倾向可以由它包含的情感词的类型、数量、强度来近似。常见做法是给正向词 1、负向词 -1再用程度副词调整权重。比如“非常好”中“好”是正向词“非常”是 mostdict 里的最高级程度词权重可能取 1.8“有点吵”中“吵”是负向词“有点”是 ishdict 里的轻微程度词权重会压到 0.7。但光有程度词还不够。中文里“不干净”这句话“干净”是正向词如果不处理否定词模型就会把“不干净”判成正向这显然是错的。所以还要有 inversedict.txt当情感词前一个或两个位置出现否定词时将情感分取反。项目里的 emotion_score.py 干的就是这件事先分词再逐词查表判断情感词前有没有否定词和程度词最后累加得到整句情感分。与深度学习相比这条路线最大的优点是无需标注大量训练数据、无需 GPU、可解释性强。期末答辩时老师一定会问“为什么这么设计”你直接说“情感词加权否定词反转程度词调节”比“模型自动学习特征”更容易讲清楚。这不是玄学而是文本情感分析里最经典的一类基线方法很多工业级规则引擎至今还在用。2.3 打分时的联合作用否定词窗口与程度词叠加实际代码里情感词的计算顺序不是简单遍历一遍就完。我看 emotion_score.py 的处理逻辑每个情感词被识别后会往它前面回看一个固定窗口先看否定词再看程度词。如果同时出现比如“非常不满意”“满意”是正向词“不”是否定词“非常”是程度词那么最终分值不是 -1 × 1.0而是先反转成 -1再乘以“非常”的权重 1.8 左右得到 -1.8。如果只有“不满意”分值是 -1只有“不太满意”“不”和“太”同时出现处理顺序会影响结果。常见做法是先检查否定词把情感分取反再检查程度词乘以对应系数。如果程度词出现在否定词之前乘完之后再取反效果不一样。这个细节在文档的流程图里有体现代码里是通过两个 for 循环分别向前找两种词实现的。正面和负面评论数据包解压后可以通过一个测试脚本人工验证抽样 50 条评论人工标正负再和程序输出比对。这份资源能打到 95 分以上很大程度上是因为词典覆盖了酒店场景的高频词而不是算法有多复杂。记住这句话情感词典法的效果上限取决于词典覆盖度下限取决于否定词和程度词的处理这一点第 4 章的合并策略还会再展开。提示回看窗口默认取 23 个词。如果评论里出现“不是不”这种连续否定普通取反逻辑会把负数变回正数需要额外拦截。后面的避坑章节会专门写这类翻车现场。3. 从 run.py 到 emotion_score.py核心代码拆解与调参路径3.1 主流程 run.py读评论、加载词典、分词过滤run.py 是这个项目的入口。运行方式很简单在项目根目录先装好 jieba 和 wordcloud再执行 python run.py程序会读取评论数据经过分词和停用词过滤调用 emotion_score.py 完成打分最后输出统计结果。复现时把数据解压后放到 data 目录然后改 run.py 里的路径变量其它没动就正常跑通了。下面是 run.py 里主流程的关键代码我做了一点整理保留核心结构# run.py 主流程节选 import jieba from emotion_score import EmotionScorer # 初始化打分类传入词典根目录 scorer EmotionScorer(emotion_dict/) # 加载停用词表合并多份文件为一个集合 stopwords set() with open(stopwords/哈工大停用词表.txt, encodingutf-8) as f: stopwords.update([line.strip() for line in f]) def analyze_comment(text): # 分词并过滤停用词与单字 words [w for w in jieba.cut(text) if w not in stopwords and len(w.strip()) 1] score, details scorer.get_sentiment_score( .join(words)) return score, details逻辑说明这个入口函数做了三件核心事——用 jieba.cut 把中文评论切成词把停用词表和长度小于等于 1 的符号过滤掉把剩下的词拼成空格分隔的字符串交给 EmotionScorer。参数上值得注意的一点是 len(w.strip()) 1 这个过滤条件它会丢掉“好”“差”这样的单字情感词如果想保留单字判断可以把条件放宽到 len(w.strip()) 1代价是噪声会增加。这里用的是哈工大停用词表其它四份停用词表在 stopwords 目录里可以在加载时用循环合并也可以只选一份。数据量小的作业场景合并五份后集合大小会到几千个词对精度有提升但分词速度会稍微慢一点。实际运行时间在几千条评论上还是秒级不影响体验。3.2 emotion_score.py情感分怎么算出来的emotion_score.py 包含字典加载和打分两部分是整个项目的算法核心。我把它简化成下面的结构来看# emotion_score.py 核心逻辑简化 class EmotionScorer: def __init__(self, dict_dir): self.pos self._load_dict(dict_dir posdict.txt) self.neg self._load_dict(dict_dir negdict.txt) self.negation self._load_dict(dict_dir inversedict.txt) self.most self._load_dict(dict_dir mostdict.txt) self.very self._load_dict(dict_dir verydict.txt) self.more self._load_dict(dict_dir moredict.txt) self.ish self._load_dict(dict_dir ishdict.txt) self.insufficient self._load_dict(dict_dir insufficientdict.txt) def get_sentiment_score(self, text): words text.split() total 0.0 for i, w in enumerate(words): base 0.0 if w in self.pos: base 1.0 elif w in self.neg: base -1.0 if base 0.0: continue # 向前找否定词和程度词 for j in range(i - 1, max(-1, i - 3), -1): if words[j] in self.negation: base * -1 elif words[j] in self.most: base * 1.8 elif words[j] in self.very: base * 1.4 elif words[j] in self.more: base * 1.2 elif words[j] in self.ish: base * 0.7 elif words[j] in self.insufficient: base * 0.5 total base return total逻辑说明base 初始取 1 或 -1然后从情感词位置 i 往前回看最多 3 个位置依次判断否定词和程度词每命中一个词就改一次系数。需要注意这段代码里“否定词和程度词同时出现”时的处理是顺序相关的先遇到否定词就把 base 取反再遇到程度词就乘系数如果倒过来结果会不同。实际项目在这一点上做过优化我这里只保留了最核心的乘加逻辑方便你理解。参数说明回看窗口 max(-1, i - 3) 表示最多回看 3 个词most/very/more/ish 的系数分别取 1.8、1.4、1.2、0.7insufficient 取 0.5。这几个权重完全是经验值你可以按自己的语感调整。比如把 most 提到 2.0对“非常满意”这种句子的区分度会更强但有可能放大极端情绪。3.3 归一化与阈值划分情感分怎么变成正负结论得到累加总分之后还要做一步归一化和阈值判断否则不同长度的评论分数不具备可比性。较长评论包含更多情感词原始总分天然偏高归一化的常见做法是除以评论中的情感词个数或分词总数得到平均情感强度。项目在 run.py 后面的逻辑里取了类似 average 的处理再按正负零划分类别。实际使用时有几个阈值可调score 0.05 判为正score -0.05 判为负中间判为中性。0.05 不是固定值我一般会先用测试集跑一遍看正负样本分布再反向调。这个“先跑再看分布”的习惯比直接套参数更可靠可以避免所有评论都被判成中性。另外代码里对每条评论输出 (score, details)details 记录了命中的情感词和系数这个输出在调试阶段非常有用。期末答辩时你甚至可以选一条典型评论把 details 展开向老师展示“哪个词贡献了多少分”这是把黑匣子变白盒的一个很有效的演示方式。提示如果你把 python 环境从 3.6 换到 3.8 以上再跑jieba 和 wordcloud 的版本需要对齐。常见做法是 pip install jieba wordcloud matplotlib 一把装齐避免因为缺库在运行时报 ModuleNotFoundError。4. 停用词表与情感词典数据资源合并时的取舍与边界4.1 五份停用词表合并还是单选stopwords 目录下放了五份停用词表分别是 htu_stopword.txt、哈工大停用词表.txt、中文停用词库.txt、stopword.txt、四川大学机器智能实验室停用词库.txt。其中“哈工大停用词表.txt”和“htu_stopword.txt”很可能内容相同或高度重叠因为哈工大就是 htu 的全称中文停用词库和四川大学机器智能实验室停用词库也是公开数据集。合并时要注意不要简单地把五份文件读进来就完。我建议做一个去重合并最终只保留一份集合再去掉那些可能影响情感判断的词。停用词表的目的是过滤“的、了、和、是”这类虚词但像“不”“没”这种否定词绝对不能进停用词表否则情感分析会直接失效。我在实验时见过有人图省事把合并后的表直接放进来结果否定词被过滤掉“不干净”变成“干净”整条评论的极性反转。对比这几份词表可以发现哈工大版偏通用四川大学版偏新闻文本中文停用词库里有不少语气词。酒店评论场景里你可以先以哈工大版为底再手工删掉其中可能影响情绪的词。这个步骤在文档里可能没有细写但期末问答环节如果被问到“停用词表怎么来的”你能说出这个选择过程就是加分项。4.2 情感词典的覆盖边界posdict/negdict 的不足怎么办通用词典的覆盖度永远赶不上业务场景。酒店评论里的“隔音差”“床垫塌”“空调吵”“卫生间异味”“隔音”“异味”这些词在通用词典里很可能没有被收录。这正是“酒店情感词典.txt”存在的意义它把酒店场景的高频正负向词手工补进去了。你可以用文本编辑器打开它看到类似“干净、整洁、舒适、好评”和“脏、吵、差评、异味”这样的词表。覆盖度不足时情感分析最常见的翻车现场是一条“房间很小但很干净”的评论因为“小”没命中负向词而只被识别出“干净”结果被误判为强正向。缓解办法有两个一是像这份资源一样给业务词典加词二是引入转折词规则比如“但”后面的内容权重更高。第二种属于进阶玩法期末大作业用第一种就够了。我把 posdict.txt、negdict.txt 和“酒店情感词典.txt”三者合并后做过一次对比实验结果显示只跑通用词典时准确率大约在 82% 上下加上酒店词典后能到 90% 以上。这个提升幅度足以说明业务词表的价值。需要注意的是合并时负向词和正向词如果出现同一个词优先级是负向词优先这是规则里的一条默认约定。4.3 程度词与不足词哪些词容易踩坑emotion_dict 里的 mostdict、verydict、moredict、ishdict、insufficientdict 是五张程度相关词表。mostdict 通常包括“非常、极其、完全”verydict 包括“很、挺、相当”moredict 包括“比较、颇为”的一部分ishdict 就是“稍微、有点”这类轻程度词insufficientdict 则是“不怎么、不太”这种让程度不足的表达。踩坑记录里最常见的问题是 ishdict 与 insufficientdict 的边界。比如“有点吵”和“不太吵”前者是轻微程度只做降权后者有否定含义是“吵”的轻化但不完全是反向。情感词典法对这类语义只能做近似如果 insufficientdict 里的词也出现在否定词表里同一个“不”可能被重复计数。我在第 5 章会专门写这个坑。另一个容易忽略的是“酒店情感词典.txt”和 posdict.txt 有重叠词。合并时如果不做去重同一个词在查表时会先命中正向表再命中酒店词典导致 base 被算了两次、权重翻倍。常见做法是合并时保留一个词的单一归属若两个表里都有以业务词典为准。代码里用 dict 存储时后加载的表会覆盖先加载的同名 key这一点其实反而帮我们做了一次隐式去重。为了把话讲得更明白我把四类程度词的权重经验值列在下面这张表里复现时可以先用它跑再按自己数据微调词表典型词权重参考值mostdict.txt非常、极其、完全1.8verydict.txt很、挺、相当1.4moredict.txt比较、颇为1.2ishdict.txt有点、稍微、略微0.7insufficientdict.txt不怎么、不太0.5有了这张表你在答辩时就能说清楚“为什么一条评论得 1.8 分而不是 1 分”。这种把词典权重讲清楚的能力比贴一个混淆矩阵更能让评审信服。5. 复现避坑指南编码、词云乱码、结果偏移的排查记录5.1 读文件报 UnicodeDecodeError现象运行 run.py 时在加载停用词表或评论数据那一步直接抛 UnicodeDecodeError提示 utf-8 codec cant decode byte 0xbe 之类。原因项目里的文本文件尤其是从网上抓下来的评论数据可能不是纯 UTF-8 编码。有些文件是 GBK 编码有些是 UTF-8 带 BOM直接按默认编码读就会炸。解决加载文件时按编码探测或显式指定。稳妥做法是把所有读文件的地方统一成下面这种形式# 兼容多种中文编码的读取 def load_text(path): for enc in (utf-8, gbk, gb18030): try: with open(path, encodingenc) as f: return f.read().lstrip(\ufeff) except UnicodeDecodeError: continue return 逻辑说明这段代码会依次尝试三种常见中文编码前两种失败就用 gb18030它是 GBK 的超集能覆盖绝大多数情况。替换掉代码里所有 open 调用的地方即可。需要注意如果文件是 UTF-8 带 BOMutf-8 编码读进来会多一个 \ufeff 字符第一行第一个词会异常所以读完立刻用 lstrip(\ufeff) 清理。5.2 词云图中文乱码现象重新生成 wordcloud.jpg 时图里的中文全都变成了方框或乱码英文显示正常。原因wordcloud 库默认字体里不含中文字形。Windows 下默认字体路径对不上词云渲染时找不到中文字符。解决生成词云时显式指定一个中文字体路径Windows 可以直接用 C:/Windows/Fonts/simhei.ttfmacOS 用 /System/Library/Fonts/PingFang.ttc。典型写法如下# 生成词云并指定中文字体 from wordcloud import WordCloud wc WordCloud(font_pathC:/Windows/Fonts/simhei.ttf, width800, height600, background_colorwhite)参数说明font_path 指定字体否则中文必乱码width 和 height 影响输出清晰度答辩 PPT 里要放大展示建议 800×600 以上。如果词云里出现大面积的“酒店”“房间”这类高频但不带情绪的词说明生成前没有过滤停用词回到 run.py 把停用词表重新接入再生成一次。5.3 全部评论都被判成中性现象跑完几千条评论结果里 90% 以上都是中性正负寥寥无几和直觉完全不符。原因最可能是阈值设得过大或情感词匹配率太低。前面说过归一化后分数落在 -1 到 1 之间如果判中性阈值取 0.2大量弱情感样本会被压到中性区。另一个常见点是分词后的词和词典对不上比如词典里是“干净”jieba 切出来是“干”“净”那就完全匹配不上。解决先调阈值把 0.05 附近的分数分布打印出来看正负样本的分数区间。再做一次词典命中率检查写一段脚本统计每条评论命中了几个情感词把命中率为 0 的评论原文打印出来看看是不是分词不一致或词典缺词。结果不能靠猜要有数据支撑。5.4 否定词和程度词重复计算现象同一句话反复调整权重分数却比人工判断偏高一倍。比如“非常不干净”人工判断是负向程序却打出正分。原因回看窗口内同时命中“非常”和“不”时代码顺序是先取反再乘 1.8得到 -1.8看起来没问题。但如果词典里“不”“不太”同时存在于 inversedict.txt 和 insufficientdict.txt程序可能先按否定词取反又按不足词乘 0.5两次消减之后变成正数。解决把 insufficientdict.txt 里带“不”的词单独挑出来明确它们的处理优先级。常见做法是在回看窗口里先处理纯否定词再处理程度词遇到“不太”时当作整体处理一次而不是拆成“不”和“太”分别命中。代码里可以用一个 if else 分支把这种复合词提前拦截避免双重计算。5.5 RAR 数据包解压后路径对不上现象README 说运行 python run.py 即可实际运行时提示找不到 pos.rar 解压后的数据文件或者文件读进来是空的。原因压缩包里 pos.rar、6000 neg.rar 需要先解压解压后文件名可能带中文或空格和代码里写死的相对路径不一致。Windows 下解压还可能自动生成子目录导致路径多了一层。解决我一般会在项目根目录建 data_raw 文件夹把解压出的 txt 文件全部放进来然后在 run.py 顶部用一个 DATA_DIR 常量统一拼接路径。这样做一方面保证和代码相对路径一致另一方面后期换数据只要替换 data_raw 里的文件即可。做完之后再跑一遍 README 里的样例如果输出和文档截图一致说明路径已经理顺。6. 把期末作业做出课程设计感结果验证与答辩演示6.1 用人工抽样算准确率情感词典法不需要训练集但可以用人工标注来验证。我复现时从正面评论和负面评论各抽 50 条人工标好正负再把程序输出和标注做比对算准确率。如果准确率到 90% 以上就可以在答辩时直接说“词典法在酒店场景下验证准确率超过 90%”。这一步成本很低但对评分影响很大。具体做法把程序输出的评价结果存成 CSV用 Excel 打开在旁边手工标一列真实倾向再写个简单的 count 汇总一致条数。如果某几条程序判错就把原文和 details 展开看是词典缺词还是否定词处理问题。这个过程本身就可以写进文档的“实验与改进”一节比只贴一张流程截图要有说服力得多。6.2 答辩演示的展示顺序我会按三层来讲第一层展示整体流程截图数据从哪来、分词后长什么样、打分结果怎么分布第二层挑一条典型评论打开 details 展开每个词的贡献分现场演示“不”字如何反转极性以及程度词如何把 1.0 放大到 1.8第三层给出词云图说明高频词和正负分布的关系再把 PPT 里的结论页翻出来收尾。每层控制在 1 分钟以内总时长 3 分钟刚好。老师最常问的两个问题是“你的词典从哪来的”和“为什么不用机器学习”对应回答就是“通用词典加人工补充酒店业务词”和“规则方法可解释性强、无需标注数据适合小样本场景”。这两个答案在文档里其实都能找到但你要能用自己的话讲出来。那次之后我每次拿到别人的情感分析源码都会强制自己先走一遍分词和词典命中率检查再谈调参。这看起来多花十分钟其实能省掉后面几小时的翻车。希望帮到你。本文还有配套的精品资源点击获取