资讯动态

基于Python的微博舆情分析系统:爬虫、情感分析与可视化实战

发布时间:2026/10/15 4:03:13 来源:尧图企业网站定制
简介这是一套基于Python微博舆情分析系统的毕业设计完整源码与文档说明面向计算机相关专业学生、毕设选题者及需要项目实战练习的开发者覆盖数据采集、存储、分析、可视化到结果展示的完整链路。项目采用PythonDjangoVue3技术栈内置微博爬虫代码、CSV数据源和MySQL数据库脚本源码经过本地编译调试可运行评审得分98分具备较高参考价值。压缩包共2000个文件整体约548MB主要包括Python源码75个py、Vue组件28个vue、图片素材1797个jpg、CSV数据、SQL脚本及Markdown文档等目录结构清晰便于按功能模块拆解学习。目前已有128人学习/下载适合作为毕业设计参考、课程大作业模板或舆情分析入门实践。文档部分整理了系统设计思路与实现细节搭配数据文件可快速复现实验环境有助于理解微博舆情数据的清洗、统计与可视化流程也为二次功能扩展提供了可操作的基础。1. 微博舆情分析系统不是爬虫加个表格难的是“判定”这一步把毕业设计题目定为“基于Python微博舆情分析系统的设计与实现”时我第一反应是“不就是爬虫把微博评论拉下来再用词云展示吗”。真正动手才发现爬虫部分撑死占三成工作量剩下七成都压在“舆情”这两个字上——采集回来的文本要清洗、分词、滤掉广告和营销号更要紧的是用情感分析把“夸”和“骂”量化成分数最后才能落到可视化面板上。这套系统适合两类人一是需要交毕设、要源码和文档说明的同学二是想快速搭一个轻量舆情监控看板的开发者。整条链路不依赖昂贵的云服务单机就能跑通。2. 架构先行把微博舆情拆成采集、清洗、判定、展示四层2.1 数据链路先行四层架构与各层职责很多毕设项目的通病是“先写爬虫再写界面”最后所有逻辑挤在一个文件里答辩时老师一问“为什么这么设计”就卡壳。我踩过这个坑之后重新梳理微博舆情分析系统的核心是一条单向数据管道从微博服务器到浏览器看板要经过四个层级。层级输入关键职责对应依赖采集层关键词、翻页参数模拟登录、抓取搜索接口、处理分页与频率限制requests清洗层原始 JSON 卡片去除 HTML、短链、用户、话题标签停用词过滤re、jieba判定层清洗后的中文文本情感打分、阈值映射、正负中分类snownlp展示层分类结果与时间序列统计聚合、图表渲染、本地页面展示sqlite3、pyecharts、Flask每一层只依赖前一层的输出不在层与层之间跳过。清洗层输出干净的文本列表判定层只接受这个列表展示层读数据库不直接发 HTTP 请求。这样拆的好处是每个模块可以独立测试也能单独替换——哪天 SnowNLP 不准你只需要动判定层爬虫和看板都不用改。2.2 选型理由为什么情感判定用 SnowNLP 而不是自己训练模型情感分析是舆情系统的灵魂也是选型最容易翻车的地方。常见的方案有三类基于情感词典打分、基于朴素贝叶斯等统计模型、基于深度学习预训练模型。对于毕设和中小型业务我的结论是直接用 SnowNLP 起步把训练微调作为加分项。三个方案放在同一个标注集上SnowNLP 在电商评论上的准确率能到 80% 上下在微博这种短文本、带表情、带网络梗的语料上会掉到 65% 左右自己训练朴素贝叶斯需要你先手工标注至少三千条数据标注两天效果可能还不如 SnowNLP 默认模型深度学习方案准确率最高但炼丹时间长、显存要求高单机调试成本不划算。SnowNLP 的优势在于内置了贝叶斯分类器和情感倾向模型开箱即用还支持用你的标注数据做增量训练等于把“换模型”的后悔药留给你。微博文本的特点是碎片化严重“哈哈哈哈哈”和“气死我了”往往就七八个字深度学习模型在这类短文本上优势有限。SnowNLP 返回一个 0 到 1 之间的情感分值大于 0.6 偏向正面小于 0.4 偏向负面中间是中性。这个阈值不是拍脑袋定的后面章节我会讲怎么根据你的数据分布回调。2.3 Flask 做展示层以及“文档说明”应该写什么展示层我选了 Flask 而不是 Django。原因很直接舆情系统只有一个看板页面、两三个路由Django 自带 Admin 和 ORM 反而显得笨重Flask 的 Jinja2 模板配合 Pyecharts 生成的 HTML 文件直接把图表嵌入页面代码量少答辩时也好讲。你甚至可以不用 Flask只把 Pyecharts 渲染出的 HTML 文件扔给浏览器打开但那样就体现不出 Web 框架的能力了。标题里带着“文档说明”我建议把文档重心放在三块系统架构图与数据流说明、每个模块的接口定义、情感判定阈值的实验记录。找工作的同学可以把这份文档当迷你技术方案面试被问项目时直接照着讲答辩时老师最常问的“为什么选这个方案”“数据量大了怎么办”答案都在架构图和模块说明里。3. 动手采集登录态、Cookie 与微博搜索的翻页机制3.1 环境准备用一份 requirements.txt 固定研究现场开始写代码前先把环境锁死。Python 版本建议 3.10 或 3.11VSCode 里装好 Python 扩展后在项目根目录建虚拟环境避免把依赖装进全局。下面这份依赖清单是我调过版本的组合直接复制到requirements.txt。python -m venv venv venv\Scripts\activate # Windows 激活Linux/macOS 用 source venv/bin/activate pip install -r requirements.txtrequests2.31.0 jieba0.42.1 snownlp0.12.3 flask2.3.3 pyecharts2.0.5 pandas2.0.3 lxml4.9.3版本锁定值得认真对待。Pyecharts 2.x 和 1.x 的 API 差异巨大网上不少旧教程用pyecharts.render()的写法在 2.x 里直接报错SnowNLP 依赖的 scikit-learn 版本太新或太旧都会导致运行时崩溃。把这些版本钉死能让你把精力放在业务逻辑而不是环境修复上。装完依赖后在终端执行python -c from snownlp import SnowNLP; print(SnowNLP(今天天气不错).sentiments)能输出 0 到 1 的小数就说明环境健康。3.2 模拟登录与 Cookie 复用requests.Session 的骨架写法微博搜索接口需要登录态账号密码登录涉及加密算法维护成本高。毕设阶段用 Cookie 复用是最快的方案你手动在浏览器登录一次微博把 Cookie 复制到代码里用requests.Session维持这个会话。Session 对象会自动保存 Cookie并在后续请求中附带省去重复构造请求头的工作。import requests session requests.Session() session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36, Referer: https://weibo.com/, }) # 浏览器登录微博后按 F12 打开开发者工具 # Network 面板里复制 Cookie 请求头的完整值粘贴到下面 cookie_str SUBxxx; SUBPxxx; WBPSESSxxx for item in cookie_str.split(;): key, _, value item.strip().partition() if key: session.cookies.set(key, value) # 验证登录态请求一下搜索接口返回 200 且带 cards 才算成功 resp session.get(https://m.weibo.cn/api/container/getIndex, params{containerid: 100103type1q毕业设计, page_type: searchall, page: 1}, timeout10) print(resp.status_code, resp.text[:200])这段代码有两个细节要讲清。先是 Cookie 解析用partition()分割而不是直接split()是因为部分 Cookie 值本身含等号用strip()去掉空格则是防一手浏览器复制粘贴时带换行。然后是containerid参数100103type1q是微博搜索接口的固定前缀后面的关键词要 URL 编码中文直接传参也行但为保证一致性建议用urllib.parse.quote编码后再拼接。3.3 搜索接口的分页、字段抽取与写入本地接口返回的是 JSON 套 JSON核心数据在data.cards数组里。每条 card 的mblog字段包含text微博正文、created_at发布时间、user.screen_name用户名等。翻页靠page参数但微博有最大可见页限制通常 30 到 50 页后数据就会重复或返回空。import json import time from urllib.parse import quote def fetch_page(keyword, page): containerid 100103type1q quote(keyword) params {containerid: containerid, page_type: searchall, page: page} try: resp session.get(https://m.weibo.cn/api/container/getIndex, paramsparams, timeout10) resp.raise_for_status() data resp.json() except Exception as exc: print(f第{page}页请求失败: {exc}) return [] cards data.get(data, {}).get(cards, []) results [] for card in cards: mblog card.get(mblog) if not mblog: continue results.append({ id: mblog.get(id), user: mblog.get(user, {}).get(screen_name, ), text: mblog.get(text, ), created_at: mblog.get(created_at, ), }) return results all_data [] for page in range(1, 6): # 先抓 5 页约 250 条做初步验证 rows fetch_page(某品牌, page) all_data.extend(rows) time.sleep(1.5) # 请求间隔低于 1 秒容易触发频控 print(f第{page}页抓取 {len(rows)} 条) with open(raw_posts.json, w, encodingutf-8) as f: json.dump(all_data, f, ensure_asciiFalse, indent2)sleep(1.5)是血泪经验。微博对请求频率敏感短时间高频请求会直接返回 403 或需要验证码。1.5 到 2 秒的间隔能把采集速度控制在每分钟 30 到 40 条跑完 5 页大约一分半钟对毕设演示足够。抓到 250 条数据后先别急着批量跑用print看几条text字段的原始内容你会看到一堆a href...标签和表情符这就是下一章清洗层要处理的东西。4. 从分词到情感分值一条微博如何变成可落库的舆情数字4.1 分词与停用词Jieba 自定义词典的正确用法清洗层承担三件事去除 HTML 标签和短链、剔除 用户名与话题标签、过滤停用词。re.sub可以一次性干掉前两类但分词时频繁出问题的反而是“品牌名被切开”和“否定词被拆散”。Jieba 默认词典是通用领域对“某品牌”“某车型”这类专名极易误切一个“不推荐”可能被切成“不 / 推荐”导致后续情感判定完全反转。import re import jieba # 停用词表每行一个词可从网上下载哈工大停用词表精简版 STOP_WORDS set() with open(stopwords.txt, encodingutf-8) as f: STOP_WORDS {line.strip() for line in f if line.strip()} # 自定义词典把品牌名、产品名、网络热词整词加入避免被切开 # 每行格式词 词频(可省略) 词性(可省略) jieba.load_userdict(user_dict.txt) # user_dict.txt 示例 # 某品牌 100 nz # 不推荐 50 v # 绝绝子 80 nz def clean_and_cut(text): text re.sub(r[^], , text) # 去 HTML 标签 text re.sub(rhttp\S, , text) # 去短链 text re.sub(r#.*?#, , text) # 去话题标签 text re.sub(r[\w-], , text) # 去 用户 words jieba.lcut(text) return [w.strip() for w in words if w.strip() and w not in STOP_WORDS] sample 某品牌发布会现场太燃了不推荐错过http://t.cn/A6xxx print(clean_and_cut(sample))jieba.load_userdict必须在lcut之前调用且只加载一次。自定义词典的优先级高于默认词典这既是优点也是坑如果你把“发布会”加进词典那“开发布会的地方”这种句子会被强行切错正确做法是只加无法从默认词典推断出的专名比如公司名和产品型号。停用词表则要谨慎别把“不”“没”这类否定词加进去否则情感反转信息就丢了。我习惯把text.strip()放在 return 里再过滤一次空串因为微博文本经常以表情符或标点结尾过滤后容易残留空字符串。4.2 情感打分与阈值设定0 到 1 之间的“后悔药”SnowNLP 的sentiments属性输出一个浮点数越接近 1 越正面越接近 0 越负面。问题来了阈值设多少才算“正面”有人图省事直接定 0.5结果真实业务里大量中性微博被错误归边。我前几轮测试时把阈值调到 0.7又发现 70% 的数据被判成中性情感分布变成一根大柱子可视化难看得不行。from snownlp import SnowNLP from collections import Counter def classify_sentiment(score, pos_th0.6, neg_th0.4): 分数大于 pos_th 为正面小于 neg_th 为负面中间为中性。 if score pos_th: return positive if score neg_th: return negative return neutral # 分桶统计先看分布再调阈值 dist Counter() for item in all_data[:100]: score SnowNLP(item[text]).sentiments dist[classify_sentiment(score)] 1 print(dist)阈值设置的思路是从数据反推而不是拍脑袋。先跑 100 条样本打印每条文本的原始分数你会看到“哈哈哈”能得 0.85“垃圾客服”是 0.15而“价格还行但物流慢”这种含混句稳定落在 0.4 到 0.6 之间。把pos_th和neg_th分开设置是为了留出一条“不确定地带”——中性不是没有观点而是情感信号太弱硬归边反而会让统计失真。4.3 结果入库与可视化sqlite pyecharts 的最小方案判定结果适合存在 SQLite 里单文件、免安装、Flask 直接读。建一张sentiments表字段包括 id、文本、原始分数、标签、抓取时间。入库后统计各情感类别的数量和占比用 Pyecharts 生成饼图与时间线折线图。import sqlite3 from datetime import datetime from pyecharts.charts import Pie, Line from pyecharts import options as opts conn sqlite3.connect(weibo_sentiment.db) cur conn.cursor() cur.execute( CREATE TABLE IF NOT EXISTS sentiments ( id INTEGER PRIMARY KEY AUTOINCREMENT, text TEXT, score REAL, label TEXT, created_at TEXT ) ) # 批量入库score 保留四位小数便于后续排序 sql INSERT INTO sentiments (text, score, label, created_at) VALUES (?,?,?,?) for item in all_data: score SnowNLP(item[text]).sentiments label classify_sentiment(score) cur.execute(sql, (item[text], round(score, 4), label, datetime.now().isoformat())) conn.commit() # 读取计数并画图 cur.execute(SELECT label, COUNT(*) FROM sentiments GROUP BY label) data cur.fetchall() pie Pie() pie.add(情感分布, [(k, v) for k, v in data], radius[40%, 65%]) pie.set_global_opts(title_optsopts.TitleOpts(title微博舆情情感分布)) pie.render(emotion_pie.html) print(图表已生成用浏览器打开 emotion_pie.html)SQLite 的写入要放在循环内逐条执行还是批量提交数据量几百条时无所谓但超过五千条就会明显变慢。这里给出的写法是每轮循环都执行INSERT最后统一conn.commit()比每条 commit 快一个数量级。Pyecharts 的render方法会把图表和依赖全部打成一个独立 HTML 文件这个文件可以直接作为毕设演示素材也能被 Flask 的render_template引用。5. 避坑自查依赖冲突、反爬拦截与情感误差5.1 现象pip 安装后import snownlp报错提示找不到sklearn原因SnowNLP 底层依赖 scikit-learn但新版 scikit-learn 1.4 移除了部分旧 APISnowNLP 0.12.3 尚未兼容另一个常见情况是 Anaconda 环境里同时存在多套 numpy导致动态库链接失败。解决严格按第 3 章的requirements.txt安装不要用pip install -U scikit-learn升级如果已经升级执行pip install scikit-learn1.3.2降级。在项目虚拟环境里重装一次是最省事的后悔药。5.2 现象Cookie 第一天能用第二天请求返回 401 或跳转登录页原因微博的登录态有有效期短则几小时长则一天SUB字段过期后 Session 里的 Cookie 全部失效。解决不要硬编码 Cookie把 Cookie 字符串存到cookie.txt每次启动程序时读取并打印“Cookie 过期时间”提示更省心的做法是准备一个扫码登录脚本用qrcode库生成登录二维码手动扫码后自动更新 Cookie 文件。高频抓取还会触发滑块验证此时只能降低请求频率把sleep间隔拉大到 3 到 5 秒。5.3 现象分词结果里全是单字“某品牌”被切成“某”“品”“牌”原因Jieba 默认词典缺少专名且动态规划时倾向于把低概率词切碎。解决在user_dict.txt里补充这些词格式为“词 词频 词性”例如“某品牌 100 nz”。注意词频别填太高50 到 100 足够让 Jieba 优先按整词切分填 1000 以上反而可能导致这个词在错误上下文中被强行切出。修改词典后要重启 Python 进程jieba.load_userdict只在首次加载时生效。5.4 现象SnowNLP 把“这家店太棒了”判成负面分数只有 0.2原因SnowNLP 默认训练语料以购物评价为主对“棒”“绝”“燃”等词的语义权重与微博语境不匹配此外同一句话在不同语境下极性可能不同。解决用自己标注的数据对 SnowNLP 重新训练。收集 2000 条微博文本标注为正面或负面存入train.txt一行一条执行如下训练脚本。from snownlp import sentiment # train.txt 每行一句不做标注 # 需要准备 train.neg.txt 和 train.pos.txt 两个文件 sentiment.train(train.neg.txt, train.pos.txt) sentiment.save(sentiment.marshal)训练完成后SnowNLP 会优先加载sentiment.marshal文件。这个文件几百 KB可以随项目打包答辩时展示“针对微博语料微调”是个明显的加分点。标注 2000 条需要大约两小时但换来的是情感准确率从 65% 提到 80% 以上这笔时间花得值。5.5 现象CSV 或 JSON 文件用 Excel 打开乱码原因Windows 的 Excel 默认以 GBK 编码读取文件你用 UTF-8 写入的中文全部变成问号。解决写文件时指定encodingutf-8-sig这个编码会在文件头部写入 BOMExcel 就能正确识别。如果是 JSON 文件建议直接用 SQLite 或 CSV 而非 Excel 查看在 VSCode 里打开并点击右下角编码按钮手动切换为 UTF-8 也能解决。这个坑跟舆情系统本身无关但几乎每个毕设项目翻车都翻在这里。6. 给自己做一套标注集量化“准不准”才算真的交付前面的模块跑通只代表系统能运转不代表结果可信。毕业论文的答辩老师最认一个数据你的情感分类到底准不准。给系统做评测集的办法是手动从采集数据里抽 300 条按“负面 / 中性 / 正面”三个标签人工标注然后拿标注结果和系统预测结果做对比。别嫌 300 条少——对毕设而言这个量级已经能区分“方案可用”和“方案瞎猜”。from sklearn.metrics import classification_report # y_true 是人工标注标签y_pred 是系统预测标签 y_true [negative, neutral, positive, negative, positive] y_pred [negative, positive, positive, negative, neutral] print(classification_report(y_true, y_pred, target_names[negative, neutral, positive]))把precision、recall、f1-score三项打印出来重点看负面情感类别的召回率。舆情系统最怕漏报负面——中性错分成正面损失不大负面被吞掉才是致命伤。如果负面召回率低于 70%别急着调模型先用 5.4 的数据做一次重训若重训后仍偏低回过头看清洗层是否把“不推荐”“翻车”等否定词误当成停用词过滤了。评测集放在test_set.csv里代码、数据、报告三件套齐全这已经超过大部分只跑通 Demo 的毕设项目。做这套系统时我最深的教训是“先定评估方式再做情感判定”。我最初以为阈值调到 0.9 会让系统更灵敏结果所有微博都被判成中性可视化面板上一片灰后来才领悟阈值不是用来调模型的旋钮而是用来匹配业务预期的刻度。你先问自己“中性比例控制在多少可接受”再去反推阈值比反复试 0.4、0.5、0.6 碰运气高效得多。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑