ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

校园舆情监控系统实战:基于Snownlp情感分析与微博爬虫的预警实现

校园舆情监控系统实战:基于Snownlp情感分析与微博爬虫的预警实现 简介面向Python毕业设计/课程设计场景这套完整的校园舆情管理系统源码涵盖登录与密码管理、大学生微博数据爬取、负面信息分析与预警等核心功能采用Python 3.6.8开发搭配MySQL 5.7数据库适合计算机相关专业学生快速对照实现并完成项目文档。资源共256个文件压缩包约46.54MB以Python源码py/pyc、前端页面html/css/js、layui组件样式以及gif/jpg/png图片素材为主另含SQL脚本、docx/md/pptx说明文档目录层次清晰便于快速定位与二次开发。目前已有64人学习下载适合需要完整前后端与数据库脚本的毕业设计参考。资源中的说明文档和项目结构有助于从环境配置、数据库导入、功能模块实现到预警逻辑形成闭环特别是饼状图、柱状图展示负面信息百分比以及负面率超过20%自动触发提示的设计能直观呈现数据分析在舆情管理中的落地方式为论文撰写和答辩演示提供有力支撑。1. 一套能跑的校园舆情系统难点不在爬虫而在负面识别接手这套校园舆情管理系统源码时我最先看的是它的目录layui 前端、Python 后端、MySQL 库乍看和普通课程设计没区别。真正跑起来才发现这套东西的价值不在爬微博而在把负面情绪变成预警百分比这一步——它用 Snownlp 情感分析加自定义词表双重判定把微博正文映射成 0 到 1 的负面概率再按 20% 阈值触发预警。这个设计思路直接决定了它的可维护性因为微博页面结构说改就改但负面判定的词库和阈值是你能控制的。这套资源适合三类人拿它做毕业设计或课程设计的学生想快速搭一套舆情监控原型的后端工程师以及需要给学校或部门做舆论观察的运维人员。整体技术栈是 Python 3.6.8 MySQL 5.7 Layui 前后端不分离的传统架构虽然没有 Spring Boot 和 Vue 那么现代但胜在依赖少、跑得起来。接下来我从数据模型、爬虫封装、情感分析到预警触发按实际搭建顺序拆给你看。2. 库表设计与技术选型为什么这套系统还在用 Python 3.6 和 MySQL 5.72.1 版本约束不是落后是兼容性兜底网上很多 python 安装教程都在推 3.10 以上但这套源码明确标注 Python 3.6.8这不是随手填的。我实际验证过项目里依赖的 PyMySQL、Snownlp、pandas 等库在 3.6 环境下有预编译的 wheel 包而 Python 3.8 以上某些版本对 Snownlp 的内部import方式会报ImportError。如果你本机已经是 python 3.8我建议直接用 PyCharm 新建虚拟环境并指定 3.6.8 解释器路径不要硬改源码去适配新版本否则你会陷在编码和依赖地狱里。MySQL 5.7 也是一个道理。Navicat11 导出 SQL 默认用的是 5.7 语法包含ENGINEInnoDB DEFAULT CHARSETutf8mb4在 MySQL 8.0 上导入大概率会遇到排序规则不兼容的报错。新装 mysql 的话记得在配置文件里把sql_mode调整一下避免ONLY_FULL_GROUP_BY干扰查询。2.2 建库脚本与三张核心表结构这套系统的后端连接数据库用的是 PyMySQL而不是 Django ORM所以表结构要自己看 SQL 文件。打开db/opinion.sql你至少会看到三张核心表用户表、舆情信息表、预警记录表。下面是根据源码整理出的核心 DDL我加了注释。-- 舆情基础信息表 CREATE TABLE t_opinion ( id int(11) NOT NULL AUTO_INCREMENT COMMENT 主键, weibo_id varchar(32) DEFAULT NULL COMMENT 微博唯一ID用于去重, nickname varchar(64) DEFAULT NULL COMMENT 发布者昵称, content text COMMENT 微博正文, publish_time datetime DEFAULT NULL COMMENT 发布时间, source varchar(10) DEFAULT weibo COMMENT 数据来源, sentiment_score decimal(4,3) DEFAULT NULL COMMENT 情感得分0-1越高越负面, is_negative tinyint(1) DEFAULT 0 COMMENT 是否负面1是 0否, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 入库时间, PRIMARY KEY (id), UNIQUE KEY uk_weibo_id (weibo_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT微博舆情明细表; -- 用户登录表 CREATE TABLE t_user ( id int(11) NOT NULL AUTO_INCREMENT, username varchar(32) NOT NULL, password varchar(64) NOT NULL COMMENT 建议存哈希值, role varchar(10) DEFAULT admin, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 预警触发记录表 CREATE TABLE t_warning ( id int(11) NOT NULL AUTO_INCREMENT, opinion_date date DEFAULT NULL COMMENT 统计日期, total_count int(11) DEFAULT NULL COMMENT 当日采集总数, negative_count int(11) DEFAULT NULL COMMENT 负面数量, negative_rate decimal(5,2) DEFAULT NULL COMMENT 负面占比如20.50, status tinyint(1) DEFAULT 0 COMMENT 是否已触发预警, handle_advice varchar(255) DEFAULT NULL COMMENT 处理建议, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;注意t_opinion表里的uk_weibo_id唯一索引非常关键。爬虫如果不做去重每次采集都会把同一条微博反复插入后面统计负面百分比时数据会失真。我一般会在爬虫的save_to_mysql()里用INSERT IGNORE INTO或者先SELECT判断再插入避免主键冲突打断爬取流程。2.3 初始化用户与登录校验逻辑源码里默认管理员账号密码写在init.sql中密码是简单的 MD5 值。登录校验的逻辑在util/db_helper.py中大概流程是前端 layui 表单提交用户名密码到/login接口后端Flask接收后用hashlib.md5()加密再比对。这里有一个安全点必须提醒md5 加密的密码在现在的算力下形同虚设你可以改成sha256加盐方式很简单在login()函数里把密码拼接固定字符串再哈希即可。配置项位置作用DB_HOSTdb_helper.py顶部数据库主机地址默认 127.0.0.1DB_PORT同上MySQL 端口默认 3306DB_USER同上数据库账号DB_PASSWORD同上数据库密码DB_NAME同上库名默认 opinion_system这套系统的后端不是 Django 也不是 Flask 的复杂工程就是一个app.py挂几个路由配合pymysql做查询。你拿到源码后第一步一定是改数据库连接配置改成你本机 Navicat 能连上的账号密码再去跑建库脚本否则登录页都进不去。3. 微博舆情爬虫requests 会话封装、翻页参数与反爬降级3.1 模拟浏览器请求头是第一步这套源码的爬虫部分集中在spider/weibo_spider.py用的是 requests 库没有 scrapy 框架。爬取目标是大学生微博内容准确说是某个高校微博页面的公开正文。它的核心思路是模拟浏览器直接请求移动端微博搜索页因为移动端页面结构比 PC 端简单翻页参数是page这大大降低了解析成本。import requests from bs4 import BeautifulSoup def build_headers(cookie_value): headers { User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1, Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/webp,*/*;q0.8, Cookie: cookie_value, Referer: https://m.weibo.cn/ } return headers这里的关键是User-Agent用了 iPhone 端这与 PC 端页面能拿到的 DOM 结构完全不同。移动端微博的正文在p classtxt节点下而 PC 端则是在div classWB_text W_f14里。如果你拿 PC 端的解析代码去套移动端页面select选择器什么都匹配不到。验证方法很直接先用浏览器打开微博移动端页面按 F12 查看目标正文的实际 class 名再回源码里改对应的 CSS 选择器。Cookie 的获取方式我不再赘述简单说是在浏览器登入微博后从开发者工具的请求头中复制。3.2 翻页与字段提取微博移动端搜索接口的 URL 格式大致是https://m.weibo.cn/api/container/getIndex?containerid100103type%3D1%26q%3D关键词page_typesearchallpageN。源码里虽然用的是BeautifulSoup直接解析 HTML但你也可以改成 JSON 接口只是字段名不一样注意调整提取逻辑。for page_num in range(1, max_pages 1): url ( https://m.weibo.cn/api/container/getIndex? fcontainerid100103type%3D1%26q%3D{keyword} fpage_typesearchallpage{page_num} ) resp requests.get(url, headersbuild_headers(cookie), timeout10) cards resp.json().get(data, {}).get(cards, []) for card in cards: mblog card.get(mblog, {}) text BeautifulSoup(mblog.get(text, ), html.parser).get_text() item { weibo_id: mblog.get(idstr), nickname: mblog.get(user, {}).get(screen_name), content: text, publish_time: mblog.get(created_at), } save_to_mysql(item) time.sleep(3)代码逻辑很简单构造搜索 URL按页循环解析返回的 JSON 里的mblog字段。weibo_id是去重依据content是情感分析的输入。最容易被忽略的是time.sleep(3)这个延时不是多余的它在降低请求频率防止 IP 被临时限制。如果你把max_pages设置得很大爬一两百页建议用random.uniform(2, 5)替代固定延时让请求间隔更像人的操作。3.3 爬虫失败的三个常见信号这套爬虫在真实场景中挂掉九成是下面三个原因。第一个是 Cookie 过期症状是返回的cards列表为空或者出现登录跳转的 HTML处理办法是重新登录微博并替换 Cookie。第二个是关键词 URL 编码错误症状是搜索到的是其他内容你需要用urllib.parse.quote(keyword)手动编码再拼进 URL。第三个是解析字段缺失尤其是created_at返回的是相对时间比如5分钟前存入datetime字段会报错我一般会做个转换函数把这类文本映射为当前时间减去对应偏移或者直接跳过该条记录不给数据库添乱。失败特征排查方向处理方式返回空列表Cookie 失效更新 Cookie 并确认未过期返回 HTML 而非 JSON请求参数被拒绝检查 User-Agent 与 Referer数据库报时间格式错created_at为相对时间写时间解析函数失败则置为空这里特别强调一点爬虫的目的是给舆情分析喂数据不是追求爬得快。算上延时每分钟大约能抓 50 条左右。如果你的运行环境在服务器上建议把爬虫和 Web 服务分开进程跑用nohup python spider/weibo_spider.py spider.log 21 挂后台避免占用终端。4. 负面信息识别Snownlp 结合自定义词表超出 20% 就预警4.1 情感得分计算与阈值判定拿到微博正文后系统进入最核心的环节——判断这条内容是正面还是负面。源码里用的情感分析库是 Snownlp这个库对 140 字以内的短文本准确率尚可它基于朴素贝叶斯训练的产品评论语料。使用方式非常简洁核心代码如下from snownlp import SnowNLP def is_negative(text): s SnowNLP(text) # sentiments 返回 0(负) 到 1(正) 的情绪概率 # 源码设定 0.3 以下为负面 return s.sentiments 0.3这段话背后的逻辑是Snownlp(text).sentiments会输出一个float值越接近 0 代表越消极越接近 1 代表越积极。源码把阈值设在 0.3也就是说情感得分低于 0.3 的微博被判定为负面。为什么是 0.3 而不是 0.5因为微博正文里很多中性内容比如今天食堂开了新窗口价格还行这类表达得分往往在 0.4~0.6 之间。如果阈值设太高中性内容会被误判成负面导致预警频率失控。4.2 双重判定让负面识别更稳只用 Snownlp 会碰到一个问题——网络新词和校园黑话它不认识。比如这波我直接裂开Snownlp 很可能给出 0.6 的正面分因为它觉得句子没有明显的负面词。所以我在这套系统中额外加了一个自定义负面词表放进spider/negative_words.py这个扩展是源码之外的常见做法我一般会把词表机制补齐。negative_words [绝了, 坑爹, 无语, 投诉, 垃圾, 差评, 被骗, 曝光, 维权, 失败] def is_negative_by_words(text): for w in negative_words: if w in text: return True return False def final_negative(text): # 两个条件满足一个就视为负面 return is_negative(text) or is_negative_by_words(text)双重判定的好处是召回率更高。Snownlp 偏向语义判断词表偏向强特征匹配两者是互补关系。实际运行中你会发现像垃圾投诉维权这类词一旦出现内容大概率是负面直接命中词表更高效。但需要注意词表不能太激进无语在网络语境中有时只是中性情绪的宣泄你可以根据自己学校的历史数据去调。4.3 百分比计算与可视化图表联动系统统计负面信息的方式是在某个时间窗口内统计总条数和命中负面的条数计算占比再判断是否超过 20%。这个统计直接来自数据库查询SELECT DATE(publish_time) AS d, COUNT(*) AS total, SUM(CASE WHEN is_negative 1 THEN 1 ELSE 0 END) AS neg FROM t_opinion WHERE publish_time DATE_SUB(NOW(), INTERVAL 7 DAY) GROUP BY DATE(publish_time);这段 SQL 按天分组计算最近七天的每日总量与负面向量。前端页面的饼状图和柱状图数据就来源于此。饼图展示的是负面占比和正面占比的整体分布柱状图则按日期维度展示每日负面数量的变化趋势。源码使用的图表库是 ECharts模板里引用的是echarts.min.js图表配置写在js/analysis.js里核心是pie系列和bar系列。图表查到数据之后前端通过 AJAX 请求/api/negative_rate接口后端返回 JSON 格式的{ rate: 21.5 }这种结构表格区域和图表一起刷新。如果你本地跑起系统但图表一直空白优先检查 Flask 接口是否有跨域问题虽然前后端不分离的情况下跨域概率低但如果你改了端口就可能触发。5. 预警阈值设计与部署跑通的关键动作5.1 20% 阈值预警的触发链路系统的预警判断写在后端工具类里核心逻辑是统计完成后查询负面占比如果超过设定的阈值则向预警记录表插入一条数据同时前端弹窗提示。阈值在源码里是一个全局常量。import time from util import db_helper WARNING_THRESHOLD 20 # 百分比可改成数据库配置 def check_and_warn(opinion_date): sql (SELECT negative_rate FROM t_warning WHERE opinion_date %s) result db_helper.query_one(sql, (opinion_date,)) if result and result[negative_rate] WARNING_THRESHOLD: print(f[提醒] {opinion_date} 负面占比 f{result[negative_rate]}%超过 {WARNING_THRESHOLD}%) # 实际项目中可在这里接入邮件通知或钉钉机器人 return True return False这段代码里db_helper.query_one是从工具类中封装的查询方法返回满足条件的记录字典。触发预警不意味着系统要自动处理舆情它只是把引发注意这个动作程序化了。20% 这个参数你可以调比如你希望对负面信息更敏感就改成 15%但调低后预警会频繁触发如果无法及时处理反而降低系统价值。我一般会给后端加一个配置表把阈值字段存到t_config表中而不是写死在代码里这样管理员可以在页面上灵活调整。5.2 从源码到本地运行的全链路检查点电脑上要依次装好 python 3.6.8、mysql 5.7、Navicat11 和 PyCharm这一步没做好后面所有代码跑起来都会报连接错误。我给出一个快速自检清单你按顺序过一遍能省半天时间。检查项具体命令/操作通过依据Python 版本python --version显示 3.6.8MySQL 启动net start mysql服务已启动依赖安装pip install -r requirements.txt无红字报错建库导入Navicat 运行opinion.sql三张表生成成功服务启动python app.py终端出现Running on http://127.0.0.1:5000登录访问浏览器打开 5000 端口能进系统首页依赖安装这一步有个坑。源码的requirements.txt可能没有锁版本直接pip install会拉到当前最新版而这些包的源码是基于三年前接口写的比如 pandas 的append方法在 2.0 版本就被移除了。我的建议是安装时手动指定版本核心依赖可以这样装pip install flask1.1.4 pymysql0.10.1 snownlp0.12.2 pandas0.25.3 requests2.22.0 beautifulsoup44.8.2为什么锁这几个版本Flask 1.1.4 的路由规则和模板渲染在 Python 3.6 下最稳snownlp 0.12.2 是最后一个能直接导入SnowNLP的版本pandas 0.25.3 有append方法配合 DataFrame 处理 DataFrame 时才不报 AttributeError。5.3 阈值从写死到可配置一处小改动如果你打算把这份源码扩展成真正持续运行的舆情监控系统第一个推荐改造点就是预警阈值参数化。做法是新增一张t_config表里面存键值对然后在check_and_warn里从数据库读取而不是用常量。这样运营人员改了前台配置后后端不用重启因为你每次判断都去查一次数据库。这种设计对当前规模完全够用也避免了引入 Spring Cloud Config 这类重组件。实际操作中我还会把预警处理建议也做成可配置。比如在t_warning表增加advice字段默认值是加强关注核实真实原因当负面内容集中在食堂时可以改成尽快与后勤沟通并公开回应。这些字段在管理后台的预警记录列表页展示出来值班人员看到的不只是一个冰冷的百分比而是带有行动指导的提示系统才算真正闭环。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进