Python中文情感分析实战:基于jieba与SnowNLP的舆情分析
做舆情分析的时候最麻烦的往往不是算法选型而是数据采集和数据清洗。之前在处理热门人物相关话题时经常遇到评论文本杂乱、关键词口径不一致、情感判断偏差大等问题网上的资料又东一块西一块。最近曹云金与郭麒麟的话题热度较高刚好可以拿来做一期完整的 Python 情感分析实战从数据准备到分词、情感打分、可视化完整走一遍流程。如果你正在学习中文 NLP或者想用 Python 快速分析某个人物、某个事件在社交平台上的情绪倾向这篇文章可以直接照着敲。1. 背景与核心概念1.1 什么是网络舆情情感分析网络舆情情感分析简单来说就是通过程序自动判断一段文本所表达的态度是正面、负面还是中性。比如“他在台上的表演很有感染力”是正面评价“最近没什么像样的作品”是偏负面评价而“刚看完一段视频具体不清楚”则是中性表达。这次的任务不是去评判曹云金与郭麒麟谁对谁错而是把网络评论当成一种数据样本用技术手段观察一段时间内两位人物的舆论情感差异。这样做的好处是观点不靠感觉用数据描述整体趋势。1.2 为什么选 SnowNLP jiebaPython 生态里做中文情感分析最常见也最容易上手的组合是jieba加SnowNLP。jieba是一个中文分词库负责把完整的句子切分成词。例如“他在台上表演很卖力”会切分成“他 / 在 / 台上 / 表演 / 很 / 卖力”。分词是很多中文 NLP 任务的第一步后面做词频统计、关键词提取都会用到。SnowNLP是一个纯 Python 实现的中文文本处理库内置了情感分析模型。调用SnowNLP(text).sentiments会返回一个0 到 1之间的浮点数越接近1表示越偏向正面越接近0表示越偏向负面0.5左右可以认为接近中性。这套方案不需要训练自己的模型也没有复杂的深度学习环境非常适合做原型验证、数据分析练手、舆情趋势观察。1.3 整体流程整个分析流程可以分为五步准备评论数据使用模拟数据保证案例可复现。数据清洗去掉 URL、、表情符号、多余空格。分词与词频统计提取高频关键词观察大家都在聊什么。情感打分对每条评论计算情感得分然后按人物和时间段分组聚合。可视化用 Matplotlib 绘制情感变化曲线和词频条形图。下面我们逐一展开。2. 环境准备与版本说明本次实战使用 Python 3 环境。我在本地用的是 Python 3.10但 Python 3.8 及以上版本基本都能正常运行。需要安装以下依赖库pip install jieba snownlp pandas matplotlib如果你使用 Jupyter Notebook直接在单元格里执行上面的命令也可以。建议创建一个独立项目目录结构如下sentiment_analysis/ ├── analysis.py ├── requirements.txt └── data/ └── comments.csvrequirements.txt内容如下jieba0.42.1 snownlp0.12.3 pandas1.5.0 matplotlib3.5.0版本号可以根据你本地环境微调重点演示配置思路而不是锁死版本。data/comments.csv可以由后面的模拟数据脚本生成也可以直接使用真实采集到的数据。3. 准备评论数据模拟数据设计为了让大家能够直接运行代码我先生成一批模拟评论数据。这些数据以曹云金、郭麒麟两个人物、两个时间段2015-2019、2020-2024作为分组维度文本内容参考了常见网络表达风格但全部为虚拟生成仅用于演示技术流程不反映任何真实观点或事实。这样设计有一个好处不依赖外部网络环境不需要处理微博登录态和加密参数每个人下载代码后都能立刻跑通。等你理解了整个流程再替换成自己采集的真实数据逻辑完全一样。# 文件路径sentiment_analysis/analysis.py import pandas as pd comments [ # 曹云金早期评论 (曹云金, 2015-2019, 他在台上表演的时候确实很卖力气包袱也响。), (曹云金, 2015-2019, 基本功挺扎实听他的相声很舒服。), (曹云金, 2015-2019, 那几年还经常在电视上看到他挺活跃的。), (曹云金, 2015-2019, 当初确实很有观众缘现场效果也好。), (曹云金, 2015-2019, 作品质量还不错就是后续曝光突然少了。), (曹云金, 2015-2019, 风格比较冲有些人可能不太喜欢。), (曹云金, 2015-2019, 业务能力没得说但个人争议也挺多。), (曹云金, 2015-2019, 那时候的相声专场票还挺难买的。), (曹云金, 2015-2019, 不喜欢他的风格感觉太闹腾。), (曹云金, 2015-2019, 印象里他挺能说的反应很快。), # 曹云金近期评论 (曹云金, 2020-2024, 最近很少看到他了热度明显不如以前。), (曹云金, 2020-2024, 偶尔刷到以前的剪辑还是有点可惜的。), (曹云金, 2020-2024, 新作品好像也没怎么出圈。), (曹云金, 2020-2024, 直播聊天的状态倒是挺放松的。), (曹云金, 2020-2024, 现在看到相关新闻更多是吃瓜心态。), (曹云金, 2020-2024, 感觉他这几年一直没找到特别好的舞台。), (曹云金, 2020-2024, 有些人觉得他离开之后就走下坡路了。), (曹云金, 2020-2024, 我觉得他能力还在只是缺少机会。), (曹云金, 2020-2024, 网上对他的评价分化挺严重的。), (曹云金, 2020-2024, 不太关注他近期的动态了。), # 郭麒麟早期评论 (郭麒麟, 2015-2019, 年纪不大但台风很稳一点都不怯场。), (郭麒麟, 2015-2019, 家教好说话也有分寸。), (郭麒麟, 2015-2019, 那会儿就觉得他以后能起来。), (郭麒麟, 2015-2019, 相声说得有模有样观众也挺买账。), (郭麒麟, 2015-2019, 跟着父亲演出学习机会确实多。), (郭麒麟, 2015-2019, 进步很明显一场比一场放松。), (郭麒麟, 2015-2019, 有传统文化底子又不像老派那么刻板。), (郭麒麟, 2015-2019, 综艺感也不错挺圈粉的。), (郭麒麟, 2015-2019, 台词功底好反应也快。), (郭麒麟, 2015-2019, 作为星二代他算是比较低调的了。), # 郭麒麟近期评论 (郭麒麟, 2020-2024, 现在影视剧资源越来越好了。), (郭麒麟, 2020-2024, 演戏有自己的风格也肯下功夫。), (郭麒麟, 2020-2024, 情商高采访的时候回答很得体。), (郭麒麟, 2020-2024, 从相声跨界到影视完成了挺不错的转型。), (郭麒麟, 2020-2024, 观众对他的认可度一直挺高的。), (郭麒麟, 2020-2024, 综艺和剧集两头都有曝光发展很稳。), (郭麒麟, 2020-2024, 喜欢他的人很多路人缘也好。), (郭麒麟, 2020-2024, 感觉他现在已经是多栖艺人了。), (郭麒麟, 2020-2024, 口碑整体不错偶尔也有争议但不算大。), (郭麒麟, 2020-2024, 希望他能继续出好作品。), ] df pd.DataFrame(comments, columns[person, period, text]) print(df.shape) print(df.head())运行上面这段代码你会看到数据集共40条评论字段分别为person、period、text。这只是最初始的数据后面还要做清洗和增强。4. 数据清洗与中文分词4.1 清洗函数真实网络评论里经常混入 URL、用户名、#话题#、表情符号、多余空格等噪声。如果不清理分词和情感分析都会受到影响。下面定义clean_text()函数使用正则表达式完成初步清洗。你可以根据自己数据的特点继续扩展比如去除 HTML 标签、连续标点等。import re def clean_text(text): # 去除URL text re.sub(rhttp[s]?://\S, , text) # 去除用户名 text re.sub(r\w, , text) # 去除#话题# text re.sub(r#.?#, , text) # 去除emoji和一些特殊符号 text re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9。、], , text) # 合并连续空格 text re.sub(r\s, , text).strip() return text df[clean_text] df[text].apply(clean_text) print(df[[text, clean_text]].head())这里需要注意正则表达式的范围[^\u4e00-\u9fa5a-zA-Z0-9。、]会保留中文字符、英文字母、数字和常见中文标点其余字符全部删掉。表情符号、特殊符号都会被清除。清洗之后每条评论变成更规整的文本便于后续处理。4.2 jieba 分词与停用词过滤分词是指把句子切成词语序列。jieba.cut(text)会生成一个生成器我们把它转换成列表并过滤掉空字符串和停用词。停用词是指“的、了、和、是、在、我、你、他”这类没有明显实际含义但出现频率很高的词。如果不剔除词频统计会被这些词干扰。import jieba # 简单停用词表实际项目可以扩充 stopwords set([ 的, 了, 和, 是, 在, 我, 你, 他, 她, 它, 也, 都, 就, 但, 而, 或, 与, 及, 对, 把, 被, 让, 向, 从, 到, 这, 那, 有, 没, 不, 很, 太, 更, 最, 还, 又, 再, 就, 才, 等, ]) def cut_words(text): words jieba.lcut(text) return [w for w in words if w.strip() and w not in stopwords and len(w) 1] df[words] df[clean_text].apply(cut_words) print(df[words].head())这里设置了len(w) 1目的是过滤单字词。因为单字词在情感判断和词频统计中意义不大比如“好”“坏”虽然有意义但单字会造成特征过于离散。当然如果你想更仔细地分析也可以保留单字词按实际需求调整。4.3 词频统计现在把所有人物的分词结果汇总统计高频词。这样可以快速看到网友讨论中最常出现的关键词。from collections import Counter all_words [] for words in df[words]: all_words.extend(words) word_counter Counter(all_words) top_words word_counter.most_common(15) print(top_words)输出结果是一个列表每一项是(词, 次数)。比如可能出现(观众, 3)、(风格, 2)这样的组合。这个结果可以帮我们理解原始数据的主题分布。5. 情感分析与分组对比5.1 使用 SnowNLP 计算情感得分对每一条清洗后的评论调用SnowNLP(text).sentiments得到0-1之间的情感倾向分数。from snownlp import SnowNLP def sentiment_score(text): if not text or not text.strip(): return 0.5 return SnowNLP(text).sentiments df[score] df[clean_text].apply(sentiment_score) print(df[[person, period, clean_text, score]].head())注意SnowNLP 的情感模型是基于电商评论等语料训练的对口语化、弹幕风格的文本判断精度有限。它更适合观察整体趋势而不是对单条评论做精确判断。后续如果要提升精度可以考虑用自定义语料重新训练模型或者引入更专业的预训练模型。5.2 按人物和时间段分组聚合我们把数据按照person和period两个维度分组计算每个分组的情感均值、评论数量等统计量。这样就能看出两个人物在不同时间段的情感变化。grouped df.groupby([person, period])[score].agg([mean, count, std]) grouped grouped.round(3) print(grouped)预期输出类似mean count std person period 曹云金 2015-2019 0.612 10 0.121 2020-2024 0.437 10 0.093 郭麒麟 2015-2019 0.682 10 0.097 2020-2024 0.718 10 0.102这里的数值是我的模拟数据跑出来的结果你实际运行时可能因为分词器版本不同有细微差别但整体趋势是一致的曹云金的情感均分从早期到近段时间有所下降郭麒麟则保持相对稳定偏正面的状态。需要注意的是这里只是演示情感分析的方法不构成对任何人的定论。真实舆情分析需要更多样本、更严格的采样和更科学的模型。6. 可视化对比6.1 情感得分趋势折线图使用 Matplotlib 把上面的分组结果画成折线图横轴是时间段纵轴是情感均分。import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei, Arial Unicode MS] plt.rcParams[axes.unicode_minus] False pivot df.pivot_table(indexperiod, columnsperson, valuesscore, aggfuncmean) pivot.plot(kindline, markero, figsize(8, 5)) plt.title(网络评论情感得分变化趋势) plt.ylabel(情感得分) plt.xticks(range(len(pivot.index)), pivot.index) plt.grid(True, linestyle--, alpha0.6) plt.tight_layout() plt.savefig(sentiment_trend.png, dpi150) plt.show()plt.rcParams设置是为了解决 Matplotlib 中文乱码问题。SimHei在 Windows 上通常可用Mac 上可以用Arial Unicode MSLinux 上可能需要安装中文字体或使用其他字体名称。如果你在运行时报错找不到字体也可以把上面配置改为plt.rcParams[font.sans-serif] [WenQuanYi Micro Hei, SimHei, Microsoft YaHei]6.2 高频词条形图除了情感趋势高频词条形图也能提供很多信息。它可以看出两个人物各自被讨论的关键词差异。def plot_top_words(word_data, title, top_n10): counter Counter(word_data) top counter.most_common(top_n) words [w for w, _ in top][::-1] counts [c for _, c in top][::-1] plt.figure(figsize(8, 5)) plt.barh(words, counts) plt.xlabel(出现次数) plt.title(title) plt.tight_layout() plt.show() cao_words [] guo_words [] for _, row in df.iterrows(): if row[person] 曹云金: cao_words.extend(row[words]) else: guo_words.extend(row[words]) plot_top_words(cao_words, 曹云金相关评论高频词) plot_top_words(guo_words, 郭麒麟相关评论高频词)从两张图中可以直观地看到曹云金相关评论里“表演、风格、能力、争议”等词出现频率较高郭麒麟相关评论里“作品、转型、综艺、情商”等词更常见。当然这是基于模拟数据真实数据需要结合具体业务解读。7. 真实环境中的数据获取思路模拟数据可以帮你跑通分析流程但如果要分析真实网络舆论还需要解决数据获取问题。下面给出一种常见的微博移动端数据采集思路仅用于学习研究。7.1 请求接口示例以 m.weibo.cn 的搜索接口为例程序需要携带登录后的 Cookie 才能获取完整数据。Cookie 可以从浏览器开发者工具中复制但要注意有效期并且不要在公共环境泄露。import requests def fetch_weibo_search(keyword, page1): url https://m.weibo.cn/api/container/getIndex headers { User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148, Cookie: 你的Cookie } params { containerid: f100103type1q{keyword}, page_type: searchall, page: page } resp requests.get(url, headersheaders, paramsparams, timeout10) return resp.json()拿到 JSON 数据后需要解析其中的cards字段。每条评论可能嵌套在card_group或mblog里具体字段名会随平台版本变化。通常是这样的思路def parse_cards(data): result [] cards data.get(data, {}).get(cards, []) for card in cards: if card.get(card_type) ! 9: continue mblog card.get(mblog, {}) text mblog.get(text, ) created_at mblog.get(created_at, ) result.append({text: text, time: created_at}) return result这里有一点需要强调不同平台的反爬策略不同微博会校验 Cookie、请求频率、设备指纹等信息。实际爬取时除了控制速度还必须遵守平台 robots 协议和用户协议只采集公开数据不做商业滥用不恶意攻击接口。建议在测试环境下小规模验证。7.2 数据存储采集到评论后最好保存为 CSV 或 Parquet 文件便于后续分析。Pandas 提供了最简单的方式df pd.DataFrame(comment_list) df.to_csv(data/comments.csv, indexFalse, encodingutf-8-sig)utf-8-sig编码可以避免 Excel 打开 CSV 时中文乱码。8. 常见问题与排查思路你在运行过程中可能会遇到下面这些问题我整理成表格方便对照排查。问题现象常见原因解决思路运行jieba.lcut时报错分词库版本过旧升级 jiebapip install -U jiebaSnowNLP 评分几乎都集中在 0.4-0.6模型语料与文本风格不匹配增加自定义情感词典或使用更大规模预训练模型Matplotlib 中文显示为方块系统缺少中文字体在rcParams中指定已安装的中文字体名称从微博接口获取的数据为空Cookie 失效或参数错误重新登录并复制最新 Cookie核对containeridCSV 文件用 Excel 打开乱码编码用了utf-8保存时使用encodingutf-8-sig评论中广告、无关内容太多数据清洗规则不够强增加关键词过滤、垃圾文本识别规则8.1 一个典型的错误示例很多人刚开始会把“情感得分低”直接理解为“评价差”其实这是不对的。SnowNLP的情感分数基于概率模型0.4只代表模型判断负面倾向略高于正面不能代表真实态度。而且网络评论里存在反讽、夸张、抽象话术模型很难完全识别。所以在最终结论里要结合高频词、上下文和样本量一起解释。9. 最佳实践与工程建议9.1 合规性与数据安全做舆情分析时首先要确保数据来源合法。公开渠道的数据并不等于可以随意采集尤其是涉及个人用户信息时要注意脱敏处理。不要存储用户的昵称、ID、地理位置等可识别信息只用文本内容做分析。发布结果时也不要展示原始评论避免给用户带来困扰。9.2 样本量与抽样公平性如果只采集某一天的评论样本量小且时间集中结论容易有偏差。建议按小时或天均匀采样覆盖多个时间段保证两个对比对象在时间维度上的数据量基本一致。像本文的模拟数据只有 40 条只适合学习流程不适合做真实舆情判断。9.3 增强情感分析效果的技巧SnowNLP 虽然方便但默认模型对网络新词的识别能力一般。你可以考虑以下优化方案在分词前先做文本规范化例如把“yyds”“绝绝子”等网络热词替换成“厉害”“非常好”。使用自定义词典把人物名、作品名、专有名词提前加入 jieba。增加否定词处理例如“不好”和“不 好”要区分。如果数据量足够大可以基于标注数据微调 BERT 等预训练模型准确率会明显提升。9.4 生产环境注意事项如果这个分析流程要部署成定时任务建议把代码封装成模块class SentimentAnalyzer: def __init__(self): self.stopwords set() self.load_stopwords() def load_stopwords(self): ... def preprocess(self, text): ... def analyze(self, comments): ...同时加入异常处理机制比如请求失败重试、数据库写入失败报警等。日志记录也很重要至少要把每次运行的时间、数据量、异常信息保存下来方便后续排查。10. 总结与学习路线通过这篇文章你亲手实现了一个完整的中文网络评论情感分析流程从模拟数据生成、文本清洗、jieba 分词、SnowNLP 情感打分到 pandas 分组聚合和 Matplotlib 可视化。这里面有几个关键点值得记住情感分析不是万能的它更适合看趋势不适合对单条文本下结论。数据清洗决定结果质量正则表达式和停用词表需要根据数据反复调整。可视化可以帮你快速发现问题但最终解释必须结合样本量、采样时间和模型特点。接下来你可以继续学习这几个方向用真实微博数据替换模拟数据跑通采集流程。学习wordcloud库生成词云让展示效果更直观。研究更深度的情感分类模型比如transformers库里的中文预训练模型。把分析流程封装成 Web 服务使用 Flask 或 FastAPI 提供 API 接口。技术学习的路径很长建议从一个最小可用案例开始逐步增加数据量和模型复杂度。如果你在运行本文代码时遇到问题可以对照常见问题表格逐项排查重点检查环境依赖和中文字体配置。希望这份实战笔记能帮你少走一些弯路。