简介:这是一套面向计算机、人工智能、通信等专业学生与教师的Python毕业设计项目,基于自然语言处理技术实现微博用户情感分析系统,可用于毕业设计、课程大作业或期末课程设计,也适合具备一定基础的学习者进阶参考。压缩包共734个文件,约85MB,涵盖131个Python源码文件、166个JavaScript脚本、86个HTML页面、35个CSS样式表及22个Vue组件,另含模型序列化文件、多语言配置、数据库与静态资源,构成完整的前后端工程结构。项目已通过调试测试,答辩评审分达98分,代码可直接运行。读者可获得完整源码、文档说明与目录组织方式,理解情感分析从数据采集、文本预处理到模型推理的实现链路,并在此基础上修改调整,扩展出不同功能。目前已有244人学习下载,适合需要完整毕设方案与可运行代码的读者参考。
1. 微博情感分析系统:从一条“带情绪的转发”说起
一条微博发出去,评论区三小时就能吵翻天。做运营的想知道这波舆情是正面还是负面,做产品的想知道用户对新功能是夸还是骂,做毕业设计的同学则想知道:我能不能用 Python 和自然语言处理,把这几万条微博自动分出情绪来。这个标题讲的就是这件事——基于 NLP 的微博用户情感分析系统,带源码和文档说明。它解决的核心问题是:把非结构化的中文短文本,自动映射到正面、负面、中性三类标签上,并且能按用户、按时间段做聚合统计。适合谁?适合正在做课程设计或毕业设计的学生,也适合想快速搭一个中文情感分析原型的初级工程师。难点不在模型多深,而在中文微博的噪声:表情符号、网络缩写、反讽、@提及、话题标签,这些才是真正让模型翻车的地方。
2. 微博文本到底怎么变成模型能吃的向量
2.1 中文短文本预处理的四个关键动作
微博文本和新闻语料完全不同。一条典型微博可能是“今天这个新版本真的绝了[doge]//@某某:又卡又慢”。如果直接丢给模型,标点、表情、转发链全是噪声。我一般按四步走:去 URL 和 @提及、保留表情符号的语义映射、分词、去停用词。注意,表情符号不要直接删,[doge]和[泪]携带的情绪信息比很多形容词都强。
分词工具选型上,jieba是最常见的选择,原因是它对网络新词的支持可以通过自定义词典补强。下面是一个可复现的预处理函数:
import re import jieba # 自定义词典:补充微博高频网络词 jieba.load_userdict("weibo_dict.txt") # 表情符号到情感词的映射表 EMOJI_MAP = { "[doge]": "调侃", "[泪]": "悲伤", "[怒]": "愤怒", "[心]": "喜欢", "[good]": "赞同", "[弱]": "失望" } def clean_weibo(text): # 1. 去掉 URL text = re.sub(r'http[s]?://\S+', '', text) # 2. 去掉 @提及,但保留转发内容 text = re.sub(r'@[\w\u4e00-\u9fa5-]+', '', text) # 3. 表情符号替换为情感词 for emoji, word in EMOJI_MAP.items(): text = text.replace(emoji, word) # 4. 去掉话题标签的 # 号但保留内容 text = re.sub(r'#(.+?)#', r'\1', text) # 5. 去掉多余空白和特殊符号 text = re.sub(r'[^\u4e00-\u9fa5a-zA-Z0-9]', ' ', text) return text.strip() def tokenize(text): text = clean_weibo(text) words = jieba.lcut(text) # 过滤单字和空白 return [w for w in words if len(w) > 1 and w.strip()]逻辑说明:clean_weibo的顺序很重要。先去 URL 再去 @,否则 URL 里的@可能被误匹配。表情映射放在分词前,保证[doge]变成一个可分词的词。tokenize里过滤单字是因为微博里单字噪声太多,“的”“了”“吗”对情感判断几乎没有贡献。参数方面,weibo_dict.txt每行一个词,建议至少加入“绝绝子”“yyds”“破防”这类高频网络词,否则 jieba 会把它们切成无意义的碎片。
2.2 从词袋到词向量:三种方案的取舍
预处理完,下一步是表示。常见做法有三种:TF-IDF 词袋、Word2Vec 词向量、预训练模型微调。毕业设计里我建议先用 TF-IDF + 线性模型跑通基线,再考虑升级。原因很直接:TF-IDF 可解释性强,训练快,在 5 万条微博上几分钟就能出结果,而且文档里写清楚特征权重怎么来的,答辩时好讲。
TF-IDF 的核心参数是max_features和ngram_range。微博短文本建议max_features=5000,ngram_range=(1,2),因为“不 喜欢”和“不喜欢”在情感上是反的,二元组能捕捉这种否定搭配。下面是对应的向量化代码:
from sklearn.feature_extraction.text import TfidfVectorizer vectorizer = TfidfVectorizer( tokenizer=lambda x: x, # 传入已分好词的列表 preprocessor=None, max_features=5000, # 保留最高频的5000个特征 ngram_range=(1, 2), # 一元和二元词组 min_df=3, # 至少出现在3条微博中 max_df=0.8 # 出现在超过80%微博中的词丢弃 ) # X 是分词后的列表,如 [['今天','版本','绝了'], ...] X = vectorizer.fit_transform(tokenized_corpus)参数说明:min_df=3过滤掉只出现一两次的错别字和噪声词;max_df=0.8过滤掉“转发”“微博”这类几乎每条都有的词。tokenizer=lambda x: x是因为我们已经分好词了,直接透传。如果跳过预处理直接传原始文本,TfidfVectorizer默认按空格切分,中文会整句变成一个 token,效果直接崩掉。
2.3 标签怎么定:三类还是五类
微博情感分析最常见的标签体系是三类:正面、负面、中性。但实际标注时你会发现,大量微博是“中性偏负”或“中性偏正”。我的血泪经验是:毕业设计阶段就做三类,别贪多。五类标注一致性很难保证,两个人标同一批数据,Kappa 系数能低到 0.5 以下,模型再准也没意义。如果一定要细粒度,可以先用三类跑通,再在负面里用规则区分“愤怒”和“失望”,这样文档里也能写出层次。
3. 用 scikit-learn 搭一个能跑通的情感分类器
3.1 数据集划分与基线模型训练
假设你已经有了标注好的数据,格式是text,label,label 为 0/1/2。第一步是划分训练集和测试集,比例 8:2,并且要分层采样,保证三类比例一致。下面是一个完整的训练脚本:
import pandas as pd from sklearn.model_selection import train_test_split from sklearn.linear_model import LogisticRegression from sklearn.metrics import classification_report # 读取标注数据 df = pd.read_csv("weibo_labeled.csv") df['tokens'] = df['text'].apply(tokenize) # 分层划分,random_state 固定保证可复现 X_train, X_test, y_train, y_test = train_test_split( df['tokens'], df['label'], test_size=0.2, stratify=df['label'], random_state=42 ) # 向量化 vectorizer = TfidfVectorizer( tokenizer=lambda x: x, preprocessor=None, max_features=5000, ngram_range=(1, 2), min_df=3, max_df=0.8 ) X_train_vec = vectorizer.fit_transform(X_train) X_test_vec = vectorizer.transform(X_test) # 逻辑回归基线 clf = LogisticRegression( C=1.0, # 正则化强度,越小正则越强 max_iter=1000, # 迭代次数,防止不收敛 class_weight='balanced' # 类别不平衡时自动加权 ) clf.fit(X_train_vec, y_train) # 评估 y_pred = clf.predict(X_test_vec) print(classification_report(y_test, y_pred, target_names=['负面','中性','正面']))逻辑说明:stratify=df['label']是关键,微博情感数据往往负面偏多,不分层的话测试集可能全是负面。class_weight='balanced'自动按类别频率反比加权,解决类别不平衡。C=1.0是默认值,如果发现过拟合,降到 0.1;如果欠拟合,升到 10。max_iter=1000是因为默认 100 在 5000 维特征下经常不收敛,会报ConvergenceWarning。
3.2 评估指标怎么看:准确率会骗人
三类情感分析里,准确率是最容易骗人的指标。如果负面占 70%,模型全猜负面也有 70% 准确率。我一般看三个数:宏平均 F1、各类别召回率、混淆矩阵。宏平均 F1 对少数类敏感,能暴露模型是不是只会猜多数类。混淆矩阵能看出“正面”被误判成“中性”多,还是“负面”被误判成“正面”多——后者在舆情场景里是致命的。
下面这段代码输出混淆矩阵,建议直接放进文档说明里:
from sklearn.metrics import confusion_matrix import seaborn as sns import matplotlib.pyplot as plt cm = confusion_matrix(y_test, y_pred) sns.heatmap(cm, annot=True, fmt='d', xticklabels=['负面','中性','正面'], yticklabels=['负面','中性','正面']) plt.xlabel('预测') plt.ylabel('真实') plt.savefig('confusion_matrix.png', dpi=150)参数说明:fmt='d'保证显示整数而不是科学计数法。保存图片时dpi=150足够文档使用。如果发现“负面”被大量预测成“中性”,通常是中性样本里混入了弱负面表达,需要回头检查标注标准。
3.3 模型持久化与推理接口
训练完要把模型和向量器一起保存,否则每次推理都要重新 fit,浪费时间且结果不一致。用joblib保存:
import joblib joblib.dump(vectorizer, 'tfidf_vectorizer.pkl') joblib.dump(clf, 'lr_model.pkl') # 推理时加载 vectorizer = joblib.load('tfidf_vectorizer.pkl') clf = joblib.load('lr_model.pkl') def predict_sentiment(text): tokens = tokenize(text) vec = vectorizer.transform([tokens]) label = clf.predict(vec)[0] proba = clf.predict_proba(vec)[0] return {'label': int(label), 'confidence': float(max(proba))} # 测试 print(predict_sentiment("这个功能真的太好用了"))逻辑说明:transform而不是fit_transform,保证用训练时的词汇表。predict_proba返回置信度,低于 0.6 的可以标记为“待人工复核”,这在文档里是一个加分项。注意tokenize函数必须和训练时完全一致,否则特征对不上,预测结果会莫名其妙。
4. 避坑与排查:微博情感分析里最容易翻车的五件事
4.1 现象:模型在测试集上 F1 很高,一上真实微博就乱判
原因:训练数据来自某个时间段,网络新词和热点事件没覆盖。比如训练时没有“破防”这个词,模型把它当成普通动词。解决:在预处理阶段维护一个动态更新的自定义词典,每周从微博热搜里补充新词;同时在文档里写明模型适用时间范围,别让用户以为能分析任何时期的微博。
4.2 现象:分词结果里出现大量无意义单字组合
原因:jieba默认模式对网络缩写切分很差,“yyds”会被切成“y”“y”“d”“s”。解决:在自定义词典里加入缩写词,并且开启jieba.cut的HMM=True参数让隐马尔可夫模型辅助发现新词。如果还不行,直接在预处理阶段用正则把常见缩写替换成中文全称。
4.3 现象:训练时准确率 95%,测试时掉到 60%
原因:过拟合。特征维度 5000,样本只有几千条,逻辑回归也能过拟合。解决:降低max_features到 2000,增大正则化强度(C=0.1),或者改用朴素贝叶斯。另外检查是不是把标签泄漏进了特征——比如文本里包含“正面”这个词。
4.4 现象:ConvergenceWarning刷屏,模型训练不收敛
原因:max_iter太小,或者特征没有归一化。TF-IDF 本身已经归一化,但如果有极端值仍会导致不收敛。解决:把max_iter调到 2000,或者换SGDClassifier配合early_stopping。如果还不行,检查是否有全零行。
4.5 现象:表情符号被当成乱码丢弃
原因:读取 CSV 时编码不对,[doge]变成?。解决:统一用pd.read_csv(..., encoding='utf-8-sig'),保存时也用同样编码。如果数据来自爬虫,确保爬取时没有做encode('ascii', 'ignore')这种操作。
5. 把系统跑起来:从单机脚本到可演示的 Web 界面
5.1 用 Flask 包一个最小可用接口
毕业设计答辩时,光有脚本不够,最好有一个能点按钮的界面。用 Flask 写一个单页应用,前端一个输入框,后端调predict_sentiment。核心代码如下:
from flask import Flask, request, jsonify, render_template import joblib app = Flask(__name__) vectorizer = joblib.load('tfidf_vectorizer.pkl') clf = joblib.load('lr_model.pkl') @app.route('/') def index(): return render_template('index.html') # 一个简单的输入框和按钮 @app.route('/predict', methods=['POST']) def predict(): text = request.json.get('text', '') if not text: return jsonify({'error': 'empty input'}), 400 tokens = tokenize(text) vec = vectorizer.transform([tokens]) label = int(clf.predict(vec)[0]) proba = float(max(clf.predict_proba(vec)[0])) return jsonify({'label': label, 'confidence': round(proba, 3)}) if __name__ == '__main__': app.run(host='0.0.0.0', port=5000, debug=False)逻辑说明:debug=False在生产环境必须关掉,否则有安全风险。host='0.0.0.0'允许局域网访问,方便答辩时用手机演示。前端index.html只需要一个textarea和一个fetch请求,这里不展开。
5.2 批量分析微博数据并导出报表
单条预测只是演示,实际需求往往是分析几千条微博。下面是一个批量处理脚本,输出每个用户的情感分布:
import pandas as pd def batch_analyze(input_csv, output_csv): df = pd.read_csv(input_csv, encoding='utf-8-sig') results = [] for _, row in df.iterrows(): tokens = tokenize(row['text']) vec = vectorizer.transform([tokens]) label = int(clf.predict(vec)[0]) proba = float(max(clf.predict_proba(vec)[0])) results.append({ 'user': row['user'], 'text': row['text'], 'sentiment': ['负面','中性','正面'][label], 'confidence': round(proba, 3) }) out = pd.DataFrame(results) # 按用户聚合 summary = out.groupby('user')['sentiment'].value_counts().unstack(fill_value=0) summary.to_csv(output_csv, encoding='utf-8-sig') return summary summary = batch_analyze('weibo_raw.csv', 'user_sentiment_summary.csv') print(summary.head())参数说明:value_counts().unstack()把长表转成宽表,每个用户一行,三列分别是负面、中性、正面的计数。fill_value=0保证没有某类情感时显示 0 而不是 NaN。输出用utf-8-sig是为了 Excel 直接打开不乱码。
5.3 一个提升准确率的小技巧:规则兜底
模型不是万能的。对于包含“转发抽奖”“哈哈哈”这类明显模式的微博,直接用规则覆盖模型输出,准确率能提升 3 到 5 个百分点。规则写在模型前面:
POSITIVE_RULES = ['哈哈哈', '太棒了', '喜欢', '支持'] NEGATIVE_RULES = ['垃圾', '恶心', '失望', '投诉'] def rule_based(text): for w in POSITIVE_RULES: if w in text: return 2 # 正面 for w in NEGATIVE_RULES: if w in text: return 0 # 负面 return None # 交给模型 def predict_with_rules(text): rule_label = rule_based(text) if rule_label is not None: return {'label': rule_label, 'confidence': 1.0, 'source': 'rule'} return predict_sentiment(text)逻辑说明:规则优先的好处是可解释,坏处是可能误伤反讽。比如“哈哈哈,这功能真垃圾”会被规则判成正面。所以规则词表要尽量选情感明确的词,并且定期审查误判案例。我一般把规则命中率控制在 10% 以内,太高说明模型太弱。
5.4 文档说明里必须写清楚的三个边界
第一,模型在训练数据分布外的表现会下降,尤其是反讽和隐喻。第二,置信度低于 0.6 的结果建议人工复核,不要直接用于决策。第三,情感分析不等于舆情分析,它只判断文本极性,不判断事件严重程度。把这三条写进文档,答辩时老师会觉得你清楚系统的边界,而不是盲目吹嘘。
最后说一个我自己的习惯:每次跑完批量分析,我会随机抽 20 条低置信度的结果人工看一眼,把误判的典型例子记下来,下一轮迭代时优先补进训练数据。这个习惯比调参管用得多。希望帮到你。
本文还有配套的精品资源,点击获取