简介:这份资源面向具备一定Python基础、希望入门自然语言处理与文本挖掘的学习者,围绕豆瓣《肖申克救赎》影评展开情感分析与词云可视化实践,重点演示如何调用SnowNLP库完成中文情感倾向判定,并配合分词、词频统计等环节形成完整分析链路,适合课程教学、课后练习与小型文本分析项目参考。压缩包共10个文件,约37KB,以8个py脚本为主体,覆盖数据读取、分词处理与多组SnowNLP情感分析示例,另含1个csv评论数据与1个txt文本素材,便于直接运行与替换数据复现实验。目前已有16952人学习下载,说明该案例在入门群体中认可度较高。读者可从中获得可运行的脚本模板、中文分词与情感打分的实现思路,以及从原始评论到词云呈现的完整流程参考,便于迁移到其他评论数据集开展类似分析。
1. 从一堆短评里挖出情绪:这个项目到底解决什么问题
手头有一份从公开渠道整理下来的影评数据,几千条短评堆在 CSV 里,人眼翻十条还行,翻一千条就只剩麻木。这个项目干的事很直接:用 Python 把中文短评读进来,先做清洗,再交给 SnowNLP 逐条打分,输出每条评论的情感倾向,最后把高频词抽出来画成词云,让「大家到底在夸什么、骂什么」一眼可见。它适合两类人:一类是想跑通一个完整中文文本分析流程的新手,另一类是手里已经有评论数据、想快速搭个分析脚本做二次开发的熟手。技术栈不复杂,Python + SnowNLP + jieba + wordcloud + pandas,机器学习的门槛被压得很低,重点在数据清洗和结果解读这两头。
2. 项目结构与技术选型:为什么是 SnowNLP 而不是自己训模型
2.1 目录骨架与各文件职责
这类评论分析项目,常见做法是拆成「数据层、处理层、展示层」三段,目录不会太深,方便一个人维护。我一般会按下面这个结构组织,读者拿到源码后可以对照着找入口:
douban_sentiment/ ├── data/ │ ├── raw_comments.csv # 原始短评,字段:评论ID、用户名、评分、评论内容、时间 │ └── clean_comments.csv # 清洗后的中间结果 ├── src/ │ ├── loader.py # 读数据、字段校验、编码处理 │ ├── cleaner.py # 去噪、去重、分词、停用词过滤 │ ├── sentiment.py # SnowNLP 打分与情感标签映射 │ └── wordcloud_gen.py # 词频统计与词云出图 ├── output/ │ ├── sentiment_result.csv # 带情感分的结果表 │ └── wordcloud.png # 词云图 ├── stopwords.txt # 中文停用词表 ├── requirements.txt └── main.py # 串起全流程的入口main.py是唯一入口,按「加载 → 清洗 → 打分 → 出图」顺序调用四个模块,每个模块只做一件事,改起来不会牵一发动全身。data/放原始和中间数据,output/放结果,这种「输入输出分离」的习惯在后期换数据源时特别省事——换一份评论数据,只要字段名对齐,其余代码基本不动。
2.2 技术选型:SnowNLP 的定位与边界
SnowNLP 是一个面向中文文本处理的轻量库,内置了情感分析、分词、关键词提取、拼音转换等功能。它的情感分析本质上是基于情感词典加朴素贝叶斯训练出来的模型,对每条文本返回一个 0 到 1 之间的分数,越接近 1 越偏正面,越接近 0 越偏负面。选它的理由很实际:不用自己标注数据、不用训练、装完就能用,对「快速跑通一条中文情感分析链路」这个目标来说性价比最高。
但它的边界也要说清楚。第一,它是通用领域模型,对影视评论这种带大量口语、反讽、网络用语的文本,准确率会有波动,比如「这剧情真是绝了」这种句子,模型可能判成正向,实际是吐槽。第二,它返回的是连续分数,不是三分类标签,需要自己定阈值切分。第三,它不处理长文本的篇章级情感,只适合短句级别的判断。所以这个项目的定位不是「做一个高精度情感分类器」,而是「用最低成本把一批短评的情绪分布先摸出来」,后续要提精度,再考虑换模型或加规则,这是二次开发的空间。
分词环节用 jieba,词云用 wordcloud,数据处理用 pandas,这几个都是中文文本分析里的常规组合,安装和调用都很成熟,不需要额外折腾。
2.3 依赖安装与环境准备
环境上,Python 3.8 以上即可,依赖不多,requirements.txt里常见写法是:
pandas>=1.3.0 snownlp>=0.12.3 jieba>=0.42.1 wordcloud>=1.8.1 matplotlib>=3.4.0安装命令一行搞定:
pip install -r requirements.txt这里有个容易忽略的点:wordcloud 在部分环境下编译需要本地 C 编译器,如果pip install报错,常见做法是先升级 pip 再重试,或者直接用预编译 wheel 包。matplotlib 是 wordcloud 出图的依赖,别漏装,否则词云生成时会报后端相关的错。装完后建议先跑一个最小验证:
from snownlp import SnowNLP s = SnowNLP("这部电影很好看") print(s.sentiments) # 输出 0~1 之间的分数能打印出分数,说明 SnowNLP 的模型文件加载正常,后面整条链路才有基础。
3. 核心链路拆解:从原始短评到情感分数与词云
3.1 数据加载与字段校验
第一步是把 CSV 读进来,并确认关键字段存在。评论数据常见的坑是编码不一致(UTF-8 和 GBK 混用)和空值,加载层要把这两件事挡在前面:
import pandas as pd def load_comments(path): # 优先按 utf-8 读,失败再退回 gbk,兼容不同来源的导出文件 try: df = pd.read_csv(path, encoding="utf-8") except UnicodeDecodeError: df = pd.read_csv(path, encoding="gbk") # 字段校验:评论内容列是后续所有处理的前提 required = ["评论内容"] for col in required: if col not in df.columns: raise ValueError(f"缺少必要字段: {col}") # 丢掉评论为空的行,避免后续分词和打分报错 df = df.dropna(subset=["评论内容"]) df["评论内容"] = df["评论内容"].astype(str).str.strip() df = df[df["评论内容"] != ""] return df.reset_index(drop=True)这段代码的逻辑是「先容错、再校验、后清洗」。encoding的 try/except 是为了兼容不同工具导出的 CSV,实际项目里这一步能省掉大量「读进来全是乱码」的排查时间。dropna加空串过滤是必须的,SnowNLP 对空字符串会返回异常分数,jieba 对空串分词也会得到空列表,提前挡掉比事后补判断干净。reset_index是为了让后续按行号对齐时不出错。
3.2 文本清洗与分词
原始短评里混着表情符号、链接、重复标点、无意义的口语词,直接丢给 SnowNLP 和 jieba 会影响结果。清洗层做三件事:去噪、去重、分词加停用词过滤:
import re import jieba def clean_text(text): # 去掉 URL、@用户、话题标签、多余空白和重复标点 text = re.sub(r"http\S+|www\.\S+", "", text) text = re.sub(r"@\S+", "", text) text = re.sub(r"#\S+#", "", text) text = re.sub(r"([!?。,,.~])\1+", r"\1", text) text = re.sub(r"\s+", " ", text) return text.strip() def segment(text, stopwords): # 精确模式分词,过滤停用词和单字,保留有意义的词 words = jieba.lcut(text) return [w for w in words if len(w) > 1 and w not in stopwords] def load_stopwords(path): with open(path, encoding="utf-8") as f: return set(line.strip() for line in f if line.strip())clean_text里的正则按顺序处理:先删链接和 @,再删话题标签,然后合并重复标点(「好看!!!」变成「好看!」),最后压缩空白。顺序不能乱,否则删完链接可能留下多余空格。segment用 jieba 的精确模式,过滤长度小于等于 1 的词,是因为单字在词云里没有信息量,还会挤占版面。停用词表建议用通用中文停用词表打底,再手动补上「电影」「剧情」这类在本场景下高频但无区分度的词——这一步是词云好不好看的关键,血泪经验是停用词不补,词云里全是「的」「了」「是」。
3.3 SnowNLP 情感打分与标签映射
打分环节是整条链路的核心。SnowNLP 对每条文本返回一个分数,项目里通常再映射成「正面 / 中性 / 负面」三档,方便统计和展示:
from snownlp import SnowNLP def score_sentiment(text): try: score = SnowNLP(text).sentiments except Exception: # 极短或异常文本兜底,返回中性分 score = 0.5 return score def label_sentiment(score, pos_th=0.6, neg_th=0.4): # 阈值可按数据分布调整,默认 0.6 以上正面,0.4 以下负面 if score >= pos_th: return "正面" elif score <= neg_th: return "负面" else: return "中性" def batch_score(df): df["情感分数"] = df["评论内容"].apply(score_sentiment) df["情感标签"] = df["情感分数"].apply(label_sentiment) return dfscore_sentiment里的 try/except 是必要的兜底,SnowNLP 对某些特殊字符组合会抛异常,一条报错不该让整批数据中断。label_sentiment的两个阈值是参数化的,默认 0.6 和 0.4 是经验值,实际用的时候建议先看分数分布再定——如果数据整体偏正面,0.6 可能把大量中性判成负面,这时候把阈值往上调更合理。batch_score用apply逐行处理,几千条数据量下速度可以接受,数据量上到十万级再考虑并行或批量优化。
打分完成后,通常会做一次分布统计,看看正面、中性、负面各占多少,这个比例本身就是结论的一部分:
dist = df["情感标签"].value_counts(normalize=True) print(dist)3.4 词频统计与词云生成
词云部分先把所有评论分词后汇总词频,再用 wordcloud 出图。中文词云必须指定字体文件,否则会显示成方块,这是新手最容易翻车的地方:
from collections import Counter from wordcloud import WordCloud import matplotlib.pyplot as plt def build_word_freq(df, stopwords): counter = Counter() for text in df["评论内容"]: counter.update(segment(clean_text(text), stopwords)) return counter def draw_wordcloud(counter, font_path, out_path): wc = WordCloud( font_path=font_path, # 中文字体路径,必填 width=1200, height=800, background_color="white", max_words=200, colormap="viridis" ) wc.generate_from_frequencies(counter) plt.figure(figsize=(12, 8)) plt.imshow(wc, interpolation="bilinear") plt.axis("off") plt.savefig(out_path, dpi=200, bbox_inches="tight")build_word_freq用Counter累加每条评论的分词结果,得到全局词频。draw_wordcloud里font_path指向系统中文字体(如思源黑体或系统自带黑体),max_words控制展示词数,colormap决定配色。generate_from_frequencies直接吃词频字典,比传原始文本更可控,因为分词和停用词过滤已经在前面做完了。保存时bbox_inches="tight"能去掉多余白边,出图更干净。
4. 跑起来与避坑:几个真实会卡住的地方
4.1 常见问题排查
现象一:词云全是方块。原因是 wordcloud 默认字体不含中文。解决方法是显式传font_path,指向一个确定存在的中文字体文件,路径写绝对路径最稳。
现象二:SnowNLP 分数普遍偏高或偏低。原因是通用模型对特定领域文本有偏置。解决方法是先看分数直方图,再调整label_sentiment的阈值,不要直接套默认值。
现象三:分词结果里全是单字和标点。原因是没加载停用词或停用词表不匹配。解决方法是确认stopwords.txt编码为 UTF-8,并在segment里同时过滤长度和停用词。
现象四:读 CSV 报编码错误。原因是导出工具用了 GBK。解决方法是加载层做 try/except 双编码兜底,或者用chardet先探测编码。
现象五:词云图里出现「电影」「剧情」这类词。原因是停用词没覆盖领域高频词。解决方法是在通用停用词表基础上手动追加,这一步没有捷径,只能看一次词云补一次。
4.2 运行顺序与验证方法
整条链路的运行顺序是固定的:先load_comments读数据,再clean_text加segment做清洗分词,然后batch_score打分,最后build_word_freq加draw_wordcloud出图。验证时不要一上来就跑全量,先取前 20 条跑一遍,确认每一步的中间结果合理——清洗后没有乱码、分词后没有单字堆、分数在 0 到 1 之间、词云能正常出图。这四步都过了,再上全量数据,能省掉大量「跑到一半报错不知道哪一步出问题」的时间。
5. 二次开发:把通用脚本改成能出结论的分析工具
这个项目的骨架已经能跑,但离「能出结论」还差一层。二次开发我一般从三个方向下手。
第一个方向是按评分做交叉验证。原始数据里如果有用户评分字段,可以把评分和 SnowNLP 分数做对比,算一下两者的一致性。评分高但情感分低的评论,往往是反讽或吐槽,这批样本单独拎出来看,能反过来校准阈值。做法很简单:
# 评分归一化到 0~1,与情感分数做相关性对比 df["评分归一"] = df["评分"] / df["评分"].max() corr = df["评分归一"].corr(df["情感分数"]) print(f"评分与情感分相关性: {corr:.3f}")相关性低于 0.5 时,说明模型和人工判断分歧较大,这时候要么调阈值,要么引入规则修正,比如检测到「呵呵」「真是」这类反讽高频词时,把分数往中性拉。
第二个方向是分时段看情绪走势。如果数据带时间字段,按天或按周聚合情感分数,画一条趋势线,能看出评论情绪随剧情推进或口碑发酵的变化。这一步只需要在batch_score之后加一个groupby,成本很低,但结论价值比单张词云高得多。
第三个方向是词云分正负两组出图。把正面评论和负面评论分开做词频统计,各出一张词云,对比两张图里高频词的差异,比一张混合词云信息量大得多。实现上就是把build_word_freq的输入按情感标签过滤一下,调用两次draw_wordcloud。
| 改造方向 | 改动位置 | 产出价值 |
|---|---|---|
| 评分交叉验证 | 打分后加相关性计算 | 校准阈值、发现反讽样本 |
| 分时段情绪走势 | 打分后按时间聚合 | 看情绪随时间变化 |
| 正负分组词云 | 词频统计前按标签过滤 | 对比正负评论关注点差异 |
改造时有个习惯我一直保持:每加一个功能,先在 20 条样本上验证输出格式对不对,再上全量。因为这类分析脚本的 bug 往往不报错,只是结果悄悄偏了,等全量跑完才发现,后悔药都没得吃。从那以后我每次改完打分或分词逻辑,都强制先跑小样本对一遍中间结果,确认没偏再放开。希望这个思路能帮到你,把手里那批评论数据真正用起来。
本文还有配套的精品资源,点击获取