简介:这份资源面向具备一定Python基础、希望入门文本挖掘与情感分析的学习者,围绕豆瓣《肖申克救赎》评论文本,演示如何调用SnowNLP库完成情感倾向判断,并配合分词与词云展示,帮助读者理解从原始评论到可视化结论的完整分析思路,属于机器学习与Python数据挖掘方向的基础实践素材。压缩包共10个文件,以8个py脚本为主体,覆盖评论抓取、中文分词、SnowNLP多版本情感计算等环节,另含1个csv评论数据与1个txt文本,整体约37KB,轻量易读,便于按脚本顺序逐步运行与调试。目前已有16951人学习下载,说明其在情感分析入门场景中具有一定参考价值。读者可据此掌握SnowNLP的调用方式、评论数据的组织方法以及词云呈现的基本流程,并在此基础上迁移到其他评论文本的分析任务中。
1. 从一堆“看不出情绪”的短评说起
做影评数据的人大多遇到过这种场面:爬下来几千条豆瓣短评,打开表格一看,全是“还行”“有点失望”“后劲很大”这类短句,人工一条条标情感极性,标到第三百条就开始怀疑人生。更麻烦的是,短评里没有明显的“好评”“差评”字样,情绪藏在语气词和程度副词里,规则匹配基本失效。这套基于 SnowNLP 的豆瓣评论情感分析与词云方案,解决的就是“短文本情感极性批量判定 + 高频词可视化”这两个交付目标,适合做机器学习课程设计、NLP 入门毕设,或者需要快速给评论数据打标签的从业者。整套流程用 Python 串起来,从数据清洗、情感打分、极性分布统计到词云出图,一条链路走完,中间不依赖重型深度学习框架,普通笔记本就能跑。
2. 需求拆解与技术选型:为什么是 SnowNLP 而不是自己训模型
2.1 先把需求翻译成四个可验收的功能点
拿到“豆瓣评论情感分析”这个题目,第一反应不该是打开编辑器写代码,而是把模糊需求拆成能验收的功能点。我一般会拆成四块:第一,能读入原始评论数据并完成清洗,去掉 HTML 标签、表情符号、重复行和纯标点行;第二,对每条评论输出一个 0 到 1 之间的情感分值,并映射成正面、中性、负面三档;第三,统计整体极性分布,给出正面率和负面率这类能写进报告的指标;第四,基于分词结果生成词云,让高频词一眼可见。这四块对应四个可演示的产出:清洗后的数据表、带情感标签的结果表、极性分布图、词云图。答辩时老师问“你做了什么”,你指着这四样东西讲,比空谈“我做了情感分析”扎实得多。
功能点定下来之后,还要明确数据规模假设。课程设计场景下,评论量通常在几百到几千条,这个量级决定了技术选型不必上 BERT 这类大模型——训练成本高、调参周期长,而且短评语料上预训练模型未必比词典方法稳。SnowNLP 自带一个基于电商评论训练的情感模型,对中文短文本的极性判断在入门场景下够用,开箱即调,这是它最大的价值。
2.2 SnowNLP 的能力边界与选型理由
SnowNLP 是一个轻量中文处理库,核心能力包括分词、情感打分、关键词提取、拼音转换等。它的情感模块返回 0 到 1 的浮点数,越接近 1 越正面,越接近 0 越负面,0.5 附近视为中性。选它的理由有三条:一是零训练成本,pip install snownlp之后直接调SnowNLP(text).sentiments就能出分,适合周期紧的课设;二是纯 Python 实现,没有 GPU 依赖,环境配置不会成为翻车点;三是源码可读,情感模型本质是朴素贝叶斯加情感词典,想讲清楚原理时可以直接翻源码,比黑盒调 API 更有说服力。
但它的边界也要提前说清楚,否则验收时容易被问住。SnowNLP 的情感模型训练语料偏电商评论,迁移到影评场景会有偏差,比如“这部电影太绝了”这种反讽或强语气的句子,分值可能不够高;再比如带否定词的“不怎么样”,词典方法偶尔会判错。这些不是 bug,是词典方法的固有局限。合格的方案里应该主动承认这一点,并在报告里给出改进方向,比如引入否定词表做后处理、或者用人工标注的小样本做阈值校准。承认局限比假装完美更能拿分。
2.3 整体技术链路与依赖清单
整条链路是线性的:原始数据 → 清洗 → 分词 → 情感打分 → 极性映射 → 统计聚合 → 词云渲染。依赖库控制在五个以内,避免环境问题拖慢进度:
# 核心依赖,版本不锁死,装最新稳定版即可 pip install snownlp # 情感打分与分词 pip install jieba # 分词补充,词云前处理更可控 pip install pandas # 数据表操作 pip install matplotlib # 极性分布绘图 pip install wordcloud # 词云生成这里有个选型细节值得说:SnowNLP 自带分词,但词云场景下我更倾向用 jieba 再分一次,原因是 jieba 支持自定义词典和停用词表,能把“演技”“剧情”这类领域词切准,而 SnowNLP 的分词结果直接喂给词云容易出现碎词。两个库各干各的活,情感打分走 SnowNLP,词云分词走 jieba,职责清晰,排查问题时也好定位是哪一环出的错。
3. 数据清洗与情感打分模块:从原始短评到极性标签
3.1 清洗规则设计与实现
豆瓣短评的脏数据主要有四类:HTML 实体(如 )、表情符号、无意义短句(“哈哈哈哈”“mark”)、以及重复评论。清洗不做干净,后面情感分值和词云都会被污染。下面这段清洗函数把四类问题一次处理掉:
import re import pandas as pd def clean_comment(text): if not isinstance(text, str): return "" # 去掉 HTML 标签与实体 text = re.sub(r"<[^>]+>", "", text) text = re.sub(r"&[a-zA-Z]+;", "", text) # 去掉表情符号与非中英文数字字符 text = re.sub(r"[^\u4e00-\u9fa5a-zA-Z0-9,。!?、]", "", text) # 压缩连续重复字符,"好好好" -> "好" text = re.sub(r"(.)\1{2,}", r"\1", text) return text.strip() def load_and_clean(path): df = pd.read_csv(path) df["clean"] = df["comment"].apply(clean_comment) # 过滤空串和过短评论 df = df[df["clean"].str.len() >= 4] # 去重 df = df.drop_duplicates(subset=["clean"]) return df.reset_index(drop=True)这段代码的关键在三个正则。第一个去 HTML 标签,防止爬虫残留的<br>混进文本;第二个去表情和非目标字符,豆瓣短评里大量 emoji 会干扰分词;第三个压缩连续重复字符,因为“好好好好看”这类表达在情感打分时会被重复字符带偏权重。过滤长度小于 4 的评论是经验值——太短的句子情感信息不足,打出来的分值噪声大,不如直接丢弃。去重放在最后,避免清洗前后顺序不同导致漏删。
3.2 情感打分与三档极性映射
打分环节的核心是把 SnowNLP 的连续分值映射成离散标签。直接按 0.5 一刀切太粗糙,我一般用双阈值:大于 0.6 判正面,小于 0.4 判负面,中间判中性。阈值不是拍脑袋,而是拿一小批人工标注样本校准出来的,课设场景下标 50 条就够看出分布。
from snownlp import SnowNLP def score_sentiment(text): try: return SnowNLP(text).sentiments except Exception: # 空串或异常文本返回中性值,避免整批中断 return 0.5 def label_polarity(score, pos_th=0.6, neg_th=0.4): if score >= pos_th: return "正面" elif score <= neg_th: return "负面" return "中性" df["score"] = df["clean"].apply(score_sentiment) df["polarity"] = df["score"].apply(label_polarity)score_sentiment外面包了 try-except,这是血泪经验:SnowNLP 遇到极短或纯符号文本偶尔会抛异常,一条报错能让整批任务中断,加个兜底返回 0.5 最省事。双阈值的设计让中性区间有明确边界,报告里可以解释“为什么 0.5 附近不强行归类”,这比一刀切更经得起追问。阈值参数暴露成函数入参,方便后面做敏感性分析——把阈值从 0.6/0.4 调到 0.65/0.35,看正面率变化多少,这个对比表放进报告是加分项。
3.3 极性分布统计与结果落表
打完分要把结果聚合成能写进报告的指标。正面率、负面率、中性率三个数,加上均值和中位数,基本能描述整体情绪倾向。落表时保留原始评论、清洗文本、分值、标签四列,方便答辩时随机抽查几条验证合理性。
summary = df["polarity"].value_counts(normalize=True).round(3) print(summary) df[["comment", "clean", "score", "polarity"]].to_csv( "sentiment_result.csv", index=False, encoding="utf-8-sig" )encoding="utf-8-sig"这个细节别省,否则用 Excel 打开中文会乱码,答辩现场演示时很尴尬。统计结果建议同时输出绝对数量和占比,因为占比在样本量小时波动大,两个数一起看更稳。
4. 词云生成模块:让高频词自己说话
4.1 分词与停用词处理
词云的质量八成取决于分词和停用词。豆瓣短评里“电影”“这部”“真的”这类词高频但无信息量,必须进停用词表。我的做法是维护一个基础停用词表,再叠加领域停用词:
import jieba STOPWORDS = set(["电影", "这部", "真的", "就是", "觉得", "一个", "没有", "什么", "可以"]) def segment(text): words = jieba.lcut(text) return [w for w in words if len(w) >= 2 and w not in STOPWORDS] df["words"] = df["clean"].apply(segment) all_words = [w for ws in df["words"] for w in ws]长度小于 2 的词直接过滤,单字词在词云里既占地方又没意义。停用词表建议单独存成 txt 文件而不是硬编码在代码里,方便迭代时增删——第一版词云出来发现“演员”太泛,加进去重跑就行,不用改代码逻辑。
4.2 词频统计与词云渲染
词云渲染有两个坑:中文字体路径和背景色。matplotlib 默认字体不含中文,不指定字体路径会渲染出一堆方块。下面这段是能直接跑的完整渲染逻辑:
from wordcloud import WordCloud import matplotlib.pyplot as plt from collections import Counter freq = Counter(all_words).most_common(200) wc = WordCloud( font_path="C:/Windows/Fonts/simhei.ttf", # 中文字体路径,按系统改 width=1200, height=800, background_color="white", max_words=150, colormap="viridis" ).generate_from_frequencies(dict(freq)) plt.figure(figsize=(12, 8)) plt.imshow(wc, interpolation="bilinear") plt.axis("off") plt.savefig("wordcloud.png", dpi=200, bbox_inches="tight")font_path是最大的翻车点,Windows 用 simhei,macOS 换成/System/Library/Fonts/PingFang.ttc,Linux 找系统里的中文字体,路径写错就是满屏方块。generate_from_frequencies比generate更可控,因为词频已经算好了,不用让 wordcloud 再分一次词。max_words=150是经验值,词太多图会糊成一片,150 个词在 1200×800 画布上疏密刚好。
4.3 正面与负面评论分开出图
只出一张总词云信息量有限,更有说服力的做法是正面评论和负面评论各出一张,对比着看。正面词云里高频出现“感动”“演技”“剧情”,负面词云里高频出现“无聊”“尴尬”“拖沓”,这种对比本身就是分析结论。
for label in ["正面", "负面"]: subset = df[df["polarity"] == label] words = [w for ws in subset["words"] for w in ws] freq = dict(Counter(words).most_common(150)) if not freq: continue wc = WordCloud(font_path="C:/Windows/Fonts/simhei.ttf", width=1000, height=700, background_color="white").generate_from_frequencies(freq) wc.to_file(f"wordcloud_{label}.png")循环里加了空判断,防止某一类样本为空时generate_from_frequencies报错。两张图并排放进报告,配一句“正面评价聚焦表演与情感,负面评价聚焦节奏与逻辑”,分析深度立刻不一样。
5. 避坑与常见问题排查:那些让结果失真的细节
5.1 情感分值集中在 0.5 附近,区分度差
现象:跑完几千条评论,分值全挤在 0.45 到 0.55 之间,正面负面几乎分不开。原因通常是短评太短,SnowNLP 的朴素贝叶斯模型在信息量不足时倾向于输出接近先验的值。解决办法有两个:一是把双阈值放宽到 0.55/0.45 先看分布,二是对长度小于 8 的评论单独标记为“低置信度”,统计时剔除或单列。我一般选后者,报告里写清楚“低置信度样本占比 X%”,比强行归类诚实。
5.2 词云全是方块或乱码
现象:词云图打开全是方框。原因只有一个——字体路径不对或字体文件不存在。解决:先确认系统里有没有中文字体文件,Windows 在C:/Windows/Fonts/下找 simhei.ttf 或 msyh.ttf,macOS 用 PingFang,找不到就下载一个开源中文字体放到项目目录,用相对路径引用。别用font_path=None碰运气,跨机器必翻车。
5.3 分词把“不好看”切成“不”“好看”
现象:负面评论的词云里出现大量“好看”。原因是 jieba 默认词典不含“不好看”这类否定组合。解决:把“不好看”“没意思”“不推荐”这类高频否定短语加进自定义词典,jieba.load_userdict("user_dict.txt")一行搞定。自定义词典每行一个词,词频可省略,格式是“词语 词频 词性”,只写词语也能生效。
5.4 重复评论拉高某个词频
现象:某部剧的粉丝刷了上百条相同短评,词云里某个词大得离谱。原因:清洗阶段只按清洗后文本去重,但刷屏评论可能带不同标点导致清洗后仍有差异。解决:去重前先做一次归一化,去掉所有标点再比对,或者按评论长度和内容相似度做近似去重。课设场景下简单做法是统计每条清洗文本的出现次数,超过 3 次的只保留一条。
5.5 情感打分逐条调用太慢
现象:几千条评论跑了几分钟还没完。原因:SnowNLP 每次调用都要加载模型,逐条 apply 有重复开销。解决:数据量在万级以下时,把SnowNLP(text).sentiments包成函数用apply已经够快,真正的瓶颈往往是正则清洗。如果确实慢,先把清洗结果缓存成文件,打分和词云分两个脚本跑,避免每次调试都重跑全流程。这个习惯能省下大量等待时间。
6. 验收与答辩:怎么证明这套分析是可信的
验收环节最怕的是“结果看起来对,但说不出为什么对”。我的做法是准备三层证据。第一层是抽样验证表:从正面、中性、负面三档里各随机抽 10 条,人工核对标签是否合理,把核对结果做成表格放进报告,正面准确率、负面准确率各报一个数。课设场景下 80% 以上的抽样一致率就站得住,低于这个数就回头调阈值。第二层是阈值敏感性对比:把双阈值从 0.6/0.4 调到 0.65/0.35 和 0.55/0.45,各跑一遍,列出正面率变化表,证明结论对阈值不敏感。第三层是词云对比图:正面和负面各一张,配文字说明高频词差异,这是最直观的视觉证据。
答辩时被问“为什么不用深度学习模型”,别慌,按这个逻辑答:数据量在千级、任务周期短、需要可解释性,词典方法在这三个约束下是合理选择;同时主动说出局限——反讽和否定句处理不好,并给出改进路径,比如引入否定词表后处理或人工标注小样本微调。主动暴露边界比被问出来强得多。
| 验收项 | 合格标准 | 检查方式 |
|---|---|---|
| 数据清洗 | 无 HTML 残留、无重复行 | 随机抽 20 条目检 |
| 情感标签 | 抽样一致率 ≥ 80% | 三档各抽 10 条人工核对 |
| 极性分布 | 三档占比之和为 1 | 代码断言校验 |
| 词云质量 | 无方块、无碎词 | 目检 + 停用词表复查 |
| 阈值稳健性 | 阈值浮动 ±0.05 结论不变 | 三组阈值对比跑 |
最后说个习惯。我早期做这类分析,跑完直接截图放报告,结果答辩时被问“这条负面评论为什么判负面”,翻半天找不到原始数据对应关系。从那以后我每次做情感分析,都强制把原始评论、清洗文本、分值、标签四列一起落表,随机抽查时直接按行号定位。这个习惯看着笨,但能让你在任何追问面前三秒内拿出证据。希望帮到你。
本文还有配套的精品资源,点击获取