简介:一份面向计算机专业毕业设计场景的微博情感分析系统完整项目,基于Python实现,综合运用SVM、朴素贝叶斯与AdaBoost集成学习完成情感分类,适合需要参考完整框架或直接二次开发的学生开发者。项目涵盖微博数据获取、文本预处理、特征提取、分类器训练与评估等环节,提供SVM、朴素贝叶斯、AdaBoost等多套独立实现,并附训练语料、情感词典、中间词表、预训练模型及研究论文文档,可支撑从数据到结果的全流程验证。压缩包共71个文件,以Python脚本(31个)、文本数据(13个)、NumPy模型(15个)为主,另有4个模型文件与docx说明文档,整体约6.25MB,目录按功能模块划分,便于查阅。该资源已有6975人学习,对于正在完成情感分析类课题的本科生或研究生,既是代码参考,也可作为实验比对与答辩素材的有力补充。
1. 基于微博情感分析系统:毕设真正做到什么程度才算合格
“基于微博情感分析系统”听起来像是一个分类模型项目,但动手之后你会发现,真正的复杂度不在模型,而在整个链路。所谓合格,不是调一个准确率90%的分类器,而是把数据采集、清洗、标注、建模、可视化串成一条能自圆其说的完整通路。系统解决的是量化舆情倾向的问题:把一条条微博文本变成可统计、可回溯、可展示的正向/负向/中性信号,支撑事件趋势、用户画像或话题情绪演变分析。这套东西适合计算机、软件工程、大数据专业的学生做毕设,也适合刚入行舆情分析方向的人拿来练手。多数人翻车的地方不在算法,而在“数据进不来、清洗不干净、标签对不上”。
2. 先定架构再动手:三层系统设计与技术选型理由
2.1 技术选型:为什么是 Python + Flask + SQLite 的组合
毕设系统最重要的原则是“自己讲得清楚”,不是你用了多前沿的框架。Python 在这个场景里几乎没有替代品:jieba 做分词,SnowNLP 和 scikit-learn 做情感判定,pandas 做清洗,Flask 起本地 Web 服务,ECharts 出图,每一环都有成熟库,答辩时每个组件的职责都可以一句话说明白。
数据库我推荐 SQLite 而不是 MySQL。常见误区是觉得“系统”必须配一个正经数据库,但毕设阶段数据量通常在几万条以内,SQLite 单文件、零配置、随项目走,能少掉配置数据库服务这一层麻烦。如果你后续要接地理分析或并发写入,再迁 MySQL 也不迟,sqlite3 和 MySQLdb 的 SQL 语法差异极小,迁移成本可控。
Web 框架选 Flask 而非 Django,理由和选 SQLite 一样:Django 自带 ORM、Admin、认证一套全家桶,对毕设来说一半用不上,还得花时间学它的约定。Flask 只负责把情感分析结果以 JSON 接口抛给前端,代码量能控制在几十行内。
2.2 系统模块划分:数据采集、预处理、情感判定、可视化
在动手写代码前先画清楚模块边界。我的惯用切法是分成三层四模块:
- 数据层:微博数据获取(API / 爬虫 / 数据集),原始数据落库。
- 分析层:清洗去噪、分词、情感打分/分类,把结果写回数据库。
- 展示层:Flask 接口 + ECharts 前端,展示时间趋势、情感占比、词云和关键词表。
常见失败案例是把所有代码堆在一个 process.py 里,数据采集写完直接接着训练模型,跑起来一团乱。正确的做法是每一个模块独立成文件,主程序只做调度,这样调试时能快速定位是哪一层出了问题,答辩讲解时也更清晰。
2.3 一份可直接落地的项目目录与数据库表设计
我一般会给毕设同学这样的目录结构:
| 文件/目录 | 作用 |
|---|---|
| data/raw/ | 存放原始采集数据(CSV/JSON) |
| data/clean/ | 清洗后的中间数据 |
| models/ | 训练好的分类器与向量器 |
| app.py | Flask 主程序 |
| processor/cleaner.py | 清洗与分词 |
| processor/sentiment.py | 情感判定逻辑 |
| static/ | 前端 ECharts 模板和词云图 |
| weibo.db | SQLite 数据库文件 |
数据库表不用很复杂,一张微博表加一张标签表足够,索引要提前建好。下面是建表明细:
import sqlite3 conn = sqlite3.connect("weibo.db") c = conn.cursor() c.execute(""" CREATE TABLE IF NOT EXISTS weibo ( mid TEXT PRIMARY KEY, user_id TEXT, created_at TIMESTAMP, content TEXT, cleaned_content TEXT, sentiment_label INTEGER, sentiment_score REAL ) """) # 时间字段查趋势时频繁排序,必须建索引 c.execute("CREATE INDEX IF NOT EXISTS idx_created_at ON weibo(created_at)") conn.commit() conn.close()这里把 mid 作为主键是有讲究的。微博每条内容有唯一 id,采集时天然去重,重复抓取用 INSERT OR IGNORE 就不会产生脏数据;created_at 以 UTC 时间入库,展示时再转换时区,避免混淆。sentiment_label 用整数(1 正向、-1 负向、0 中性),score 保留浮点概率,用于后续置信度过滤和排序。
3. 数据是最大的坎:微博数据获取、清洗与分词的落地流程
3.1 微博数据获取的三种方式与取舍
正式动手前必须想清楚数据源,这决定了整个毕设能不能按期完成。常见有三条路:
第一,微博开放平台 API。理想方案,但个人开发者拿不到高级接口权限,发微博、搜微博都有重重限制,学生阶段基本等不到审核通过,不建议把毕设押在这条路上。
第二,爬虫抓取 m.weibo.cn 移动端接口。这是实际项目里最常见的做法,详情见下面代码。但要注意控制频率和合规边界,只做学术研究、只采集公开可见内容、遵守平台规则,不要拿去做商业用途。
第三,使用开源数据集。如果你赶时间,可以用社区整理好的微博情感数据集,比如 weibo_senti_100k 这类带标注的语料,省去最耗时的标注环节。缺点是数据时间陈旧,情感表达方式和当下有偏差,但训练流程完全可以跑通,适合先交一版系统再补实时数据。
下面是一个基于移动端搜索接口的采集示例。这个接口结构相对稳定,返回 JSON 里直接包含微博正文、时间、用户 id:
import requests import time import random import json def fetch_weibo_search(keyword, page=1, cookie=""): # 注意:仅用于学术研究,请求频率务必控制在 1 秒以上 url = "https://m.weibo.cn/api/container/getIndex" params = { "containerid": f"100103type=1&q={keyword}", "page_type": "searchall", "page": page } headers = { "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1", "Referer": "https://m.weibo.cn/", "Cookie": cookie } r = requests.get(url, params=params, headers=headers, timeout=10) time.sleep(random.uniform(1, 2)) if r.status_code == 200: return r.json() return None # 调用示例:cookie 由你自己在浏览器登录后复制,不要硬编码到源码里 # data = fetch_weibo_search("天气", page=1, cookie="你的cookie")代码里的三个关键参数必须说清楚。containerid是搜索容器的固定格式,q后面接关键词;page_type=searchall指全类型搜索,如果你想只搜原创微博,可以改成feed;Referer设置为m.weibo.cn是为了让请求看起来像是移动端正常跳转。UA 用了 iPhone 的移动端标识,因为 m 站的接口对移动端 UA 更宽松。切记每次请求后要随机 sleep 1~2 秒,这不是玄学,而是被风控之后的血泪经验——频率稍高,接口就会返回空的data字段,看起来像没抓到数据,其实是 IP 被临时限制了。
3.2 清洗流程:从 HTML 实体到表情占位符的规范化
采集到的原始文本充满噪声。微博文本常见噪声包括:HTML 实体、超链接、@提及、话题标签、表情符号占位符(形如[哈哈])、连续空格和换行。这些噪声不影响人读,但对模型特征提取是致命干扰,尤其是分词后会产生大量无意义词汇。
清洗我用一个函数串起来,每一行都有明确用途:
import re from html import unescape def clean_weibo_text(raw: str) -> str: text = unescape(raw) # 反转义HTML实体,如 & → & text = re.sub(r"<[^>]+>", "", text) # 去富文本标签 text = re.sub(r"https?://\S+", "", text) # 去链接 text = re.sub(r"@[\w\u4e00-\u9fa5-]+", "", text) # 去@用户 text = re.sub(r"#([^#]+)#", "", text) # 去话题标签 text = re.sub(r"\[([^\]^\s]+)\]", "", text) # 去表情占位符 text = re.sub(r"\s+", " ", text).strip() return text这里的关键决策点是表情占位符和话题标签的去留。如果后续用的是情感词典方案,保留[哈哈]、[哭]这类表情反而能提供强情感信号;但如果做机器学习,表情占位符会被 TF-IDF 当成普通词频特征,稀疏且不稳定。我的处理方式是:清洗时先单独提取表情和话题存到附加字段,正文里去掉,后续做特征融合时再把表情特征拼回去。这样两边的好处都占了。
另一件容易忽略的事是 unescape。微博文本里经常出现&、<这类实体,有些是转发时自动生成的,直接用正则去<[^>]+>是去不掉的,必须先反转义。顺序不能反,先 unescape 再去标签,否则<div>会被误杀或者漏网。
3.3 分词与停用词:jieba 的词典策略
jieba 分词本身是开箱即用的,但微博语料有两个明显痛点:网络新词多、口语短句多。“绝绝子”“破防”“yyds”这些词默认词典里没有,会被切成“绝绝”“子”这种碎片,情感强度直接丢失。解决办法是准备一个微博词典文件,每行一个词加词频和词性,用load_userdict加载:
import jieba jieba.load_userdict("data/weibo_dict.txt") stopwords = set() with open("data/stopwords.txt", "r", encoding="utf-8") as f: stopwords = set(f.read().split()) def tokenize(text: str) -> list: return [w for w in jieba.cut(text) if w not in stopwords and len(w.strip()) > 1]load_userdict的格式是“词语 词频 词性”,词频可以不写,jieba 会自动给一个较高优先级。weibo_dict.txt我一般会放一百来个高频微博词,比如“哈哈哈”“绝绝子”“无语子”“yyds”“破防”“蚌埠住了”,这些词出现的频率远高于普通词典词。注意len(w) > 1这个过滤不能写成死规则。情感词典里“爽”“丧”“燃”是单字且情绪极强,如果你在情感打分阶段要用单字情感词,这里就得把单字保留,或者至少单独保留一份原始分词结果。最稳妥的做法是在清洗后先存一份完整分词结果,模型训练阶段再做停用词过滤。
4. 情感判定三选一:词典打分、机器学习与预训练模型的实现对比
4.1 第一版:SnowNLP 情感打分与阈值偏移
很多毕设上来就用 SnowNLP,因为两行代码就能出结果。它底层是一个基于商品评论语料训练的朴素贝叶斯模型,sentiments属性输出 0~1 之间的情感概率,越接近 1 越正向。问题在于商品评论语料和微博语料的分布差异巨大:微博里有大量反讽、玩梗、缩写,SnowNLP 会把“这波操作太秀了”判成正向,但上下文可能是反向嘲讽。
我在实际项目里不会直接拿 0.5 当分界,而是用一个可调的偏移阈值:
from snownlp import SnowNLP SENTI_THRESHOLD = 0.6 NEUTRAL_MIN = 0.4 def infer_by_snownlp(text: str) -> tuple: score = SnowNLP(text).sentiments if score >= SENTI_THRESHOLD: return 1, score if score <= NEUTRAL_MIN: return -1, score return 0, scoreSENTI_THRESHOLD取 0.6、NEUTRAL_MIN取 0.4 是我试出来的一个相对稳定区间。SnowNLP 的输出在 0.4~0.6 之间时,人工标注的一致性很差,这部分样本与其猜一个标签,不如标成中性,反而能让正向和负向的评估指标更可信。你可以在自己的验证集上微调这两个参数,原则是让误判集中在低置信样本而非高置信样本上。
4.2 第二版:TF-IDF + 逻辑回归训练自己的分类器
如果你手里有几千条标注数据,就别再用 SnowNLP 了,自己训练一个分类器效果会明显更好。TF-IDF 加逻辑回归是这类短文本任务性价比最高的组合:训练快、可解释性好、答辩时能画出特征权重排序表。
下面是一个完整的训练流程:
import pandas as pd from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report df = pd.read_csv("data/clean/labeled_weibo.csv") X = df["clean_text"] y = df["sentiment"] vectorizer = TfidfVectorizer( ngram_range=(1, 2), max_features=50000, min_df=3, max_df=0.8 ) X_vec = vectorizer.fit_transform(X) X_train, X_test, y_train, y_test = train_test_split( X_vec, y, test_size=0.2, random_state=42, stratify=y ) clf = LogisticRegression(C=1.0, max_iter=200, class_weight="balanced") clf.fit(X_train, y_train) print(classification_report(y_test, clf.predict(X_test)))这里几个参数是文本分类的标准做法。ngram_range=(1, 2)很重要,很多否定表达是双词特征,比如“不太行”“非常喜欢”,如果只用单字词,逻辑回归很难学到“不”和“行”组合在一起的语义翻转;min_df=3去掉只在两三条微博里出现的低频词,避免特征矩阵过度稀疏;max_df=0.8去掉在八成以上文本中都出现的词,比如“微博”“转发”这类无区分度的高频词。
class_weight="balanced"是应对类别不平衡的关键参数。你采集到的自然语料里正向常常是负向的几倍,逻辑回归默认会偏向多数类,导致负向样本的召回率极低。加这个参数后,模型会按类别的样本量反比放大少数类的损失权重,在不做任何过采样的情况下就能明显改善 F1。
4.3 第三版:BERT 微调在什么情况下才值得做
导师如果对深度学习有明确要求,你可以在第二版基础上升级到 BERT 微调。但我要先泼一盆冷水:BERT 微调需要标注数据量至少在 2 万条以上才有明显收益,而且训练时 GPU 显存需要至少 8GB。如果你只有五千条标注数据,BERT 的效果和 TF-IDF + 逻辑回归很接近,但部署体积从几百 KB 变成几百 MB,推理速度慢几十倍,对毕设系统的交付压力反而更大。
如果你的机器和环境都允许,常见做法是用transformers库加载一个 12 层的中文 BERT 模型,在微博标注数据上做 3 个 epoch 的微调,学习率取2e-5,batch size 取 16 或 32。这个方向适合作为答辩加分项展示“你对前沿方法有了解”,但不建议作为主方案,因为你需要留出大量时间解决推理部署和前端联调问题。
5. 毕业设计避坑清单:五类高频翻车现场与排查记录
5.1 爬虫第 20 页后接口返回空数据
现象:前几页抓得好好的,爬到第 20 页左右,返回的 JSON 里data是None,没有任何报错,把自己当成了“没有更多数据”来判断,实际是风控。
原因:m.weibo.cn 对单个 IP 有一定频次控制,连续无间隔请求会触发临时限制,但不会直接拒绝连接,而是静默返回空数据,极具迷惑性。
解决:在每两次请求之间加随机休眠 2~3 秒,且每抓 20 页强制停 30 秒。另外每次请求新建一个requests.Session(),避免连接复用导致被识别。如果还是被限制,换个网络环境或稍等几分钟即可恢复,不要硬刚。
5.2 清洗后文本为空导致模型训练直接报错
现象:运行 TF-IDF 训练时,TfidfVectorizer抛异常empty vocabulary,之前完全没预警。
原因:微博文本里有一部分是全表情、纯链接、纯 @ 组成的,清洗后变成空字符串或只有一两个字,被min_df一过滤,特征词表没了。
解决:清洗后必须做长度过滤并重置索引,这一行代码能省掉后续所有模型输入异常:
df = df[df["clean_text"].str.len() >= 2].reset_index(drop=True)reset_index(drop=True)很容易被漏掉。过滤后索引会留下空洞,后续train_test_split时用.iloc取值会出错,虽然有时候只是警告,但一旦出错很隐蔽。
5.3 情感标签不平衡,准确率虚高但 F1 惨不忍睹
现象:标注完发现正向 8000 条、负向 1000 条、中性 1000 条,训练出来准确率 0.9,答辩演示时只报准确率,一算 F1,负向只有 0.2。
原因:多数类样本量碾压少数类,模型为了降低整体损失,把所有样本都往多数类偏,少数类完全被忽略。准确率在这种分布下没有参考价值。
解决:评估指标只看每个类别的 F1、Precision、Recall,不要只打印总准确率。如果分类器用逻辑回归,传class_weight="balanced"先试一轮,效果不足再考虑数据层面的过采样或欠采样。文本数据的过采样不能直接用 SMOTE,因为特征是稀疏 TF-IDF 向量,SMOTE 生成的样本在语义上毫无意义,需要先做四舍五入转成整数计数再用,但这样做收益很有限。
5.4 模型保存后推理结果和训练时差了一截
现象:训练时验证集 F1 0.78,部署到 Flask 接口后测同一批句子,结果明显变差。
原因:模型加载时只加载了分类器对象,没有把jieba.load_userdict重新执行,也没有把TfidfVectorizer一起保存。推理时的分词结果和特征向量和训练时完全不一致,输入分布变了,输出自然不稳定。
解决:把向量器和分类器打包在一个文件里保存:
import pickle with open("models/sentiment_model.pkl", "wb") as f: pickle.dump({ "clf": clf, "vectorizer": vectorizer, "userdict": "data/weibo_dict.txt" }, f)加载时先读userdict路径并重新jieba.load_userdict,再恢复分类器和向量器。推理服务和训练脚本必须复用同一个预处理管道,不能各写一套,这个教训我踩过不止一次。
5.5 时间序列图不准:微博时间是 UTC,展示时没有转时区
现象:ECharts 时间趋势图上,凌晨 4 点的数据大量堆积,白天反而很稀疏,甚至趋势方向都和预期相反。
原因:微博接口返回的created_at是 UTC 时间,pandas 读取后如果直接dt.strftime("%m-%d %H:%M"),会按照系统本地时区显示,当系统时区非Asia/Shanghai时,时间轴整体偏移 8 小时。
解决:入库时统一存 UTC,展示时再转东八区:
import pandas as pd df["created_at"] = pd.to_datetime(df["created_at"], utc=True) df["created_at_cst"] = df["created_at"].dt.tz_convert("Asia/Shanghai")这样数据库里始终是标准时间,图表展示的是北京时间,两边职责清晰。如果你图省事直接加 8 小时,遇到夏令时或系统时区变化时又会出错,不要做这种硬编码。
6. 可视化交付与多模态扩展:从一组图表到能讲出故事的毕设成果
6.1 ECharts 趋势图与词云生成的两个必调参数
后端我只保留一个接口,返回近 30 天的每日情感得分均值,前端用 ECharts 折线图渲染。词云生成需要手动指定中文字体路径,这是最容易翻车的点:不指定字体,生成图片里中文全部变方块。Windows 用C:/Windows/Fonts/simhei.ttf,macOS 用/System/Library/Fonts/PingFang.ttc。
from wordcloud import WordCloud wc = WordCloud( font_path="C:/Windows/Fonts/simhei.ttf", width=800, height=600, background_color="white", max_words=100 ) wc.generate(" ".join(all_tokens)) wc.to_file("static/weibo_wordcloud.png")max_words=100控制词云密度,建议保持默认,太多字会糊成一团,现在短文本舆情情感倾向分析系统设计与可视化的标准做法也是保持简洁。
6.2 从文本到多模态:图片情感得分与正文情感融合
如果你的毕设时间有余量,可以做一个小小的多模态扩展:给每条微博下载一张配图,用图像情感分类模型输出图片的正负向得分,再与文本得分做加权融合:
final_score = 0.7 * text_score + 0.3 * image_score权重不是固定的,我用 0.7/0.3 是因为微博配图和正文往往有较强的语义关联,但纯表情包配图会干扰判断,所以文本权重更高。配合微博签到数据可以再做一轮地理聚合分析,把不同城市的情感均值标注在地图上,这个衍生方向能让答辩内容丰满不少;视频多模态情感分析的扩展路径类似,但毕设周期内不建议碰,处理视频的耗时和算力成本会拖垮进度。
6.3 验证与答辩:手工标注的一致性校验
没有权威标注数据时,我习惯从清洗后的样本里随机抽 200 条,请两个同学独立标注,再用 Kappa 系数校验一致性。Kappa 值大于 0.7 说明标注标准清晰,数据集可以用于训练;低于 0.5 则要重新核对任务定义,多数是因为“中性”和“负向”的边界描述不够具体。这个方法也能在答辩现场回答“你这个标签是否可信”的追问。整个系统做完,我最大的教训是永远别急着调模型,先把采集和清洗环节打磨到能不间断跑通一整天,再回头谈算法。希望帮到你。
本文还有配套的精品资源,点击获取