☰
基于Python与Django的网络舆情分析系统及DNN/BERT双模型对比
2026/10/31 2:24:31 网站建设 项目流程

简介:这份毕业设计文档围绕基于 Python 与 MySQL 的网络舆情分析系统展开,面向网络管理部门、计算机专业学生及需要舆情监测案例的开发者,提供从需求分析、系统设计到功能实现的论文参考。压缩包内共 1 个 docx 文件,约 1.73MB,主体为可直接查阅的毕业论文正文,包含中英文摘要、引言、系统设计、数据库设计与功能模块说明等内容,结构完整,便于对照理解课题背景与实现思路。文中重点讨论言论分析、言论管理、用户管理等模块,并说明如何以城市或地区为关键词进行舆情检索、负面评论查看以及用户权限管理,兼顾隐私保护与言论自由,对同类舆情系统开发亦有参考价值。已有 1262 人学习,适合用于毕业设计选题、论文框架参考、系统功能梳理、课程设计借鉴和答辩准备。

1. 需求先立住:这套网络舆情分析系统到底在解决谁的麻烦

答辩现场被追问最多的一句往往是:直接用关键词过滤不就行了,为什么还要上模型?这个问题恰恰是网络舆情分析系统这套毕设的分水岭。真实场景里,社区运营岗一天要面对几千条公开评论,纯靠敏感词命中既漏判反讽和隐晦表达,也误伤大量正常讨论,最后人力全耗在人工复核上。这套基于 Python 与 MySQL 的网络舆情分析系统,把“采集—分类—预警—处置”串成一条闭环:文本进了文本框自动给出正面、中性、负面倾向,负面内容落进待处理队列,管理员按状态流转处置,同时用 DNN 与 BERT 两套模型跑同一批数据做对比,把准确率和耗时摆到页面上。它适合正在做舆情方向毕设或课设的学生,也适合需要一份能交付的源码加论文骨架的从业者参考。

2. 技术选型与总体架构:B/S 三层结构与五个模块的职责边界

选型这件事在毕设里常被写成“因为老师要求”,其实真正决定架构的是使用者是谁。这套系统的使用者是坐在办公室里的运营与内容管理岗,他们要的是打开浏览器就能看数据,而不是在每台机器上装一个客户端。B/S 结构把升级成本压到服务端一次发布,前端只保留 HTML5、CSS 和少量 JavaScript 做布局与交互,这也是原方案里明确采用 B/S 而没有走 C/S 的原因。下面从框架取舍、模块划分、工程落地三个层面把架构讲透,方便你直接对照改。

2.1 为什么是 Django 而不是裸写脚本或换 Flask

Python 生态里做 Web 后台有三条常见路线:纯脚本加模板拼装、Flask 轻框架、Django 全家桶。纯脚本的写法在状态管理和权限上会迅速失控,做到用户管理模块时基本要重写一遍;Flask 灵活,但 ORM、后台管理、认证授权都得自己选型拼装,对一个周期只有两三个月的毕设来说,时间全耗在选件上。Django 的 MTV 分层、自带 ORM、内置 auth 权限体系和 admin 后台,恰好对上了这套系统“用户/管理员两级角色 + 多表增删改查”的需求,写起来最省事。

对比维度纯脚本FlaskDjango
路由与视图组织手写 if/else需自行组织自带 URLconf
数据库操作拼接 SQL需选 ORM内置 ORM 与迁移
用户认证与权限从零实现需装扩展内置 auth
后台管理界面无需插件内置 admin
适合的交付节奏演示级中小型中大型多模块

选 Django 不代表它没有代价:它对目录约定比较强势,模型改动后要生成迁移,初学者容易在改动模型后忘了makemigrations而对着旧表结构调试半天。这一点在第 5 章的避坑部分会展开。

2.2 五个模块的职责划分与一次请求的完整流转

系统按功能切成五块,每块职责单一,前后端调用关系清晰:用户与权限模块负责注册、登录、角色判定;文本分析模块负责接收文本、调用模型、产出标签与置信度;文本管理模块负责结果落库、状态流转与检索;对比分析模块负责把两套模型在同一批数据上的结果做聚合展示;站内新闻与首页统计模块负责公告维护和总量指标展示。管理员比普通用户多一条线,可以进入用户管理页做新增、检索、修改与删除。

一次典型的请求走完是这样:浏览器发出表单提交,请求经 URLconf 匹配到对应视图函数,视图先做登录态和角色校验,再从请求体里取文本;文本经过清洗后交给模型服务层,服务层返回标签、置信度和两套模型各自的耗时;视图把结果写进分析结果表,同时更新文本管理表的状态字段;最后视图把结构化数据塞进模板上下文,渲染成页面返回。把模型调用单独抽成 service 层很关键,不要在视图函数里直接写模型加载代码,否则首页、文本管理页但凡引用一次就会重复加载权重,8GB 内存的开发机很容易直接卡死。

提示:模型加载写成模块级单例,进程启动时加载一次,视图里只调用推理方法。开发阶段可以用懒加载,避免改一行模板就重跑一遍权重。

2.3 开发环境与工程目录骨架

原方案给的开发环境是个人笔记本,处理器 i5 十代、内存 8GB、500G 固态,浏览器用 Chrome,编辑器用 Sublime Text 配 Navicat 管库。这套配置跑 Django 加一个轻量分类模型完全够用,但要注意 BERT 训练阶段会吃内存,微调建议放到带显卡的机器或分批小样本跑,本机只做推理验证。

# 创建虚拟环境,把依赖锁在项目内,避免和系统 Python 打架 python -m venv venv source venv/bin/activate # Windows 下用 venv\Scripts\activate # 依赖一次性装齐,装完立刻导出锁定版本 pip install django pymysql jieba scikit-learn torch transformers pandas pip freeze > requirements.txt

工程目录按 Django 约定拆,apps下每个业务一块,模型推理单独放services,静态资源与模板分开,这样后期加模块不用动老代码:

opinion_system/ ├── manage.py ├── requirements.txt ├── opinion_system/ # 全局配置:settings、urls、wsgi ├── apps/ │ ├── accounts/ # 注册登录、角色与权限 │ ├── analysis/ # 文本分析、双模型推理 │ ├── manage_text/ # 文本管理与状态流转 │ ├── compare/ # 对比分析与图表数据接口 │ └── news/ # 站内新闻与首页统计 ├── services/ │ ├── cleaner.py # 文本清洗 │ ├── dnn_model.py # DNN 基线推理 │ └── bert_model.py # BERT 推理封装 ├── templates/ └── static/

这套目录带来的好处在联调阶段最明显:清洗逻辑改了只动cleaner.py,两个模型的输出格式统一在services层收敛,视图只认“标签 + 置信度 + 耗时”这三元组,换模型不牵动业务代码。数据库连接在settings.py里集中配,密码走环境变量,别硬编码进仓库。

3. 数据库设计:五张核心表把“分析”和“处置”分开存

数据库这一步偷懒,后面一定返工。原方案里有一句很实在的教训:先设计库再写代码,否则中途想加权限判定就要加表加列,改完结构所有代码跟着改。这套系统按功能拆成用户信息、文本分类、文本信息、文本管理信息、站内新闻五张表,我在实现时又补了一张分析结果表,理由在 3.3 说。先看表结构怎么定。

3.1 五张核心表的字段取舍

用户信息表以用户 ID 为主键,存用户名、密码、手机号、角色、注册时间和最后登录时间。密码必须存哈希值,用 Django 自带的set_password处理,明文入库是这类系统最容易被追问的漏洞。角色字段区分普通用户与管理员,不要靠“用户名是不是 admin”这种土办法判断权限。

文本分类表只做一件事:管理言论的类别,比如民生、交通、教育、其他,配一个分类名和排序号即可,方便后期扩展。文本信息表存采集到的原始内容,字段包括文本 ID、分类 ID、正文、来源渠道、发布时间和入库时间。文本管理信息表存处置侧的信息,核心是状态字段,取值限定为未处理、处理中、已处理三种,另加处理人、处理时间和备注。站内新闻表存公告标题、正文、发布时间和发布管理员,只有管理员能写。

表名主键关键字段说明
user_infouser_idusername、password_hash、phone、rolerole 区分 user / admin
text_categorycategory_idcategory_name、sort_no分类可扩展
text_infotext_idcategory_id、content、source、publish_time原始文本,不存结论
text_managemanage_idtext_id、status、handler_id、handle_time状态三值枚举
newsnews_idtitle、content、publish_time、admin_id仅管理员可写
analysis_resultresult_idtext_id、model_name、label、score、cost_ms一条文本两行记录

这张表最有价值的一列是status。很多同学把“分析结果”和“处置进度”塞进同一张表,结果做文本管理页时发现按状态筛选和按模型筛选混在一起,SQL 越写越绕。拆开之后,分析只管产出结论,管理只管推进流程,两条线各走各的,页面上的筛选条件也能干净地组合。

3.2 建表 SQL 与索引选择

建表时把约束写死在库层,比在代码里到处 if 判断可靠得多。下面这段是核心三张表的建法,字符集统一 utf8mb4,否则评论里的表情符号会直接入库失败:

CREATE TABLE text_info ( text_id BIGINT PRIMARY KEY AUTO_INCREMENT, category_id INT NOT NULL, content TEXT NOT NULL, content_hash CHAR(32) NOT NULL COMMENT '正文MD5,用于去重', source VARCHAR(32) DEFAULT 'web', publish_time DATETIME NULL, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_hash (content_hash), -- 同一段文本只入一次 KEY idx_publish (publish_time) -- 按时间倒序翻页 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE text_manage ( manage_id BIGINT PRIMARY KEY AUTO_INCREMENT, text_id BIGINT NOT NULL, status TINYINT NOT NULL DEFAULT 0 COMMENT '0未处理 1处理中 2已处理', handler_id BIGINT NULL, handle_time DATETIME NULL, remark VARCHAR(255) DEFAULT '', KEY idx_status (status), -- 待处理队列的核心索引 KEY idx_text (text_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

content_hash这一列是爬取场景的血泪经验:同一段评论会被多个入口重复抓到,没有唯一键,待处理队列里会堆满重复项,运营岗第一眼就觉得这系统不靠谱。idx_status则是为待处理队列服务的,页面上默认只查status = 0的记录,数据量上万后有没有这个索引,翻页速度差一个数量级。

3.3 为什么建议多加一张分析结果表

原方案的文本管理表同时承载“管理信息”,但双算法对比分析必须要保留每个模型自己的输出。如果把两个模型的标签塞进同一行的两个字段,后面想加第三个模型就得改表结构;更麻烦的是当两模型结论不一致时,你需要记录各自置信度和耗时来判断谁更可信。

通常的做法是单独建一张analysis_result,一条文本对应两行,每行带模型名、标签、置信度、耗时和创建时间。这样对比分析页的聚合查询就是一句按model_name分组计数,饼图柱状图的数据源直接出来,不用在 Python 里做二次拼装。代价是数据量翻倍,但对毕设级别的数据规模完全无压力,换来的是模型可插拔和结果可追溯,这笔账很划算。

4. 核心功能实现:文本分析链路与双模型对比怎么落地

功能实现这块最容易被写成“界面截图加一句实现了”,真正能抄作业的是链路上的每个转换点。这套系统的分析链路是:原文进 → 清洗 → 分词 → 双模型各出一份结论 → 一致性判定 → 落库 → 页面呈现。下面按这个顺序拆,代码都给可运行的最小骨架。

4.1 文本清洗与分词:脏数据决定了模型上限

模型效果差,八成不是模型的问题,是喂进去的文本没洗。社交平台的评论里混着 HTML 标签、链接、@ 提及、连续表情和大量重复字符,直接分词会切出一堆无意义片段。

import re import jieba from services.stopwords import STOP_WORDS # 自建停用词表,一行一词 def clean_text(raw: str) -> str: """按顺序清洗:去标签 -> 去链接 -> 去提及 -> 压重复字符""" text = re.sub(r"<[^>]+>", "", raw) # 去掉 HTML 标签 text = re.sub(r"https?://\S+", "", text) # 去掉链接 text = re.sub(r"@[\w\u4e00-\u9fa5]+", "", text) # 去掉 @提及 text = re.sub(r"(.)\1{3,}", r"\1\1", text) # 连续重复压到两次 text = re.sub(r"\s+", " ", text).strip() return text def tokenize(text: str) -> str: """分词并过滤停用词,返回空格分隔的串,供向量化使用""" words = jieba.lcut(text) return " ".join(w for w in words if w.strip() and w not in STOP_WORDS)

清洗顺序不能乱:先去掉标签再去链接,否则标签里的链接属性会残留半截。重复字符压缩这一步看着不起眼,实际能显著减少“好好好好好”这类文本对词频统计的干扰。分词用 jieba 的精确模式,停用词表至少放进去语气词、代词和平台通用词,长度控制在一两百个词就够,不必追求大而全。

4.2 DNN 基线的训练与推理

DNN 这条线承担两个作用:一是给 BERT 当对照基线,二是训练成本低,项目演示时能快速给出结果。落地用 TF-IDF 向量化接一个多层感知机,8GB 内存的开发机也能在几分钟内训完。

from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.neural_network import MLPClassifier from sklearn.pipeline import Pipeline import joblib def train_dnn(corpus, labels): """corpus 是分词后的文本列表,labels 取 0负面/1中性/2正面""" pipe = Pipeline([ ("tfidf", TfidfVectorizer(max_features=20000, ngram_range=(1, 2))), # 两层隐藏层,early_stopping 防止小样本过拟合 ("clf", MLPClassifier(hidden_layer_sizes=(256, 128), activation="relu", alpha=1e-3, early_stopping=True, max_iter=200, random_state=42)), ]) pipe.fit(corpus, labels) joblib.dump(pipe, "models/dnn_pipeline.pkl") return pipe def predict_dnn(text: str) -> dict: pipe = joblib.load("models/dnn_pipeline.pkl") proba = pipe.predict_proba([tokenize(clean_text(text))])[0] idx = int(proba.argmax()) return {"label": idx, "score": float(proba[idx]), "model": "dnn"}

max_features控制在两万以内是为了压住内存,ngram_range=(1,2)让“服务 态度”这类二元搭配也能进特征。early_stopping=True会在验证集不涨时提前停,小样本数据集上不加这一项,训练集准确率能到 0.99、测试集只有 0.6,这种数字放进论文只会被追问。

4.3 BERT 微调与推理封装

BERT 这条线负责提升上限。做法是先拿预训练权重在自有标注数据上做几轮微调,把分类头改成三分类,然后固定权重只做推理。工程上必须解决一件事:模型只加载一次。

import torch from transformers import AutoTokenizer, AutoModelForSequenceClassification class BertService: """进程内单例,避免每个视图都重新加载权重""" _instance = None def __new__(cls, model_dir="models/bert_cls"): if cls._instance is None: cls._instance = super().__new__(cls) cls._instance.tok = AutoTokenizer.from_pretrained(model_dir) cls._instance.model = AutoModelForSequenceClassification.from_pretrained(model_dir) cls._instance.model.eval() return cls._instance def predict(self, text: str) -> dict: inputs = self.tok(text, truncation=True, max_length=128, return_tensors="pt") with torch.no_grad(): # 推理阶段关闭梯度 logits = self.model(**inputs).logits proba = torch.softmax(logits, dim=-1)[0] idx = int(proba.argmax()) return {"label": idx, "score": round(float(proba[idx]), 4), "model": "bert"} bert_service = BertService() # 模块导入时完成一次加载

max_length=128对评论类短文本足够,设到 512 只会让推理时间翻几倍。torch.no_grad()一定要加,否则显存和内存都会因为保留计算图而暴涨。单例的写法有个副作用:开发时改了模型文件需要重启进程才生效,别对着旧结果怀疑人生。

4.4 一致性判定与对比分析页的数据来源

两个模型跑完,真正的设计点在“结论不一致怎么办”。我一般按这个规则处理:两个模型标签相同就取 BERT 的结果作为主结论,因为它在语义理解上更强;标签不同则把状态标为“待人工复核”,在文本管理页用不同颜色高亮。这条规则让系统多了一个人工兜底出口,也让答辩时“模型不准怎么办”这个问题有了正面答案。

对比分析页的图表数据直接用分组聚合查出来,不绕 Python 循环:

from django.db.models import Count, Avg from apps.analysis.models import AnalysisResult def compare_data(): rows = (AnalysisResult.objects .values("model_name", "label") .annotate(cnt=Count("id"), avg_cost=Avg("cost_ms"))) # 输出形如 {('dnn',0): {'cnt': 42, 'avg_cost': 18.3}, ...} return {(r["model_name"], r["label"]): r for r in rows}

values加annotate走的是数据库聚合,几千条数据毫秒级返回,比把 QuerySet 拉到内存里再for循环快得多。饼图用正负中三类占比,柱状图用两模型的平均耗时和准确率对照,前端拿到的是两组扁平数组,直接用图表库渲染。

文本管理页的状态流转也要卡住权限:普通用户只能改自己录入的待处理项,管理员能改全部并做删除。这个判断放在视图入口处,不要指望前端隐藏按钮,接口是能被直接调用的。

5. 避坑与常见问题:五个最容易在上线前翻车的地方

这套系统从本地跑通到能被别人点开演示,中间踩的坑基本集中在这几处。每一条都按现象、原因、解决三段写,遇到同类问题时可以逐条对照排查。

5.1 依赖版本漂移导致环境无法复现

现象:本机能跑,换台机器或者重装系统后,ImportError、模型加载报错、中文乱码轮番出现。原因是没有锁定版本,pip install拉的是最新版,某个库的大版本升级改了接口。解决方式很简单:装好当天立刻pip freeze > requirements.txt,把文件一起放进交付物里,复现时用pip install -r requirements.txt装,别单独装某一个包。提交源码和论文时把requirements.txt和启动说明放在同一层目录,评审照着跑一遍就能起来。

5.2 中文编码与分词词典缺失

现象:评论入库后出现问号或方块,分词把“稳控”“舆情”这类词切成单字。原因是数据库字符集用了 utf8 而非 utf8mb4,且 jieba 通用词典收不到业务专有词。解决办法是建库建表统一 utf8mb4,Django 的DATABASES配置里显式写上字符集;同时给 jieba 加一份自定义词典,把业务里高频出现的专有名词补进去,加载时用jieba.load_userdict()。这一步做完,词频特征的质量会有肉眼可见的提升。

5.3 模型重复加载拖垮首页响应

现象:首页打开要等十几秒,任务管理器里内存直接顶满。原因是在视图函数或模板标签里实例化了模型对象,每来一次请求就加载一遍权重。解决方式是所有模型统一走单例,放在services层,模块导入时完成一次加载;首页这种高频页面只读数据库统计结果,不触发推理。如果确实需要预热,就在AppConfig.ready()里做,别放在视图里。

5.4 权限只做在页面上,接口裸奔

现象:普通用户登录后看不到用户管理入口,但换个请求地址就能拉到全部用户列表。原因是权限只在前端做了隐藏,后端视图没有校验。解决办法是在每个敏感视图的入口统一做角色判断,封装成装饰器,凡是管理类接口都挂上;同时把 Django 自带的 CSRF 保护开着,表单提交带上 token。这类问题在答辩时被点出来,比功能少一个扣分重得多。

5.5 采集与展示的数据合规没想清楚

现象:演示数据里出现真实账号名、手机号、身份证号片段,被追问数据来源时答不上来。原因是对数据边界没有约束。通常的做法是:演示库全部用脱敏后的模拟数据,正文里的手机号、证件号、住址做掩码处理,界面上不展示可定位到个人的信息;采集环节只处理公开可见内容,并在论文的需求分析章节写清楚数据范围和脱敏规则。这一条既是技术问题也是方案完整性问题,写到论文里反而是加分项。

6. 验收与答辩:用测试用例表和双模型对比数据把方案讲圆

功能做完不等于方案完成,验收这一步要能把“系统能跑”翻译成“指标可查”。我的做法是准备两张表:一张功能测试用例表,逐条覆盖五模块的主流程和异常分支;一张模型对比表,放两套算法在同一测试集上的准确率、F1 和平均耗时。前者证明系统完整,后者证明选型合理,两张表摆出来,答辩时大部分追问都能落到你已经准备好的数据上。

功能测试用例按模块列,每条写清输入、预期和实际结果,异常分支不要漏,比如未登录直接访问分析接口、重复提交同一段文本、状态从“已处理”回退到“未处理”:

用例编号模块输入 / 操作预期结果
TC-01注册登录用户名重复提示已存在,不写库
TC-02文本分析提交含负面倾向文本返回负面标签与置信度
TC-03文本分析重复提交同一段文本命中唯一键,不产生重复记录
TC-04文本管理将未处理项置为已处理状态更新,记录处理人与时间
TC-05对比分析打开对比页饼图与柱状图数据非空且一致
TC-06用户管理普通用户调用删除接口返回无权限,数据不变

模型对比表建议跑同一批不少于两百条的标注样本,两模型各跑一遍,记录准确率、负面类 F1 和平均单条耗时。DNN 基线通常耗时低、准确率中等,BERT 准确率高但耗时明显更长,这张表的价值在于把“为什么保留两套模型”讲成一句有数据支撑的话:低算力场景走 DNN,要求精度时走 BERT,不一致的样本进人工复核队列。

答辩时有个很实用的技巧:不要从技术栈开始讲,从一条负面评论的生命周期开始讲。它怎么进来、被清洗成什么样、两个模型各给了什么结论、不一致时怎么处理、最后在管理页怎么被关闭。讲完这条线,评委自然知道你每个模块都动过手。真正被问住的往往不是模型原理,而是“你这个状态字段为什么这么设计”“重复数据怎么处理”这类细节,而这些恰好是前面几张表能答上的。

我最早做类似系统时吃过一次亏:功能全做完了,演示前一天换了个库版本,requirements.txt没锁,环境直接起不来,通宵重装。从那以后我每次交付前都强制走一遍“新建虚拟环境 → 按 requirements 安装 → 空库初始化 → 跑一遍测试用例”的流程,跑通才算完成。希望这套结构和清单能帮到你,把时间花在设计上,而不是花在返工上。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询