我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

SnowNLP实战:豆瓣短评情感分析与词云生成全流程

SnowNLP实战:豆瓣短评情感分析与词云生成全流程 简介这份资源面向具备一定Python基础、希望入门自然语言处理与文本挖掘的学习者围绕豆瓣《肖申克的救赎》影评展开情感分析与词云可视化实践重点演示如何调用SnowNLP库完成中文情感倾向判断并配合分词、词频统计等环节形成完整分析链路适合课程教学、课后练习或数据挖掘入门项目参考。压缩包共10个文件约37KB以8个py脚本为主体覆盖影评抓取、中文分词、SnowNLP多版本情感打分等环节另含1个csv评论数据与1个txt文本素材便于直接运行与调试。目前已有16951人浏览学习说明该案例在情感分析入门场景中具有较高参考价值。读者可从中获得可复用的SnowNLP调用脚本、分词与词云处理思路以及从原始评论到情感倾向输出的完整流程示例便于迁移到其他中文评论数据集。1. 豆瓣短评这种“脏数据”为什么值得用 SnowNLP 跑一遍抓过豆瓣短评的人都清楚原始数据有多难伺候一条评论里混着表情、换行、全角标点、重复刷屏的“哈哈哈哈”还有大量“还行吧”“一般般”这种情绪极淡的句子。拿这种语料做情感分析最容易翻车的不是模型选错而是清洗没做干净导致正面词被否定词带偏、长句被截断、评分和文本对不上。SnowNLP 这个库在国内中文情感分析圈子里被反复提起原因很直接它自带一个基于电商评论训练的情感打分模型s.sentiments直接吐 0 到 1 的分数不用自己标注、不用训练装上就能跑对“先跑通再优化”的实战场景非常友好。这篇笔记拆的是一套完整的豆瓣评论情感分析加词云流程从评论采集后的清洗、SnowNLP 逐条打分、按星级做交叉验证到 jieba 分词加停用词过滤生成词云。适合已经会用 pandas 读表、想快速把一份中文评论数据变成可解释结论的从业者也适合课程设计阶段需要一套能复现、能讲清边界的分析链路的人。下面按“数据怎么进、分数怎么出、图怎么画、坑在哪”的顺序走一遍。2. 数据清洗与 SnowNLP 打分链路从原始短评到 0~1 情感值2.1 先看清 SnowNLP 的模型边界再决定怎么用SnowNLP 的情感模块本质是一个朴素贝叶斯分类器训练语料以电商评论为主输出的是“这条文本偏正面”的概率不是严格意义上的情感极性标签。这意味着两件事第一它对“好评”“差评”这类电商高频表达很敏感对豆瓣影评里那种“镜头语言克制、叙事留白”的文艺表达则容易判成中性甚至偏负第二分数是连续的0.5 附近一大片样本其实处于“模型也拿不准”的区间直接按 0.5 切二分类会制造大量假阳性。我一般会先跑一遍分布看 0.4~0.6 这段占多少占比超过三成就要考虑加规则兜底而不是硬切。选它而不是选预训练大模型理由也很实际依赖轻、离线可跑、单条推理毫秒级几千到几万条评论在普通笔记本上几分钟出结果适合做第一版基线。真要上生产再换 BERT 类模型SnowNLP 的结果还能当对照基线用。2.2 清洗函数要处理哪几类脏东西清洗不是一句dropna就完事。豆瓣短评常见的噪声包括HTML 实体nbsp;、全角空格、连续重复字符、纯表情或纯标点、以及“看过”“想看”这类非评论标记。下面这段清洗函数是我反复改过几版的版本逻辑都写在注释里。import re import pandas as pd def clean_comment(text): if not isinstance(text, str): return # 1. 去 HTML 实体与标签 text re.sub(r[^], , text) text text.replace(nbsp;, ).replace(amp;, ) # 2. 全角转半角只处理常见标点与空格 text text.replace(\u3000, ) # 3. 去掉 URL 与 提及 text re.sub(rhttp\S|www\.\S, , text) text re.sub(r\S, , text) # 4. 压缩连续重复字符如“哈哈哈哈”-“哈哈” text re.sub(r(.)\1{2,}, r\1\1, text) # 5. 只保留中文、英文、数字和常用标点 text re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9。、,.!?;: ], , text) # 6. 去首尾空白并压缩多空格 text re.sub(r\s, , text).strip() return text df pd.read_csv(douban_comments.csv) df[clean] df[comment].apply(clean_comment) # 过滤清洗后过短或为空的记录 df df[df[clean].str.len() 4].reset_index(dropTrue) print(df.shape, df[clean].head(3).tolist())这段代码的关键点在第 4 步和第 5 步。第 4 步压缩重复字符是因为“太好看了吧吧吧吧”这类文本会让分词器切出大量无意义单字影响后续词云第 5 步的白名单过滤会误伤英文影评里的撇号如果你的数据里英文评论占比高要把加进白名单。str.len() 4这个阈值不是拍脑袋低于 4 个字的短评如“还行”“不错”信息量太低SnowNLP 打分方差极大留着反而污染统计。清洗完建议先df[clean].str.len().describe()看一眼长度分布中位数低于 8 的话说明原始数据质量偏差后续结论要谨慎。2.3 逐条打分与批量加速SnowNLP 单条调用很简单但几千条循环里每次都新建SnowNLP对象会有额外开销。常见做法是把打分封装成函数配合apply跑数据量上万时再考虑多进程。下面是打分与星级对齐的完整片段。from snownlp import SnowNLP import numpy as np def get_sentiment(text): if not text: return np.nan try: return SnowNLP(text).sentiments except Exception: # 极少数文本会触发内部异常返回 NaN 而不是中断整批 return np.nan df[sentiment] df[clean].apply(get_sentiment) # 按 0.5 切极性同时保留原始分数 df[polarity] np.where(df[sentiment] 0.5, 正面, 负面) # 与豆瓣星级做交叉验证星级列名按实际数据调整 if star in df.columns: cross pd.crosstab(df[star], df[polarity], normalizeindex) print(cross.round(3)) print(df[sentiment].describe())get_sentiment里的try/except不是摆设SnowNLP 对超长文本或含特殊字符的文本偶发内部报错一条异常中断整批是血泪经验。np.where的 0.5 阈值只是默认值跑完describe()后如果发现均值明显偏离 0.5比如 0.72说明语料整体偏正面这时候用 0.5 切会把大量中性样本划进正面更稳的做法是用中位数或按星级分布反推阈值。pd.crosstab那一步是整条链路里最有价值的校验如果五星评论里被判负面的比例超过 15%基本可以断定清洗或阈值有问题而不是模型不行。参数上normalizeindex让每行归一化方便直接看各星级内部的极性占比比看绝对数直观得多。3. jieba 分词与词云让高频词真正说明问题3.1 停用词表要按语料定制别直接抄通用表词云翻车最常见的原因不是画图参数而是停用词没管好。通用停用词表能滤掉“的”“了”“是”但滤不掉豆瓣语境里的“电影”“剧情”“演员”这类高频却无区分度的词也滤不掉“一部”“这部”这种量词。我的做法是在通用表基础上把当前语料 top50 高频词里明显无信息量的手动加进去再跑第二遍。分词时还要注意SnowNLP 自带分词但词云场景下 jieba 的自定义词典更灵活能加“演技在线”“节奏拖沓”这类短语。import jieba from collections import Counter # 加载通用停用词 自定义补充词 stopwords set() with open(stopwords.txt, encodingutf-8) as f: for line in f: stopwords.add(line.strip()) stopwords.update([电影, 一部, 这部, 真的, 感觉, 觉得, 有点]) def cut_words(text): words jieba.lcut(text) # 过滤停用词、单字、纯数字 return [w for w in words if w not in stopwords and len(w) 1 and not w.isdigit()] # 分别对正面和负面语料分词便于对比 pos_text .join(df[df[polarity] 正面][clean]) neg_text .join(df[df[polarity] 负面][clean]) pos_words cut_words(pos_text) neg_words cut_words(neg_text) print(正面高频:, Counter(pos_words).most_common(15)) print(负面高频:, Counter(neg_words).most_common(15))len(w) 1过滤单字是必须的否则“好”“差”“看”会霸榜not w.isdigit()挡掉年份和评分数字。分别对正负语料分词再对比高频词比混在一起画一张词云有信息量得多正面词里出现“治愈”“细腻”负面词里出现“拖沓”“尴尬”这种对比本身就是结论。停用词文件建议单独维护成 txt每次换数据集只改这一个文件别把词表硬编码进脚本。3.2 词云参数怎么调才不糊词云图糊成一团通常是max_words太大、font_path没指定中文字体、或者背景色和词色对比度不够。下面这组参数是我在 1080p 输出下比较稳的配置。from wordcloud import WordCloud import matplotlib.pyplot as plt def make_cloud(word_list, out_path, colormapviridis): freq Counter(word_list) wc WordCloud( font_pathSimHei.ttf, # 必须指定中文字体否则方块乱码 width1200, height800, background_colorwhite, max_words150, # 超过 150 词会互相挤压 colormapcolormap, collocationsFalse, # 关闭二元组避免重复短语 prefer_horizontal0.9 # 90% 横排可读性更好 ).generate_from_frequencies(freq) plt.figure(figsize(12, 8)) plt.imshow(wc, interpolationbilinear) plt.axis(off) plt.tight_layout() plt.savefig(out_path, dpi200) plt.close() make_cloud(pos_words, cloud_pos.png, Greens) make_cloud(neg_words, cloud_neg.png, Reds)generate_from_frequencies比generate更可控因为你传的是已经过滤好的词频字典不会让词云库再自作主张分词。collocationsFalse很关键默认开启时会把“非常 好看”拼成一个二元组导致词云里出现奇怪的组合词。prefer_horizontal0.9让绝大多数词横排竖排词太多会显著降低可读性。正负两张图用不同 colormapGreens/Reds是习惯做法汇报时一眼能区分。字体文件路径按系统实际位置改Linux 下常见是/usr/share/fonts/...Windows 下指向C:/Windows/Fonts/simhei.ttf。4. 避坑与排查SnowNLP 跑豆瓣评论最容易翻车的五处4.1 分数全挤在 0.5 附近二分类失去意义现象跑完describe()发现均值 0.51、标准差 0.08正负样本几乎对半且边界模糊。原因SnowNLP 训练语料是电商评论豆瓣短评里大量“还行”“中规中矩”这类中性表达被模型判成接近 0.5加上清洗不彻底残留的语气词进一步稀释信号。解决先看 0.4~0.6 区间占比超过 30% 就不要硬切二分类改成三档0.4 负面、0.4~0.6 中性、0.6 正面或者引入星级作为弱标签做阈值校准——用五星评论的分数分布 10 分位数当正面阈值一星评论的 90 分位数当负面阈值比固定 0.5 稳得多。4.2 否定句被判反比如“不推荐”打成正面现象明显差评“完全不推荐浪费时间”输出 0.8 的高分。原因朴素贝叶斯看的是词袋否定词和情感词被拆开统计“推荐”本身是正面词权重压过了“不”。解决这是 SnowNLP 的固有短板别指望调参解决。常见做法是在打分前加一层否定词规则检测“不/没/别/无”后跟正面词的两字窗口命中则把分数翻转或直接减 0.3。规则粗糙但有效能救回大部分显式否定句。更彻底的办法是换用带否定处理的模型但那就不在“轻量基线”的范畴里了。4.3 词云全是方块中文显示不出来现象词云图里每个词都是方框。原因font_path没指定或指向了不含中文字形的字体。解决显式传入中文字体绝对路径Windows 用C:/Windows/Fonts/simhei.ttfmacOS 用/System/Library/Fonts/PingFang.ttcLinux 先fc-list :langzh查可用字体再填。注意.ttc字体集合文件在部分 wordcloud 版本里支持不好优先用.ttf。4.4 星级和情感分对不上交叉表大面积矛盾现象五星评论被判负面的比例超过 20%。原因通常有三星级列读进来是字符串5和5不相等导致分组错位、清洗时把关键情感词误删、或者数据本身是“刷分”评论五星配差评文本。解决先df[star].dtype确认类型并astype(int)再抽查 20 条矛盾样本人工看区分是清洗误伤还是数据本身噪声如果是刷分数据这部分样本应当单独标记而不是硬塞进统计。4.5 大批量打分中途报错整批结果丢失现象跑到第 3000 条突然抛异常前面结果没保存。原因单条文本触发 SnowNLP 内部异常且没有兜底。解决打分函数里必须try/except返回NaN并且每处理 500 条就df.iloc[:i].to_csv()落一次盘或者用apply前先df[clean].map(lambda x: isinstance(x, str))过滤非字符串。别嫌落盘麻烦几万条数据重跑一次的时间成本远高于多写两行保存逻辑。5. 把分数变成结论阈值校准与结果验证的两个具体手法跑通链路只是第一步真正决定这份分析能不能拿去用的是阈值和验证。我一般会做两件事。第一件是阈值校准不直接用 0.5而是拿星级当弱监督信号用分位数定阈值。具体做法是把五星评论的sentiment取 20 分位数作为正面下界一星评论取 80 分位数作为负面上界中间区间标为中性。这样切出来的三档和星级的一致性通常能从 0.5 硬切的六成多提到八成左右。第二件是抽样人工校验从每个极性档里随机抽 30 条人工标“同意/不同意”算一个粗略准确率。SnowNLP 在豆瓣语料上正面档准确率一般能到 0.8 以上负面档偏低中性档最差——这个结论本身就要写进分析报告而不是只报一个总体准确率糊弄过去。验证环节还有个小技巧把sentiment分数按 0.1 分箱画直方图正常语料应该呈现双峰或至少是宽分布如果出现极端尖峰比如 0.99 处堆了一大坨多半是清洗时把长文本截断成了短句或者重复评论没去重。去重这一步容易被忽略df.drop_duplicates(subsetclean)一行的事但不做的话刷屏评论会直接把词云和均值带偏。参数层面再补一句jieba.lcut默认精确模式词云场景够用如果要做短语级分析换jieba.cut配合HMMTrue能识别未登录词但速度会慢一截几千条以内无所谓。词云的max_words我一般控制在 100~150超过这个数图就糊了宁可少而清。从那以后我每次跑这类情感分析都强制先做三件事看分数分布、跑星级交叉表、抽 30 条人工过目三步都过了才敢往下写结论。SnowNLP 不是万能钥匙但把它当基线、把边界讲清楚一份豆瓣评论数据在半小时内变成可解释的正负词云对比是完全做得到的。希望这套流程帮到你。本文还有配套的精品资源点击获取
返回列表