简介:这是一套面向Python初学者与NLP入门者的情感分析系统源码,围绕文本情感倾向判断这一典型任务,整合了数据清洗、模型训练与情感分类的完整流程,适合用于课程设计、毕业项目或自然语言处理练手场景。压缩包共34个文件,以22个py脚本为核心,辅以7个txt语料与词表、xlsx验证数据、ini配置文件及md说明文档,整体约1.6MB,结构紧凑便于阅读与二次开发。系统通过clearMsg等模块去除非人工对话、HTML标签与表情符号,借助SnowNLP训练可区分正向、负向与中性的模型,并以Flask蓝图方式暴露Web接口,返回情感标签、情感值与分类方式。目前已有77人学习,读者可从中获得一套可直接运行的情感分析工程范例,理解语料准备、模型训练到接口部署的完整链路,并参考其模块划分与配置方式快速搭建自己的文本分析服务。
1. 拆开这份 Python 情感分析源码:从 53k 条对话到 Flask 接口的完整链路
拿到一个标注为「情感分析系统」的压缩包,多数人第一反应是打开train.py看模型结构,结果翻了三层目录才发现真正的入口在start_flask.py。这份源码的目录结构就属于这种「不按套路出牌」的类型:根目录下既有deploy.py、train.py这类脚本,又有flask_module、emotion_blueprint、config_blueprint三个包,数据文件散落在train data目录里,docs下还塞了一份 SnowNLP 的需求说明。它解决的核心问题很明确——把 53k 条客服对话原始语料清洗成训练集,用 SnowNLP 训练出能区分正向、负向、中性的情感分类模型,再通过 Flask 暴露成 HTTP 接口。适合谁用?如果你正在做舆情监控、客服质检、评论分析这类需要快速搭一个可跑通的情感分类原型,这份源码的完整链路(数据清洗 → 模型训练 → Web 接口)比单独一个模型文件更有参考价值。但要注意,它不是开箱即用的 SaaS,需要你手动跑通数据准备和训练流程。
2. 数据清洗与语料构建:从 53kf 原始对话到训练集
2.1 为什么清洗环节决定了模型上限
情感分析系统的效果天花板往往不在模型结构,而在语料质量。这份源码的train data目录里放了neg_53kf.txt、pos_53kf.txt两个原始语料文件,从命名看是来自 53kf 客服系统的对话记录。原始对话里混杂了大量非人工内容:系统自动回复、HTML 标签、表情符号、重复的问候语。如果直接拿这些数据去训练,模型会把「您好,客服正在为您接入」这类中性系统话术学成某种情感倾向,导致线上误判。
clearMsg.py就是干这个的。它做的事情比想象中细:去除非人工对话(通过关键词匹配过滤系统消息)、剥离 HTML 标签、清理表情符号和特殊字符。我一般会先看一眼清洗前后的样本对比,确认过滤规则没有误伤正常文本。常见做法是在清洗脚本里加一个--dry-run参数,只打印被过滤的句子而不实际写入文件,方便快速验证规则。
2.2 清洗脚本的核心逻辑与参数调整
clearMsg.py的清洗流程可以拆成三步:读取原始文件、逐行应用过滤规则、写入清洗后的语料。下面是一个典型的清洗函数结构,我按源码逻辑还原了关键部分:
import re def clean_text(line): # 去除 HTML 标签 line = re.sub(r'<[^>]+>', '', line) # 去除表情符号(常见 Unicode 范围) line = re.sub(r'[\U0001F600-\U0001F64F\U0001F300-\U0001F5FF]', '', line) # 去除系统自动回复关键词 system_keywords = ['客服正在为您接入', '请稍后', '系统消息', '自动回复'] for kw in system_keywords: if kw in line: return None # 去除多余空白 line = re.sub(r'\s+', ' ', line).strip() return line if len(line) > 2 else None def process_file(input_path, output_path): with open(input_path, 'r', encoding='utf-8') as f: lines = f.readlines() cleaned = [] for line in lines: result = clean_text(line) if result: cleaned.append(result) with open(output_path, 'w', encoding='utf-8') as f: f.write('\n'.join(cleaned)) print(f'清洗完成:{len(lines)} 行 -> {len(cleaned)} 行')逻辑说明:clean_text返回None表示该行应被丢弃,process_file负责批量处理。参数方面,system_keywords列表需要根据实际语料调整——53kf 的系统消息模板可能不止这几个,建议先抽样 200 行人工看一眼。len(line) > 2这个阈值是过滤掉「嗯」「哦」这类无意义短句,如果语料里短句本身有情感价值(比如「差」),可以调低到 1。
2.3 训练数据准备与正负样本词表
清洗完原始对话后,prepareTrainData.py负责把数据整理成 SnowNLP 训练所需的格式。SnowNLP 的训练接口需要正负样本分开传入,源码里pos_53kf.txt和neg_53kf.txt就是清洗后的正负语料。另外还有pos_words.txt、neg_words.txt两个词表文件,以及pos_default.txt、neg_default.txt默认词表——这些是 SnowNLP 自带的补充词典,用于在训练数据不足时兜底。
这里有个容易翻车的点:prepareTrainData.py可能会对正负样本做采样平衡。如果正负样本比例悬殊(比如正样本 40k、负样本 13k),不处理会导致模型偏向多数类。常见做法是随机下采样多数类到与少数类相当,或者用imbalanced-learn做过采样。源码里具体怎么处理的,需要打开prepareTrainData.py确认——我建议先跑一遍看输出日志里的样本数量变化。
3. 模型训练与 SnowNLP 调参:train.py 里到底做了什么
3.1 SnowNLP 的训练机制与适用边界
SnowNLP 是一个轻量级中文 NLP 库,它的情感分析模块基于朴素贝叶斯分类器。train.py的核心逻辑是:加载清洗后的正负语料,调用 SnowNLP 的训练接口,把模型持久化到本地。SnowNLP 的训练接口设计比较「朴素」——它不接受自定义特征工程,你只能喂正负样本,它内部用字符级 n-gram 做特征。这意味着两件事:第一,训练速度很快,53k 条数据在普通笔记本上几分钟能跑完;第二,模型上限受限于 n-gram 特征,对反讽、双重否定这类复杂语义的捕捉能力有限。
所以选型理由要讲清楚:如果你需要快速验证一个情感分析原型,SnowNLP 够用;如果要做生产级的情感分类,建议在train.py的基础上换成 sklearn 的TfidfVectorizer + LogisticRegression或 fine-tune 一个 BERT 小模型。源码里test_snownlp.py就是用来快速验证 SnowNLP 默认模型效果的脚本,可以先跑它看看基线表现。
3.2 训练脚本的执行流程与关键参数
train.py的执行流程大致如下:读取正负语料 → 构建 SnowNLP 训练数据 → 训练模型 → 保存模型文件。下面是一个典型的训练脚本骨架:
from snownlp import sentiment import os # 正负语料路径 pos_path = os.path.join('train data', 'pos_53kf.txt') neg_path = os.path.join('train data', 'neg_53kf.txt') # SnowNLP 训练接口:分别指定正负语料 sentiment.train(neg_path, pos_path) # 保存训练好的模型 sentiment.save('sentiment.marshal') print('模型训练完成并已保存')逻辑说明:sentiment.train(neg_path, pos_path)的参数顺序是负样本在前、正样本在后,这个顺序不能反——反了会导致模型把正负标签搞混。sentiment.save保存的是序列化后的模型文件,默认路径在 SnowNLP 包目录下,源码里可能指定了自定义路径。参数方面,SnowNLP 没有暴露学习率、迭代次数这类超参数,你能控制的就是语料质量和正负样本比例。如果训练完发现模型在验证集上表现差,优先检查语料清洗是否干净,而不是调模型参数。
3.3 验证集与效果评估
verify.xlsx和verify.py、verify_online.py是验证环节的文件。verify.xlsx里应该存了人工标注的验证样本,verify.py负责跑离线验证,verify_online.py可能用于线上接口的抽样验证。我一般会关注三个指标:准确率、正负样本各自的召回率、以及中性样本的误判率。SnowNLP 原生只输出一个 0 到 1 之间的情感值,源码里emotionclassify.py应该做了阈值切分——比如大于 0.6 判正向、小于 0.4 判负向、中间判中性。这个阈值需要根据验证集调,不能拍脑袋定。
提示:验证集一定要和训练集分开。如果
verify.xlsx里的样本混入了训练语料,评估结果会虚高,上线后翻车。
4. Flask 接口与模块化拆解:从 emotion_blueprint 到 start_flask
4.1 蓝图划分与请求处理链路
这份源码的 Web 层用了 Flask 的 Blueprint 机制,拆成emotion_blueprint和config_blueprint两个模块。emotion_blueprint负责情感分析接口,config_blueprint负责配置管理。start_flask.py是启动入口,flask_module下还有flask_config.py、flask_log.py等辅助模块。这种拆法比把所有路由塞在一个app.py里清晰得多,也方便后续扩展。
emotion_blueprint/emotionclassify.py是核心分类逻辑,emotion_func.py封装了情感值到标签的转换函数,result_json.py定义了返回的 JSON 结构。一个典型的请求链路是:客户端 POST 文本 → Flask 路由接收 → 调用emotionclassify分类 → 返回 JSON。下面是一个接口调用的示例:
import requests url = 'http://127.0.0.1:5000/emotion/classify' payload = {'text': '这个产品用起来很顺手,推荐购买'} resp = requests.post(url, json=payload) print(resp.json()) # 预期返回:{'label': 'positive', 'score': 0.87, 'method': 'snownlp'}逻辑说明:请求体用 JSON 格式传文本,返回里包含情感标签、情感值和分类方式。method字段可能是为了区分 SnowNLP 分类和规则分类——源码里textSimilarity.py和utils.py可能提供了基于相似度或规则的兜底方案。参数方面,如果接口返回 500,先看flask_log.py配置的日志路径,日志里会记录异常堆栈。
4.2 配置管理与部署脚本
config.ini和config.py负责配置管理,deploy.py是部署脚本。config.ini里通常放数据库连接、模型路径、端口号这类可变参数。我一般会把模型路径和日志级别放在配置文件里,而不是硬编码在代码中。deploy.py可能做了环境检查、依赖安装、启动 Flask 这几件事。常见做法是用gunicorn或uwsgi替代 Flask 自带的开发服务器,但源码里start_flask.py大概率是app.run()直接启动——开发环境够用,生产环境需要换。
注意:Flask 开发服务器默认单线程,并发请求会排队。如果要做压力测试,先换成 gunicorn 多 worker 模式,否则测出来的 QPS 没有参考价值。
4.3 接口测试与返回结构
verify_online.py应该是用来测试线上接口的脚本。我一般会用它做两件事:批量跑验证集样本,统计接口返回的准确率;以及测试边界情况,比如空文本、超长文本、特殊字符文本。返回结构里score字段是连续值,label是离散标签,method标识分类方式。如果method返回了非预期值,说明走了兜底逻辑,需要检查 SnowNLP 模型是否正确加载。
5. 避坑与排查:这份源码跑起来容易卡在哪
5.1 编码问题导致语料读取失败
现象:运行clearMsg.py或train.py时报UnicodeDecodeError,提示gbk codec can't decode byte。原因:53kf 导出的原始对话文件可能是 GBK 编码,而脚本默认用 UTF-8 读取。解决:在open()里显式指定encoding='gbk',或者先用iconv转成 UTF-8。我一般会先用file -i命令确认文件编码,再决定读取方式。
5.2 SnowNLP 模型保存路径混乱
现象:训练完模型后,接口返回的情感值始终是 0.5 左右,等于没分类。原因:sentiment.save()保存的模型文件路径和emotionclassify.py加载的路径不一致,导致接口加载的是 SnowNLP 自带的默认模型。解决:在config.ini里统一模型路径,训练脚本和分类脚本都从配置读取。检查方法是打印sentiment模块的data_path变量,确认实际加载的文件。
5.3 正负样本标签反转
现象:模型把明显的正向文本判成负向,负向判成正向。原因:sentiment.train(neg_path, pos_path)的参数顺序写反了,或者pos_53kf.txt和neg_53kf.txt的内容本身标反了。解决:先人工抽查两个文件各 20 行,确认内容与文件名一致;再检查train.py里的参数顺序。这个坑很隐蔽,因为训练过程不会报错,只有验证时才会发现。
5.4 Flask 接口跨域与请求体格式
现象:前端调用接口时报 CORS 错误,或者后端收不到text字段。原因:Flask 默认没有开启跨域,且request.json要求Content-Type: application/json。解决:安装flask-cors并初始化,或者手动加Access-Control-Allow-Origin响应头。请求体格式方面,如果用requests.post(url, data=payload)而不是json=payload,后端解析会失败。
5.5 依赖版本不兼容
现象:pip install -r requirements.txt后运行报ImportError或AttributeError。原因:SnowNLP 和 Flask 的版本更新可能导致接口变化,源码里readme.txt或README.md可能没锁版本号。解决:先看readme.txt里有没有指定版本,没有的话尝试snownlp==0.12.3和flask==2.0.3这两个较稳定的版本。如果还不行,用pip list对比源码里 import 的模块,逐个排查。
6. 进阶技巧:用 textSimilarity 做兜底与阈值调优
textSimilarity.py这个文件容易被忽略,但它其实提供了一个有价值的兜底思路。SnowNLP 对短文本和网络新词的判断经常不准,比如「绝绝子」在旧模型里可能被判成负向。textSimilarity.py大概率实现了基于编辑距离或余弦相似度的文本匹配——当 SnowNLP 的情感值落在 0.4 到 0.6 的模糊区间时,用相似度匹配去查一个人工维护的情感词典,命中则覆盖 SnowNLP 的结果。
我一般会这样用:先跑一遍验证集,把 SnowNLP 判错且情感值在 0.4 到 0.6 之间的样本挑出来,人工标注后加入pos_words.txt或neg_words.txt。然后调整emotion_func.py里的阈值逻辑,比如把正向阈值从 0.6 降到 0.55,观察召回率和准确率的变化。下面是一个阈值调优的示例代码:
def classify_with_threshold(score, pos_th=0.6, neg_th=0.4): if score >= pos_th: return 'positive' elif score <= neg_th: return 'negative' else: # 模糊区间走相似度兜底 return similarity_fallback(text) # 调优时遍历不同阈值组合 for pos_th in [0.55, 0.6, 0.65]: for neg_th in [0.35, 0.4, 0.45]: acc = evaluate(verify_data, pos_th, neg_th) print(f'pos_th={pos_th}, neg_th={neg_th}, acc={acc:.3f}')逻辑说明:classify_with_threshold把情感值映射到标签,模糊区间交给similarity_fallback。调优时用网格搜索找最佳阈值组合,评估函数evaluate遍历验证集计算准确率。参数方面,pos_th和neg_th的搜索范围建议在 0.5 到 0.7 和 0.3 到 0.5 之间,步长 0.05。如果验证集样本少于 500 条,阈值调优容易过拟合,建议至少 1000 条以上再调。
还有一个实用技巧:utils.py和log.py里可能有日志和工具函数,训练和推理时把关键中间结果(比如每条样本的情感值、最终标签、是否走了兜底)打到日志里。上线后如果发现某类文本误判率高,直接捞日志分析,比重新跑一遍训练快得多。从那以后我每次接情感分析项目,都强制先把日志和验证集跑通再调模型,希望帮到你。
本文还有配套的精品资源,点击获取