
简介这份资源面向具备一定Python基础、希望入门文本挖掘与情感分析的学习者围绕豆瓣《肖申克救赎》评论文本演示如何调用SnowNLP库完成情感倾向判断并配合分词与词云展示帮助读者建立从数据读取到结果呈现的完整分析思路适合课程教学与自学练手。压缩包共10个文件以8个py脚本为主体覆盖评论抓取、中文分词、SnowNLP多版本调用等环节另含1个csv评论数据与1个txt文本整体约37KB体量轻便便于直接运行与二次修改。目前已有16951人学习下载具备较好的参考基础。资源按脚本编号递进组织读者可依次对照评论数据加载、分词处理、情感值计算与词云生成等步骤理解各环节的输入输出关系并在此基础上替换数据源或调整参数快速迁移到影评、商品评价等场景的文本情感分析实践中。1. 从一堆短评里挖出情绪这个 SnowNLP 情感分析项目能干什么手头有一份抓下来的影评数据几千条短评躺在 CSV 里肉眼翻十条还行翻一千条就废了。这个项目解决的就是这件事把中文短评批量喂进去输出每条评论的情感倾向得分再按分数切出正面、负面两堆最后用词云把两堆评论里最高频的词摊开给你看。技术栈很朴素——Python 打底SnowNLP 做中文情感打分jieba 分词wordcloud 出图pandas 管数据matplotlib 兜底展示。适合谁适合刚接触中文 NLP、想拿真实评论文本练手的学生和转行开发者也适合需要快速给一批中文反馈做粗筛的产品、运营侧同学。它不追求模型精度天花板追求的是整条链路能跑通、每一步都能改。2. 项目骨架与技术选型为什么是 SnowNLP 而不是自己训模型2.1 目录结构与各文件职责这类项目通常不会太大一个能跑起来的版本大概长这样我按自己拆包时最常见的组织方式列一下sentiment_douban/ ├── data/ │ ├── raw_comments.csv # 原始抓取数据至少含评论列 │ └── cleaned_comments.csv # 清洗后的中间产物 ├── output/ │ ├── sentiment_result.csv # 带情感得分的明细 │ ├── wordcloud_pos.png # 正面评论词云 │ └── wordcloud_neg.png # 负面评论词云 ├── stopwords.txt # 中文停用词表 ├── font.ttf # 词云中文字体缺了必乱码 ├── preprocess.py # 读取、去重、去噪 ├── sentiment.py # SnowNLP 打分与正负切分 ├── wordcloud_gen.py # 分词 词云生成 └── main.py # 串起全流程的入口拆开看preprocess.py只干脏活读 CSV、丢空值、去重复、把明显是表情符号堆砌或纯标点的行剔掉。sentiment.py是核心负责给每条评论算一个 0 到 1 的分数。wordcloud_gen.py拿切分后的正负两堆文本分别分词、过滤停用词、出图。main.py不写业务逻辑只按顺序调用方便你单独调试某一步。这种拆法的好处是你想换情感模型只动sentiment.py想换词云样式只动wordcloud_gen.py互不牵连。数据文件里最关键的是评论列列名可能是comment、content或短评读进来第一件事就是确认列名别等到后面 KeyError 才回头找。2.2 选型逻辑SnowNLP 的定位与边界SnowNLP 是一个面向中文的轻量文本处理库内置了分词、关键词提取、情感分析等能力。它的情感模块本质上是基于电商评论语料训练出来的朴素贝叶斯分类器输出的是「这条文本偏正面」的概率值越接近 1 越正面越接近 0 越负面0.5 附近就是模棱两可。为什么这个项目选它而不是 BERT 微调三个现实原因。第一零训练成本pip install snownlp之后SnowNLP(text).sentiments一行就出分对练手项目来说链路完整性比精度重要。第二中文开箱即用不需要自己准备标注数据、不需要 GPU。第三速度快几千条评论几秒跑完适合反复调参试错。但它的边界必须说清楚训练语料偏电商评论用在影评上会有偏差反讽、双重否定、网络新梗经常翻车。所以这个项目的定位是「粗筛工具」而不是「精准判官」。我一般会在结果里保留原始分数而不是只留正负标签这样后面想调阈值随时能重切不用重跑一遍打分。2.3 环境准备与依赖安装Python 版本建议 3.8 以上太老的版本在 wordcloud 编译上容易出问题。依赖清单不长pip install pandas jieba snownlp wordcloud matplotlib逐个说下用途pandas负责 CSV 读写和批量处理jieba做中文分词词云前必须过一遍snownlp是情感打分主力wordcloud生成词云图matplotlib用来显示和保存图片。Windows 上如果wordcloud装不上多半是缺 C 编译环境常见做法是去下对应的预编译 wheel 包本地安装或者直接用 conda 装能省掉一堆编译报错。还有一个容易被忽略的前置条件中文字体文件。wordcloud 默认字体不含中文不指定字体的话出来的图全是方块。项目里放一个font.ttfWindows 可以从系统字体目录拷simhei.ttfmacOS 用PingFang.ttc或STHeiti Light.ttc路径在代码里配成变量换机器只改这一处。3. 核心链路拆解从原始评论到情感得分再到词云3.1 数据读取与清洗脏数据不处理后面全是玄学真实抓下来的评论数据没有干净的。空行、重复、纯表情、带链接的、长度只有一两个字的全都有。清洗这一步偷懒后面情感分数和词云都会被污染。import pandas as pd import re def load_and_clean(path, colcomment): # 读 CSV指定编码中文数据常见 utf-8 或 gbk df pd.read_csv(path, encodingutf-8) # 列名兜底不同来源列名不一致时统一成 comment if col not in df.columns: for c in df.columns: if c.strip() in (content, 短评, 评论): df df.rename(columns{c: comment}) break # 丢空值 df df.dropna(subset[comment]) # 转字符串并去首尾空白 df[comment] df[comment].astype(str).str.strip() # 去掉纯标点/纯符号的行去掉非中文非字母数字后长度为 0 的丢弃 df df[df[comment].apply(lambda x: len(re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9], , x)) 0)] # 去重复评论 df df.drop_duplicates(subset[comment]) # 过滤过短评论少于 4 个字符的信息量太低 df df[df[comment].str.len() 4] return df.reset_index(dropTrue)这段逻辑的关键点在两个正则和两个阈值。re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9], , x)把非中文、非字母数字的字符全抹掉抹完长度为 0 说明这行就是表情或标点堆砌直接丢。长度阈值 4 是我踩过坑之后定的两三个字的评论「好看」「垃圾」情感倾向其实很明确但词云阶段贡献不了有效词而且这类短评在数据里占比高时会拉偏整体分布所以统一在清洗阶段处理掉。如果你的分析目标就是短评这个阈值可以调到 2但要接受词云会比较稀疏。drop_duplicates放在长度过滤之前还是之后影响不大但一定要做——抓取脚本重试机制经常导致同一条评论入库多次不去重的话高频评论会在词云里被放大好几倍。3.2 SnowNLP 情感打分一行代码背后的参数与阈值打分本身很简单难的是怎么组织批量处理和结果落盘。from snownlp import SnowNLP import pandas as pd def score_sentiment(df, text_colcomment): scores [] for text in df[text_col]: try: s SnowNLP(text).sentiments # 返回 0~1 的正面概率 except Exception: s None # 空文本或异常时置空不中断整批 scores.append(s) df[sentiment_score] scores # 按阈值切正负0.6 以上算正面0.4 以下算负面中间留作中性 df[label] df[sentiment_score].apply( lambda x: pos if x is not None and x 0.6 else (neg if x is not None and x 0.4 else neutral) ) return dfSnowNLP(text).sentiments返回的是浮点数含义是「该文本属于正面类别的概率」。这里我用了双阈值 0.6 / 0.4 而不是单阈值 0.5原因是 SnowNLP 在 0.5 附近的分辨力很差大量中性陈述会落在 0.45 到 0.55 之间硬切两分会把一堆模棱两可的评论强行归类导致后面词云的正负对比失真。留一个 neutral 区间正负两堆的纯度会明显提升。try/except那层不能省。SnowNLP 遇到极短文本或特殊字符偶尔会抛异常一条报错中断整批处理是最烦的包一层把异常条目置 None最后统计一下 None 的比例超过 5% 就说明清洗没做到位回头查数据。阈值不是固定的影评场景下我一般会把正面阈值压到 0.55 左右因为影评里「还行」「凑合」这类表达被 SnowNLP 判得偏正面阈值太高会漏掉一批真实好评。这个值建议你拿几十条人工标注过的评论做个小验证集跑一遍看准确率再定别拍脑袋。3.3 分词与停用词过滤词云质量的分水岭词云好不好看八成取决于分词和停用词。jieba 默认分词对通用文本够用但影评里人名、片名、网络用语多需要针对性处理。import jieba from collections import Counter def cut_words(texts, stopwords_pathstopwords.txt): # 加载停用词一行一个 with open(stopwords_path, encodingutf-8) as f: stopwords set(line.strip() for line in f if line.strip()) # 影评场景补充的领域停用词这些词高频但无信息量 stopwords.update([电影, 这部, 一个, 觉得, 真的, 就是, 还是, 没有]) word_list [] for text in texts: # 精确模式分词适合分析场景 words jieba.lcut(text) for w in words: w w.strip() # 过滤单字、停用词、纯数字 if len(w) 2 or w in stopwords or w.isdigit(): continue word_list.append(w) return Counter(word_list)几个参数值得说。jieba.lcut默认是精确模式适合做词频统计如果你发现某个片名被切碎了用jieba.add_word(片名)提前加进词典这是最省事的修法。单字过滤len(w) 2是必须的中文单字歧义太大「好」「差」单独出现说明不了什么留着只会污染词云。停用词表分两层通用停用词的、了、是、在和领域停用词电影、这部、觉得。领域这层是这个项目能不能出效果的关键通用停用词网上随便下但「电影」「剧情」「演员」这种词在影评里出现频率极高却毫无区分度不手动加进去词云中心永远是这几个大词正负两堆看起来一模一样。我一般会先跑一遍不做领域过滤的词频看 Top 30 里哪些是「放之四海皆准」的词手动补进停用词表迭代两轮就干净了。3.4 词云生成字体、尺寸与遮罩参数词云这步参数多配错一个就出废图。from wordcloud import WordCloud import matplotlib.pyplot as plt def make_wordcloud(freq_dict, out_path, font_pathfont.ttf): wc WordCloud( font_pathfont_path, # 中文字体不配必乱码 width1000, height700, # 画布尺寸 background_colorwhite, # 背景色 max_words150, # 最多显示词数太多会糊 colormapviridis, # 配色方案正负可用不同色系区分 collocationsFalse # 关闭二元词组避免重复词 ) wc.generate_from_frequencies(freq_dict) # 直接吃词频字典比吃原始文本可控 plt.figure(figsize(10, 7)) plt.imshow(wc, interpolationbilinear) plt.axis(off) plt.savefig(out_path, dpi200, bbox_inchestight) plt.close()generate_from_frequencies比generate更推荐因为你已经在上一步算好了词频、过滤了停用词直接喂字典能完全掌控哪些词上图、权重多少避免 wordcloud 内部再分一次词带来的不确定性。collocationsFalse也要关否则它会把相邻词拼成二元组中文里经常拼出无意义的组合。max_words150是个经验值超过 200 词图会挤成一团低于 80 又显得空。正负两张图建议用不同colormap比如正面用暖色系、负面用冷色系视觉上一眼能区分。dpi200保证保存的图够清晰bbox_inchestight去掉多余白边。4. 避坑与排查这几处翻车我替你踩过了4.1 词云全是方块字体路径没生效现象图能生成但所有中文都是方框。原因wordcloud 默认字体是 DroidSansMono不含中文字形font_path没传或传了不存在的路径时不会报错静默出方块。解决确认font.ttf文件真实存在路径用绝对路径最稳Windows 上注意反斜杠转义或直接用正斜杠。跑之前先os.path.exists(font_path)断言一下比事后看图强。4.2 情感分数大面积集中在 0.5 附近现象几千条评论分数几乎都在 0.45 到 0.55 之间正负切不出来。原因SnowNLP 训练语料是电商评论影评的表达方式和用词分布差异大模型对影评的判别力下降大量样本落在决策边界附近。解决一是放宽阈值正面 0.55、负面 0.45接受一定噪声二是对明显的情感词做规则兜底比如文本里出现「烂片」「难看」直接判负「神作」「推荐」直接判正规则和模型分数取并集三是如果数据量够换一个在影评语料上微调过的模型SnowNLP 只作为基线对照。4.3 词云里全是「电影」「剧情」这类大词现象正负两张词云长得差不多中心都是几个通用词。原因停用词表只有通用词没有领域词高频无信息量的词占据了权重。解决先输出 Top 50 词频人工过一遍把跨正负两类都高频出现的词补进停用词表迭代两到三轮。判断标准很简单这个词在正面堆和负面堆里排名都靠前那它就没有区分价值该进停用词。4.4 读取 CSV 报编码错误现象UnicodeDecodeError或读进来全是乱码。原因抓取工具保存时用了 gbk 或带 BOM 的 utf-8-sig而pd.read_csv默认按 utf-8 解析。解决先试encodingutf-8报错换encodinggbk还不行试encodingutf-8-sig。稳妥做法是写个小函数依次尝试三种编码哪个能读用哪个别在一个编码上死磕。4.5 处理几千条数据就卡住现象跑打分阶段明显变慢几千条要等好几分钟。原因SnowNLP 每次实例化都要加载内部模型循环里反复SnowNLP(text)有额外开销加上 Python 单线程逐条处理。解决数据量上万时用multiprocessing.Pool把打分并行化按 CPU 核数开进程或者先把清洗后的数据落盘打分单独跑一个脚本避免每次调词云参数都重跑一遍情感分析。分阶段落盘是这个项目最实用的工程习惯。5. 二次开发方向把粗筛工具改成能验证的分析管线这个项目跑通之后最值得做的改造不是换更炫的词云样式而是给它加一层「可验证性」。SnowNLP 的分数到底准不准光看图是看不出来的。我的做法是抽 100 条评论人工标一遍正负存成labeled.csv然后写个对比脚本算准确率和混淆矩阵from sklearn.metrics import classification_report def evaluate(df_labeled, pred_collabel, true_colmanual_label): # 只评估 pos/neg 两类neutral 单独统计占比 mask df_labeled[pred_col].isin([pos, neg]) print(classification_report( df_labeled.loc[mask, true_col], df_labeled.loc[mask, pred_col], labels[pos, neg] ))跑完你会拿到每个类别的 precision 和 recall正面类的 recall 低说明漏判好评多负面类 precision 低说明误伤多。有了这个基线后面任何改动——调阈值、加规则、换模型——都能量化对比而不是凭感觉说「好像准了点」。第二个改造方向是规则与模型融合。建一个情感词典文件正面词、负面词各一列打分时统计文本命中的正负词数量和 SnowNLP 分数做加权。权重怎么定我一般给模型分 0.7、规则分 0.3 起步用上面的评估脚本调两三轮就能找到适合你数据的比例。这个思路在影评这种反讽多的场景里收益明显因为「画面精美但剧情稀烂」这种句子模型容易被前半句带偏规则命中「稀烂」能把它拉回来。第三个方向是时间维度。如果原始数据带评论时间按月份聚合平均情感分画一条趋势线能看出某部作品的口碑是不是随时间下滑。这步只需要在sentiment.py输出里保留时间列后面用 pandas 的groupby按月求均值matplotlib 出折线图代码量很小但分析价值比单张词云高得多。最后一个技巧关于可复现性把阈值、停用词路径、字体路径、最大词数全部抽到一个config.py里别散落在各个脚本中。我吃过亏——调完阈值忘了改回来隔天重跑结果对不上排查了半天才发现是某个脚本里硬编码的 0.5 没同步。从那以后我每次改参数都强制走一遍配置文件跑完把 config 和输出一起归档后悔药这东西提前备好总比事后找强。希望帮到你。本文还有配套的精品资源点击获取