ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

校园舆情管理系统从零搭建:微博爬虫+负面分析+可视化预警

校园舆情管理系统从零搭建:微博爬虫+负面分析+可视化预警 简介面向毕业设计场景的校园舆情管理系统完整源码包基于Python 3.6.8与MySQL 5.7开发集成用户登录密码管理、大学生微博爬取、舆情数据分析、负面信息百分比统计及饼图柱状图可视化预警等功能可作为计算机相关专业毕业设计、课程设计选题原型或二次开发基础。压缩包共256个文件约46.54MB涵盖Python源码、前端JS/CSS/HTML页面、MySQL数据库脚本、说明文档及Word版LW等类型整体可导入PyCharm配合Navicat连接MySQL运行调试。目前已有64人学习使用示例完整度较高便于快速理解舆情系统模块划分与实现思路。通过该资源可获得完整前后端代码含登录、爬虫、分析、预警、可视化等模块、数据库建表与初始化脚本、微博爬虫采集方案及负面舆情预警逻辑同时配套说明文档与LW辅助撰写毕业设计论文。前端图表展示与后台管理界面均已实现项目结构清晰可节省从零搭建系统的时间适合具备Python与Web开发基础的在校学生参考实践。1. 校园舆情管理系统先搞清楚它解决的是谁的问题校园舆情管理系统不是给微博用户用的是给学校宣传部、辅导员或者宿管老师用的。你要盯的是「学生微博里有没有集中出现负面情绪」比如停水、食堂、宿舍维修、考试压力这些话题。靠人工刷微博刷不完靠搜索一个个点也太慢这套源码把「爬取微博 → 负面词命中分析 → 饼图柱状图可视化 → 超过 20% 触发预警」串成一条完整的后台流水线前端用 layui 写管理界面后端是 Python数据落在 MySQL 5.7 里。它特别适合两类人一是做 Python 毕业设计或者课程设计的学生需要一个能现场演示完整业务流程的项目二是学校信息化部门想快速搭一个舆情监控 demo先跑通再谈扩展。我把它拆开跑了一遍下面把环境、爬虫、分析和预警的每个细节都讲清楚。2. 先把环境立起来Python 3.6.8、MySQL 5.7 与登录模块的运行细节拿到源码第一件事不是看代码是先把环境按照说明文档固定下来。这套资源明确写了 Python 版本是 3.6.8数据库是 MySQL 5.7数据库工具用 Navicat11开发工具用 PyCharm。这三个版本号不是随便写的后面很多坑都出在版本匹配上比如 Python 3.6 装不了新版 pyechartsMySQL 5.7 默认 SQL 模式比 5.5 严格Navicat 版本太老连 8.0 的 MySQL 会直接报协议错误。先把环境对齐后面少走弯路。2.1 资源文件构成static 目录里那些 CSS 分别管什么打开压缩包先看 static 目录这里暴露了这套源码前端的技术选型。项目正文里列出的文件很典型layui.css是 layui 框架的核心样式整个后台的栅格布局、按钮、表格样式全部靠它admin.css和style.css是二次开发的自定义样式一般负责左侧菜单和内容区的排版微调font-awesome.min.css是图标字体库菜单栏那些小图标都从这来layer.css是 layui 弹窗组件 layer 的样式登录失败提示、删除确认框都走它laydate.css是日期选择器的样式做舆情时间范围筛选时用得上。这些文件放在一起说明前端是「layui 为主、少量手写 CSS 覆盖」的典型后台管理方案不是前后端分离架构模板渲染的数据都是后端拼好的。再看环境清单你需要准备的依赖大概是这样组件版本要求作用Python3.6.8后端运行环境MySQL5.7业务数据存储PyMySQL0.9.x 及以上Python 连接 MySQL 的驱动requests2.x抓取微博页面BeautifulSoup44.8.x解析 HTML 提取微博内容pyecharts1.9.1锁定生成饼状图和柱状图Navicat11 或以上导入 SQL、查看数据依赖安装用 pip 一条条装就行建议直接用国内镜像源不然 pyecharts 那个包下载速度很折磨人pip install pymysql0.9.3 requests2.22.0 beautifulsoup44.8.2 pyecharts1.9.1这段命令最关键的是pyecharts1.9.1这个版本锁。pyecharts 从 2.0 开始要求 Python 3.7 以上你拿 3.6.8 去装最新版会直接提示Requires-Python 3.7然后给你抛一堆看不懂的报错。我见过不少同学卡在这里以为是环境坏了其实是版本不兼容。pymysql 选 0.9.3 也是因为这个版本对 Python 3.6 支持最稳往上走可能碰到 cryptography 依赖编译的问题。2.2 数据表设计用户表、微博表与预警记录表的关系数据库需要三张核心表用户表管登录微博表存爬到的内容和负面标记预警表记录每次触发预警的快照。用 Navicat 新建一个名为campus_opinion的库字符集一定要选utf8mb4然后执行下面这条建表 SQLCREATE TABLE user ( id int(11) NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 登录账号, password varchar(64) NOT NULL COMMENT MD5加密后的密码, role varchar(10) DEFAULT admin COMMENT 角色, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; CREATE TABLE weibo_post ( id int(11) NOT NULL AUTO_INCREMENT, user_name varchar(100) DEFAULT NULL COMMENT 微博博主, content text COMMENT 微博正文, publish_time datetime DEFAULT NULL COMMENT 发布时间, source_url varchar(255) DEFAULT NULL COMMENT 来源链接, is_negative tinyint(1) DEFAULT 0 COMMENT 是否负面0否1是, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT微博舆情数据表; CREATE TABLE warning_log ( id int(11) NOT NULL AUTO_INCREMENT, weibo_id int(11) DEFAULT NULL COMMENT 关联微博ID, analysis_batch varchar(32) DEFAULT NULL COMMENT 分析批次, negative_percent decimal(5,2) DEFAULT NULL COMMENT 负面百分比, threshold int(11) DEFAULT 20 COMMENT 预警阈值默认20, status tinyint(1) DEFAULT 0 COMMENT 0未处理 1已处理, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT预警记录表;这里有几个细节要提醒第一user是 MySQL 的保留字建表必须加反引号否则直接语法错误第二is_negative和threshold的字段注释里都设计了默认值is_negative默认 0threshold默认 20这就是摘要里说的「20% 以上就预警」的落点第三三张表之间不建议强行加外键约束舆情数据会反复批量插入和删除外键在这种分析场景下只会拖慢批量操作用程序逻辑保证关联就够了。密码字段存varchar(64)是为了容纳 MD5 的 32 位十六进制输出如果你用的加盐方案再加 16 位64 的长度也够。2.3 登录与密码管理session 机制和 MD5 加密的实现方式登录模块的逻辑不复杂就是「表单提交 → 密码 MD5 → 数据库比对 → 写入 session → 跳转首页」。这个流程里最容易翻车的是密码加密方式。项目正文里写了「对登录的用户密码管理登陆后可以进行相关操作」如果密码明文存数据库评审老师一眼就能看出来问题。用 MD5 是最省事的做法虽然不算绝对安全但毕设场景足够。import hashlib from flask import Flask, request, redirect, session, url_for app Flask(__name__) app.secret_key your_secret_key_here def md5_encrypt(text): md5 hashlib.md5() md5.update(text.encode(utf-8)) return md5.hexdigest() app.route(/login, methods[POST]) def login(): username request.form.get(username, ).strip() password request.form.get(password, ) if not username or not password: return 账号和密码不能为空 enc_pwd md5_encrypt(password) sql SELECT * FROM user WHERE username%s AND password%s user query_one(sql, (username, enc_pwd)) if user: session[user_id] user[id] session[username] user[username] return redirect(url_for(index)) return 账号或密码错误query_one是封装好的查询函数内部用 pymysql 连接数据库参数用%s占位符传值注意这里不要自己拼字符串否则 SQL 注入直接被打穿。md5_encrypt先encode(utf-8)再生成摘要这一步很多人会漏hashlib.md5()只能接收字节串直接传字符串会报TypeError。app.secret_key是 Flask 写 session 的签名密钥必须手动设置去掉它 session 写入直接失效。前端配合 layui 的 form 组件和 layer 弹窗登录失败弹出红字提示成功就跳转到管理后台首页这是整个系统里最不需要动脑但最不能出错的一环。3. 微博爬虫与负面分析从拿到 cookie 到输出百分比的全过程爬虫模块是这套系统的价值核心。项目摘要里写的是「爬取大学生微博比如喀什大学微博或者其他大学生微博主要看是否能爬」所以爬虫的目标是「指定账号的微博内容」或者「指定关键词的搜索结果」而不是微博全站数据。爬虫部分要解决三个问题怎么把微博页面稳定地抓到本地、怎么过滤掉广告和转发的噪声内容、拿到文本后怎么算负面百分比。3.1 爬虫模块的选型为什么用 requests 而不是 scrapy很多同学上来就想用 scrapy但在这个场景下我建议用requests BeautifulSoup的传统组合。理由很实际第一scrapy 的安装依赖重Python 3.6.8 环境下 Twisted 偶尔会有编译问题而 requests 是纯 Python 包装完就能用第二这套源码的爬虫只涉及一个站点、一个页面模板不需要分布式、不需要中间件爬取量在几十到几百条这个量级scrapy 的框架复杂度反而成了负担第三requests 写起来直观导师问起来也容易说清楚逻辑。爬虫的内部逻辑分四步拼接搜索 URL → 带上登录 cookie 发请求 → 用 BeautifulSoup 解析卡片 → 清洗文本后入库。微博网页端的搜索页s.weibo.com需要登录状态才能看完整内容所以 cookie 必须从浏览器里复制放进配置文件或者环境变量里。核心代码大致是这个形态import requests from bs4 import BeautifulSoup import time import re COOKIE 你的微博登录cookie # 浏览器登录后从开发者工具复制 HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Cookie: COOKIE } def fetch_weibo(keyword, pages3): rows [] base_url https://s.weibo.com/weibo for page in range(1, pages 1): params {q: keyword, page: page} resp requests.get(base_url, paramsparams, headersHEADERS, timeout10) if resp.status_code ! 200: print(f第{page}页请求失败状态码{resp.status_code}) continue soup BeautifulSoup(resp.text, html.parser) cards soup.select(div.card-wrap) if not cards: break for card in cards: user_node card.select_one(a.name) text_node card.select_one(p.txt) if user_node and text_node: row { user_name: user_node.text.strip(), content: clean_text(text_node.text.strip()), source_url: https://s.weibo.com card.get(mid, ) } rows.append(row) time.sleep(2) # 控制频率别把页面打崩 return rows这段代码有两个关键参数pages控制翻页深度默认 3 页足够演示不要贪多页面请求太频繁会被微博反爬策略盯上time.sleep(2)是请求间隔每页之间停两秒这是爬虫的自觉也是保证「主要看是否能爬」这个需求能持续跑下去的前提。clean_text是文本清洗函数去掉提及、#话题#、短链接和多余的空白字符。跑起来之后如果发现cards列表为空多半是网页结构调整了card-wrap这个类名按 F12 查看当前页面结构后替换掉选择器就行。3.2 负面信息的判定负面词库与百分比计算的实现拿到微博文本之后负面分析模块要把这些文本和负面词库比对。常见的做法是用 jieba 分词后再匹配词库但在这个项目里我建议直接用「包含匹配」也就是把负面词库放在一个 Python 列表里遍历每一条微博的文本只要包含任何一个负面词就算负面。原因是舆情分析这种场景误判影响很大分词会把「不崩溃」拆成「不」「崩溃」然后被误判为负面直接破坏百分比统计而简单包含匹配配合一个质量够用的负面词库在毕设规模的数据量上精度完全够而且代码逻辑一眼能看懂。负面词库示例[崩溃, 失望, 投诉, 危险, 愤怒, 离谱, 停水, 断网, 涨价, 围堵, 受伤, 拉黑]。这个列表应该放在独立配置文件里方便后期扩充。NEGATIVE_WORDS [崩溃, 失望, 投诉, 危险, 愤怒, 离谱, 停水, 断网] def analyze_negative(rows): total len(rows) if total 0: return {total: 0, negative_count: 0, percent: 0} negative_count 0 neg_rows [] for row in rows: hit_words [w for w in NEGATIVE_WORDS if w in row[content]] if hit_words: negative_count 1 row[hit_words] ,.join(hit_words) neg_rows.append(row) percent round(negative_count / total * 100, 2) return { total: total, negative_count: negative_count, percent: percent, neg_rows: neg_rows }percent保留两位小数方便后面和 20 做比较。这里有个容易忽略的边界问题如果rows为 0直接返回 0不能做除法不然ZeroDivisionError在演示现场会非常尴尬。返回的neg_rows列表带有命中的负面词明细这个数据可以渲染到前端表格里让老师看到「哪条微博、因为哪个词被判了负面」比只给一个百分比说服力强得多。预警逻辑也很直白if percent 20: insert_warning_log(...)预警记录写入warning_log表后台首页读取未处理记录并高亮显示。3.3 饼状图与柱状图的生成pyecharts 1.9 的参数细节与版本差异可视化模块用的是 pyecharts饼状图展示「负面与非负面的占比」柱状图展示「不同微博账号的负面率对比」。这就是摘要里面写的「饼状图柱状图对负面信息进行百分比分析」。这里必须再强调一次版本问题Python 3.6.8 只能跑 pyecharts 1.x所以下面代码里的from pyecharts.charts import Pie这种写法是 1.9 的语法如果你拿到的是老教程里的from pyecharts import Pie那套的是已经更新得很老的 0.5 版本接口格式完全不一样两个版本混用会直接ImportError。from pyecharts.charts import Pie, Bar from pyecharts import options as opts def build_pie(total, negative_count, output_pathtemplates/pie.html): pie Pie() data [ (负面信息, negative_count), (正常信息, total - negative_count) ] pie.add(, data, radius[40%, 70%]) pie.set_global_opts(title_optsopts.TitleOpts(title校园微博负面信息占比)) pie.set_series_opts(label_optsopts.LabelOpts(formatter{b}: {c} ({d}%))) pie.render(output_path) def build_bar(account_stats, output_pathtemplates/bar.html): bar Bar() accounts [item[user_name] for item in account_stats] percents [item[percent] for item in account_stats] bar.add_xaxis(accounts) bar.add_yaxis(负面率(%), percents) bar.set_global_opts(title_optsopts.TitleOpts(title各微博账号负面率对比)) bar.render(output_path)radius[40%, 70%]是环形饼图的经典参数内径 40% 外径 70%中间会留一个空洞视觉效果比实心饼图好很多。formatter{b}: {c} ({d}%)里的{d}是 pyecharts 内置的百分比占位符会自动在饼图扇区上标注占比。render直接输出一个独立的 HTML 文件生成到templates目录然后在 layui 后台里用 iframe 或者模板引入加载。这里注意 render 的输出路径必须和 Web 服务的模板目录一致否则图表生成了但页面访问不到这个坑我在第 4 章详细讲。生成的图表 HTML 文件有几百 KB因为 pyecharts 会把整套 echarts 的 JS 库内嵌进去这是正常现象不要误以为项目文件损坏了。4. 避坑与排查环境、爬虫、数据展示三层的真实记录这套源码我在拆解和复现的过程中踩了不少坑下面这些问题是拿到这套资源后最高频的翻车现场每一条都是「现象 → 原因 → 解决」的真实记录建议直接存下来对照排查。4.1 环境层MySQL 启动失败、SSL 连接错误与字符集乱码第一个高频问题启动后页面报error 2002 (HY000): cant connect to local mysql server through socket /tmp/mysql.sock。现象是后端连不上数据库报错信息提到 socket 文件路径。原因是 MySQL 服务没启动或者 pymysql 默认走 Unix socket 连接但路径对不上。解决方式分两种确认 MySQL 服务已启动Windows 下在服务管理器里启动mysql57Linux 下执行systemctl start mysqld如果服务已启动还报错就在数据库连接代码里把host参数改成127.0.0.1强制走 TCP 协议而不是 socket这就绕开了 socket 路径不匹配的问题。第二个问题是用 Navicat 连接时报MySQL SSL connection error。现象是连接时提示 SSL 协议错误明明账号密码没错却连不上库。原因是 Navicat 11 默认 SSL 选项和 MySQL 5.7 的 SSL 配置不兼容。解决很简单在 Navicat 连接配置的「SSL」页签里把「使用 SSL」去掉本地开发环境下不需要加密连接关掉就好。顺便说一嘴MySQL 8.0 的默认加密规则是caching_sha2_passwordNavicat 11 不支持所以这套源码老老实实用 5.7别往上换版本。第三个问题微博文本存入数据库后出现乱码或报错Incorrect string value。现象是爬回来的微博内容里有 emoji 表情插入数据库时直接抛字符集错误。原因是表用了utf8字符集而标准的utf8在 MySQL 里最多存 3 字节emoji 占 4 字节必须用utf8mb4。解决方式是在建库时就指定CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci如果已经建好了就用ALTER TABLE weibo_post CONVERT TO CHARACTER SET utf8mb4补救。这是爬虫类项目最普遍的暗坑微博正文里 emoji 出现频率极高不处理必翻车。4.2 爬虫层cookie 失效、请求频率与文本清洗爬虫模块最常见的翻车现场是爬了十几页数据后就拿不到新内容了或者直接返回 414。现象是前几页正常后面cards为空或者请求失败。原因是微博网页端对未登录或高频请求有反爬限制cookie 过期或者 IP 被临时限制。解决方式是重新登录微博从浏览器开发者工具里复制新的 cookie 替换配置文件里的旧 cookie同时把time.sleep的间隔从 2 秒提高到 3~4 秒一次演示会话爬 3 页左右就够了没必要硬刚反爬。记住这套系统的定位是「验证舆情分析流程」不是做数据采集平台控制请求频率是长久之计。文本清洗是另一个容易被忽视的点。爬回来的微博正文里混着大量噪声用户名、#话题名#、网页链接、多余空格和换行。如果不清洗干净负面词匹配的准确率会被直接拉低。比如「#停水通知# 明晚停水大家做好准备」这条微博本身是通知类信息但因为包含「停水」两个字会被误判为负面如果清洗时把#话题#去掉再匹配误判率会低很多。常见的做法是连续用几个正则表达式替换掉这些内容再配合strip()去掉首尾空白。4.3 数据与展示层分组查询报错、样本量失真与图表覆盖后台统计页最常报的错是Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column。现象是执行按账号分组的统计 SQL 时MySQL 直接拒绝执行并抛出上面的错误。原因是 MySQL 5.7 默认开启了ONLY_FULL_GROUP_BY模式SELECT 里的非聚合列必须都出现在GROUP BY里。解决方式有两个修改 SQL把 select 的列全部收敛成聚合函数比如COUNT(*)、AVG()、MAX()或者临时关闭这个模式执行SET SESSION sql_mode(SELECT REPLACE(sql_mode, ONLY_FULL_GROUP_BY, ))。我建议优先改 SQL不要动全局配置不然换一台机器跑又报错。样本量问题不是说代码错而是统计意义错。比如只爬下来两条微博一条负面占比就是 50%远超 20% 的预警线但两条数据的 50% 根本没有统计意义。解决方式是在分析函数里加一个最少样本量判断比如MIN_SAMPLE_COUNT 10样本低于 10 条时不在页面上展示百分比只在日志里提示「样本不足」。我在第 3 章的analyze_negative里特意强调rows为 0 的边界这里说的就是同一个思路的延伸。最后一个坑饼图和柱状图生成的 HTML 文件互相覆盖页面只显示一张图。现象是先后调用了两次render()第二次生成的 HTML 把第一次的覆盖了。原因是两次调用都输出到同一个路径templates/pie.html。解决方式是给不同图表不同的输出路径比如templates/chart_pie.html和templates/chart_bar.html然后在后台页面的不同 tab 或不同区域分别引入。如果想把两张图画在同一个页面里可以用Grid组件组合但毕设演示阶段分两个页面展示更清晰。5. 从 20% 预警到可配置阈值验证方法与一个调参习惯代码都跑通之后你一定要做一次端到端的验证不能只在浏览器里点两下页面就完了。我一般会走一遍「手工构造数据 → 触发分析 → 确认预警写入 → 检查图表刷新」的完整链路。手动往weibo_post表里插 10 条数据其中 3 条明显包含负面词然后重新触发分析INSERT INTO weibo_post (user_name, content, is_negative) VALUES (喀什大学, 今天食堂饭菜不错, 0), (喀什大学, 宿舍停水三天了真的很崩溃, 1), (喀什大学, 期末考试安排终于出了, 0), (某学生代表, 图书馆占座现象太离谱了, 1), (某学生代表, 社团招新活动很热闹, 0);插入后重新跑一次爬虫分析任务正常情况是负面百分比算出来是 40%超过 20% 阈值warning_log表里多一条未处理的预警记录后台首页的预警区域出现红字提示。如果百分比没变或者预警没触发检查两处分析任务有没有真正执行插入后的新数据以及threshold字段是不是被写死成了别的值。这个验证过程走完整套系统的核心逻辑才算真正闭环。验证通过之后我建议你做一个很小的扩展把 20% 的预警阈值从代码里抠出来放进一张配置表或者配置文件里。原因是不同学校、不同时段的舆情敏感度差别很大考试周 10% 的负面率可能就要重点关注假期 20% 都未必需要预警。我一般会加一个config表字段就是key和value程序启动时读一次后台页面留一个输入框让管理员自己改阈值这样演示的时候你还能现场调参数给老师看比拍死在代码里的WARNING_THRESHOLD 20灵活得多。从那以后我每次拿到一套新的 Python 毕设源码第一件事都是查版本依赖和数据库字符集这两个地方稳了后面写逻辑才有意义。希望这份拆解帮到你按着这个顺序跑应该一两个小时就能把这套校园舆情管理系统完整立起来。本文还有配套的精品资源点击获取
返回列表