恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
基于情感词典的酒店评论情感分析Python实现:规则打分与词云
首页
资讯中心
/
基于情感词典的酒店评论情感分析Python实现:规则打分与词云
基于情感词典的酒店评论情感分析Python实现:规则打分与词云
发布时间:2026/9/16 5:42:14
简介面向Python初学者和期末大作业的需求者这份酒店评论情感分析项目提供了可直接运行的源码与配套文档能够解决课程设计中评论文本情感分类与可视化展示的常见问题。项目完整覆盖数据预处理、情感词典构建、情感打分与可视化等环节源码经本地编译可正常运行评审分达95分以上难度适中适合作为课程设计或结课项目的系统参考。压缩包共23个文件以情感词典与停用词表等txt资源、核心处理py脚本、rar样本数据、docx设计报告和pptx演示文稿为主要类型总大小4.36MB目录结构清晰便于按模块查找和复用。已有157人学习下载资源内提供酒店评论情感词典、哈工大停用词表等常用词库并附带项目文档和答辩PPT能够帮助快速理解基于情感词典的完整分析流程也可以将词库与脚本迁移到其他类似文本分类场景中。1. 不看模型、只靠词典的酒店评论情感分析为什么能拿到95的评审分很多期末大作业一上来就套BERT或者LSTM结果不是训练数据不够就是答辩时被一句“attention权重你怎么解释”卡住。这套基于Python的酒店评论情感分析走的是完全相反的路子不训练模型靠一份情感词典加程度副词和否定词规则给每条酒店评论打出一个正负分值。压缩包里真正值钱的是emotion_dict目录下的九个词表文件和一份酒店情感词典.txt它把“隔音差、前台冷淡、床垫软塌”这类通用情感词典照顾不到的酒店场景词变成了可计算的分值。整个项目的主线就是emotion_score.py打分、run.py批量跑分最后出一张wordcloud词云图。适合期末大作业、NLP入门实践也适合想做词典基线与深度学习基线对比的从业者。2. 情感词典拆分emotion_dict 文件矩阵与酒店情感词典.txt 的领域扩展拿到压缩包先别急着跑代码先把emotion_dict目录下的文件按角色捋一遍。搞清楚每个文件在打分链路里的位置后面改起来才知道往哪里动刀否则加个词都要满目录找半天。2.1 九个词表文件的三层结构emotion_dict里九个txt文件可以分成三层基础极性层、程度调节层、否定翻转层。基础极性层是posdict.txt和negdict.txt提供最原始的正面、负面分值程度调节层是五个以dict结尾的文件负责把“有点吵”和“非常吵”拉开差距否定翻转层是inversedict.txt负责处理“不干净”“没服务”这类带否定的评论。它们之间的关系可以用一个简单的结构表来说明。层次文件在打分中的作用基础极性posdict.txt命中时给正面分值基础极性negdict.txt命中时给负面分值基础极性酒店情感词典.txt补充酒店场景专属情感词程度调节mostdict.txt / verydict.txt高倍率程度词程度调节moredict.txt / ishdict.txt中低倍率程度词程度调节insufficientdict.txt弱化型程度词否定翻转inversedict.txt翻转后续情感词的极性这里最容易被忽略的是酒店情感词典.txt的特殊性。它不在通用情感词典的体系里是从真实酒店评论里筛出来的领域词。我拆完这套资源后发现这份词典的加载逻辑和posdict、negdict还不完全一样读文件时先对每一行做split有极性标签的按标签归入正负集合只有一个字段的词条建议默认归入negdict。原因是酒店场景里“反味”“噪音大”“乱收费”这类领域专属词绝大多数是抱怨词默认归负面能明显提高召回率。判断方法很简单随便抽五十个词条人工扫一眼看负面词是否占多数再决定默认倾向。2.2 程度副词五档权重有点、很、非常、极其的系数差异五个程度词文件在打分时被映射成不同的权重系数这是情感分析结果不平淡的关键。常见做法是取经验值让“非常满意”的分值稳定高于“满意”同时又不会因为一个句子里出现三个程度词就冲到5分以上。词表文件示例词权重系数mostdict.txt极其、特别、非常1.8verydict.txt很、挺、太1.5moredict.txt更、更加、愈发1.2ishdict.txt稍微、有点、略微0.8insufficientdict.txt不大、不怎么0.5这些系数不是教材里规定的是调出来的经验值选取标准只有一个让相邻两档之间的分差在0.2到0.3左右。这个区间既能体现程度差异又不会让情感词的命中次数对总分的影响被稀释。insufficientdict要注意和否定词区分“不太干净”里的“不”归inversedict“太”归insufficientdict分词后按顺序分别处理效果才正常。2.3 否定词与程度词的组合规则连续窗口的极性修正inversedict.txt存的是“不、没、无、莫、休”这类否定词。打分时遇到否定词不是简单地把当前分值乘-1而是在一个滑动窗口内持续影响后续情感词的极性直到遇到下一个程度词或情感词才结束。这个规则对应一个语言事实“不是很满意”和“不很满意”的语义重心不同前者靠“很”强化了“满意”的正面程度后再被“不”翻转后者是“不”直接压住“很”和“满意”的整体。处理逻辑我一般写成这样维护一个neg_count计数器遇到否定词就加1遇到情感词时检查neg_count的奇偶性。奇数翻转极性偶数恢复原义处理完立即把neg_count清零避免影响句尾的下一个情感词。这个“连续否定恢复原义”的规则在中文里很重要“没有不干净”是两个否定词连续出现语义回到正面如果每遇到一个否定词就简单乘-1这类句子会被判成负面。3. 停用词合并与评论预处理哈工大、川大停用词库为什么都要保留项目里stopwords目录下放了好几份停用词文件包括哈工大停用词表、htu_stopword.txt、四川大学机器智能实验室停用词库、中文停用词库和stopword.txt。htu_stopword.txt可以理解为哈工大词表的一份英文名拷贝内容基本重合。真正有区分度的是哈工大词表和川大词库哈工大偏书面语川大机器智能实验室的词库偏口语和网络词酒店评论恰好是口语重灾区。3.1 五份文件的去重合并脚本不同来源的停用词表之间的重合度大约在60%到70%但没有任何一份能单独覆盖完整。合并的原则是取并集而不是取交集。取交集会让“啊”“呢”“嘛”这类语气词漏掉取并集则会让真正的情感词被误杀的概率降到可接受范围。做法是逐行读入五份文件用set去重后落成一份merged_stopwords.txt供run.py一次性加载。# merge_stopwords.py 合并多份停用词表为一份 import glob stopwords set() for path in glob.glob(stopwords/*.txt): with open(path, encodingutf-8) as f: # strip 同时去掉换行符和行尾空格 for line in f: word line.strip() if word and not word.startswith(#): stopwords.add(word) with open(stopwords/merged_stopwords.txt, w, encodingutf-8) as f: f.write(\n.join(sorted(stopwords))) print(fmerged {len(stopwords)} stopwords)这段代码的要点在两个细节一是过滤以#开头的行因为有些停用词表会把注释直接写在文件里不过滤会把注释行当成停用词加载二是排序后写入方便后续diff时快速定位哪些词来自哪份原始词表。运行后如果合并结果只有几千词说明读入路径有问题正常五份中文停用词表合并去重后应该在一万词以上。3.2 酒店场景的高频干扰词停用词表覆盖不到的“房间”和“酒店”停用词表能解决语气词和连词但解决不了像“酒店”“房间”“入住”这类高频名词。它们在一千条评论里可能出现几千次没有任何情感倾向却会直接影响两个地方一是打分时情感词占分词结果的比例二是词云图的视觉效果。这类词不适合写进通用停用词表否则换一个业务场景就会误伤正确做法是单独维护一份custom_stopwords.txt内容像“酒店”“房间”“前台”“入住”“办理”这种业务高频词在加载merged_stopwords.txt之后按同样方式合并进去。判断哪些词需要进custom_stopwords的方法很朴素先跑一次不带自定义停用词的run.py把生成的分词结果按词频排序然后扫一遍词频前200的词凡是与情感无关的业务名词全部丢进custom_stopwords.txt。很多时候课程设计的wordcloud.jpg看着全是“酒店”“房间”两个大词就是这个步骤偷懒了。4. emotion_score.py 到 run.py程度加权、极性反转与批量跑分的完整链路情绪词典准备好之后真正的核心在两个Python文件里。emotion_score.py负责定义打分逻辑run.py负责把打分逻辑套到整批评论上并输出统计结果和词云图。先看emotion_score.py的加载与打分实现。4.1 词典加载与分词初始化# emotion_score.py 核心词典加载与打分函数 import jieba degree_map {} # 程度词 - 权重 neg_set set() # 否定词集合 pos_set set() # 正面词集合 neg_words set() # 负面词集合 stopwords set() # 停用词集合已合并 dict_path emotion_dict/ degree_files { mostdict.txt: 1.8, verydict.txt: 1.5, moredict.txt: 1.2, ishdict.txt: 0.8, insufficientdict.txt: 0.5, } def load_dicts(): # 加载程度词并附加权重 for fname, weight in degree_files.items(): for line in open(dict_path fname, encodingutf-8): degree_map[line.strip()] weight # 加载否定词、正负面情感词 neg_set set(open(dict_path inversedict.txt, encodingutf-8).read().split()) pos_set set(open(dict_path posdict.txt, encodingutf-8).read().split()) neg_words set(open(dict_path negdict.txt, encodingutf-8).read().split()) # 领域词典统一读入按 2.1 中的策略归入正负集合 with open(dict_path 酒店情感词典.txt, encodingutf-8) as f: for line in f: parts line.strip().split() if len(parts) 1: neg_words.add(parts[0]) # 默认进负面需人工抽检 elif parts[1] pos: pos_set.add(parts[0]) else: neg_words.add(parts[0])这段代码里最容易出错的地方是编码。很多词典文件原始编码是GBK直接open会抛UnicodeDecodeError。稳妥的做法是先用errorsignore读一遍看有没有乱码或者统一先转成UTF-8再加载。我一般会在load_dicts开头加一个try-except捕获编码错误后回退到gbk编码重新读避免在答辩现场因为环境编码差异直接崩溃。4.2 单句分值累计规则程度加权、否定翻转、窗口重置def score_sentence(sentence): # 空评论直接返回0分避免后面jieba处理报错 if not sentence or len(sentence.strip()) 2: return 0.0, 0 words jieba.lcut(sentence.strip()) # 过滤停用词和无意义符号减少窗口误判 words [w for w in words if w not in stopwords and w.strip()] total 0.0 degree 1.0 # 当前程度权重初始为1 neg_count 0 # 连续否定词计数 hit 0 # 命中的情感词个数 for w in words: if w in degree_map: degree degree_map[w] # 程度词只影响下一个情感词 elif w in neg_set: neg_count 1 # 记录否定暂不翻转 elif w in pos_set or w in neg_words: base 1.0 if w in pos_set else -1.0 if neg_count % 2 1: # 奇数个否定则翻转极性 base -base total degree * base # 程度权重乘基础极性 hit 1 degree 1.0 # 处理完一个情感词重置 neg_count 0 return total, hit这个累加逻辑的关键在于“程度词只影响下一个情感词”。中文评论里程度词和情感词几乎总是相邻出现比如“非常满意”“极其糟糕”把degree的作用域限制在一个词窗口内可以避免“挺干净的但位置有点偏”这种评论里“挺”错误放大“偏”的情况。neg_count的奇偶判断则保证了“没有不干净”这类双重否定句能回到正面。这个方案的代价是处理不了否定词和情感词之间夹着从句的复杂句式但课程设计场景下它能覆盖大部分酒店评论可解释性也强答辩时老师要你讲打分逻辑照着这个函数逐行讲就行。4.3 run.py 批量跑分、分类阈值与词云输出# run.py 批量跑分主流程 import pandas as pd from wordcloud import WordCloud from emotion_score import score_sentence # 读取评论数据可以是爬虫抓取的csv或Excel导出 df pd.read_csv(hotel_comments.csv, encodingutf-8) # 逐条打分返回(总分, 情感词命中数) df[score], df[hit] zip(*df[comment].apply(score_sentence)) # 分类阈值±0.1 的容差带过滤“只有程度词没有情感词”的噪声评论 def classify(score): if score 0.1: return 正面 if score -0.1: return 负面 return 中性 df[sentiment] df[score].apply(classify) print(df[sentiment].value_counts(normalizeTrue).round(3)) # 导出带标签的结果utf-8-sig 防止Excel打开中文乱码 df.to_csv(labeled_result.csv, indexFalse, encodingutf-8-sig) # 词云先分词并用空格拼接再交给WordCloud text .join(df[comment].dropna().tolist()) words .join(jieba.cut(text)) stopwords_extra set(open(stopwords/custom_stopwords.txt, encodingutf-8).read().split()) wc WordCloud( font_pathC:/Windows/Fonts/simhei.ttf, # Linux环境改成系统黑体路径 background_colorwhite, width800, height600, stopwordsstopwords_extra, max_words200 ).generate(words) wc.to_file(wordcloud.jpg)run.py里有两个容易被忽略的细节。第一是df[score], df[hit] zip(...)这种写法它一次性把apply返回的元组解包成两列避免对评论跑两遍打分数据量上万条时能省一半时间。第二是WordCloud必须配合jieba分词后的空格文本如果直接把中文原始评论传给generate生成的就是一堆连续乱码因为wordcloud底层按空格和标点切分单词。另外font_path要改成系统里真实存在的字体Windows一般用simhei.ttfLinux服务器上常见的是/usr/share/fonts/truetype/wqy/wqy-microhei.ttc不设置中文字体词云会全部变成方块。5. 验证与迭代词云反查停用词、人工抽样算一致率、领域词典补词评分模型做完不是终点期末大作业拿95分以上的关键在验证环节。老师看的不只是输出结果而是你能不能证明结果可信。5.1 人工抽样一致率五十条评论算出可信度从跑完分的结果里随机抽五十条评论人工标注正面、负面、中性再和程序判定的sentiment列做对比一致率在0.75以上基本就能支撑结论。sample时用固定的random_state保证每次抽查的结果可复现否则演示时重跑一次数据变了答辩会很难看。# 抽样50条标记结束后计算一致率 sample df.sample(50, random_state42) sample.to_csv(eval_sample.csv, indexFalse, encodingutf-8-sig) # 在Excel里加一列 manual_label人工标 1/0/-15.2 用词云反查停用词覆盖度看wordcloud.jpg不能只看好不好看。如果图上出现大量“酒店”“房间”“入住”这类与情感无关的词语说明custom_stopwords.txt里的业务高频词还不够把这些词追加进去重新生成。如果图上出现“非常”“很”“太”这类程度词说明停用词过滤时把程度词词典也当成普通词过滤掉了检查一下degree_map里的词是否被误加进了stopwords集合。这一步能直接反映预处理逻辑是否正确。5.3 领域词典的迭代补词路线“隔音”“漏水”“退押金”这些词在一开始的情感词典里大概率是不全的。迭代方法很简单跑完第一次run.py后把每条评论里未命中任何情感词的句子打印出来人工扫一遍把高频且明显带有情感色彩的词追加进酒店情感词典.txt再重新跑run.py对比一致率是否提升。这个“跑分-筛漏-补词-重跑”的循环正是基于词典的情感分析最核心的维护方式也回答了老师最爱问的“你这个词典能不能扩展到其他领域”的问题。换到餐厅评论就把酒店场景词替换成菜品、上菜速度、服务态度相关的词条权重和否定规则完全不用动。本文还有配套的精品资源点击获取