资讯详情

Python+Django实现网络舆情分析系统:DNN与BERT双模型完整后台闭环

📅 2026/10/24 18:31:39 | 华诺云谱 👁 阅读
Python+Django实现网络舆情分析系统:DNN与BERT双模型完整后台闭环
简介一套基于Python与MySQL数据库开发的网络舆情分析系统毕业论文资源面向高校计算机、网络管理相关专业学生尤其适合正在筹备毕业设计或课题研究的读者。系统围绕网络舆情管理的实际需求使用Python语言完成业务逻辑采用MySQL数据库存储数据设计并实现了言论分析、言论管理、用户管理等多种功能能够辅助网络管理部门快速定位本地相关舆情或负面评论平衡网民隐私权与监管效率。压缩包内共1份docx文档大小约1.73MB文档包含独创性声明、授权书、中英文摘要、目录、正文及参考文献等完整论文组成部分正文部分从引言与背景介绍入手详细阐述了系统设计思路、开发环境搭建和功能模块实现过程。目前已有1262人浏览学习这份资料可以帮助读者把握舆情分析系统的设计框架与开发流程学习PythonMySQL的项目落地方式也可直接参考其章节结构和排版规范为撰写同类毕业论文节省大量时间。1. 基于 Python 的网络舆情分析系统从文本录入到数据看板的完整后台闭环“基于 Python 的网络舆情分析系统”这个名字容易让人以为它是个爬虫项目或者只是一个情感分类算法 Demo。把这份包含源码工程和论文文档的资料完整跑一遍之后你会发现它真正的分量是一套完整的 Web 后台用户登录注册、文本输入与历史记录、DNN 与 BERT 双模型推理、负面预警、文本管理、用户权限、柱状图与饼图对比分析全部串在一条业务链上。它面向高校或属地舆情管理工作人员用 MySQL 落库、Python 做业务调度从“某个人输入一句话”到“管理员看到分析结果并标记处理状态”闭环完成。正在学 Django MySQL 管理系统的开发者值得照着拆一遍。2. 技术选型与整体架构Django MySQL 的 B/S 骨架怎么撑起文本分析全流程拿到这份资料我建议你先读论文正文里的技术选型和需求分析再动手碰代码。论文的 2.1 到 2.6 节把架构、语言、前端、数据库、算法和开发环境全讲清楚了照着这些描述去反推工程落地方式比直接翻代码高效得多。这一章我把技术栈的来龙去脉和数据库设计拆开讲搞清楚这部分后面看核心代码就不会迷路。2.1 技术栈判断从开发工具与功能描述反推框架组合论文里提到的开发工具是 Sublime Text Navicat for MySQL前端用 HTML5 CSS JavaScript后端语言是 Python数据库是 MySQL架构是 B/S。这套组合在校园项目里几乎有一个默认映射后端框架用 Django。理由很直接Django 自带 ORM、模板渲染、表单验证和后台管理恰好覆盖论文 3.2 节里列出的“用户注册登录”“文本数据增删改查”“文本分析”“用户管理”这些需求。原文 2.6 节也明确提到了“进行 Python 和 Django 编程”所以这个判断基本没有悬念。B/S 架构的选择本身也合理。传统的 C/S 架构需要安装客户端、升级要重新分发B/S 只要浏览器能打开服务器部署一次所有用户访问同一个地址就行。对于舆情管理这类需要多人同时使用的后台系统浏览器端零安装是最大的优势。前端虽然用了 HTML5但整个系统不是静态页面所有页面数据都来自 Django 视图渲染后的模板变量或接口返回HTML 只负责结构和布局CSS 负责样式JavaScript 负责表单交互和图表绘制。一个常见的最小依赖清单长这样如果你要在本地复现环境按这个方向装就行# requirements.txt按本系统所需能力推断的常见组合 Django mysqlclient numpy scikit-learn bert-tensorflow这里不写死版本号是因为毕业设计项目的依赖版本往往以“当时能装上”为准。装依赖时如果遇到 mysqlclient 编译失败在 Windows 上可以换成PyMySQL然后在 Django 的__init__.py里加一行pymysql.install_as_MySQLdb()这是 Python MySQL 项目里非常经典的替代方案。2.2 总体功能结构与工程目录从首页到用户管理的请求落点论文 4.1 节的系统结构图把功能划分得很清楚用户登录、个人信息管理、文本分析、文本管理、对比分析和用户管理。这里的用户分两类普通用户负责文本分析和文本管理管理员额外拥有用户管理权限。普通用户可以有很多个管理员只有一个这个权限模型在后端实现时非常像 Django 自带的is_staff/is_superuser字段。按 Django 的常规工程布局这套系统落地后大概率是这样一个目录结构opinion_system/ ├── manage.py ├── opinion_system/ │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── app/ │ ├── models.py │ ├── views.py │ ├── urls.py │ ├── forms.py │ ├── algorithms/ │ │ ├── dnn_predict.py │ │ └── bert_predict.py │ └── migrations/ ├── templates/ └── static/重点在app/algorithms/目录。把 DNN 和 BERT 的预测逻辑独立成模块而不是混在视图函数里是这套源码值得借鉴的一个设计。视图层只负责拿文本、调算法、存结果算法层只负责接收文本、返回标签和分数两边通过一个简单的函数约定通信。这样以后想换算法、加算法改动范围被限制在algorithms/内部不会把业务代码搅乱。templates/下通常是index.html、login.html、register.html、analyze.html、text_manage.html、user_manage.html这些页面。Django 的模板继承在这里很实用一个base.html把导航栏和底部定义好其他页面只需要重写 content 块前后端联调时期能省不少重复劳动。2.3 数据库设计五张表如何支撑舆情分析与言论管理论文 4.2 节列出了系统需要的表用户信息表、文本分类及文本信息表、文本管理信息表、站内新闻表。这里有个细节值得注意——论文特意把“文本分类”和“文本信息”拆成两张表目的是用分类表管理言论种类用文本信息表存具体的文本内容避免把分类名称重复存在每一条文本记录里。这是很典型的表设计思考分类是字典数据文本是业务数据两者分开维护后续加新分类不需要动文本表结构。按常规做法这五张表的字段设计大概长这样表名职责关键字段user_info用户注册信息与登录状态id、username、password、phone、register_time、last_login_timetext_category文本分类字典id、category_nametext_info爬取或手动输入的文本内容id、user_id、category_id、content、sentiment_score、analysis_result、create_timetext_manage_info文本管理状态流转id、text_id、manage_status、manager_id、handle_time、remarknews_info站内公告新闻id、title、content、publish_timeSQL 落地时我一般会这样建表-- 用户信息表 CREATE TABLE user_info ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(128) NOT NULL, phone VARCHAR(20), register_time DATETIME DEFAULT CURRENT_TIMESTAMP, last_login_time DATETIME NULL ); -- 文本分类表 CREATE TABLE text_category ( id INT AUTO_INCREMENT PRIMARY KEY, category_name VARCHAR(50) NOT NULL ); -- 文本信息表 CREATE TABLE text_info ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, category_id INT, content TEXT NOT NULL, sentiment_score FLOAT, analysis_result VARCHAR(20), create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES user_info(id), FOREIGN KEY (category_id) REFERENCES text_category(id) ); -- 文本管理信息表 CREATE TABLE text_manage_info ( id INT AUTO_INCREMENT PRIMARY KEY, text_id INT NOT NULL, manage_status TINYINT DEFAULT 0, manager_id INT, handle_time DATETIME, remark VARCHAR(255) ); -- 站内新闻表 CREATE TABLE news_info ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(200) NOT NULL, content TEXT, publish_time DATETIME DEFAULT CURRENT_TIMESTAMP );password字段用 VARCHAR(128) 是因为 Django 的make_password生成的哈希串就是这个长度如果你打算直接用 Django 的auth模块做认证字段长度必须留够。text_info的sentiment_score我用 FLOAT 类型因为模型输出的概率分数是个 0 到 1 之间的小数。manage_status用 TINYINT0 表示未处理1 表示已处理2 表示忽略比直接用 VARCHAR 存中文状态更节省空间也方便写查询条件。这套表结构覆盖了系统所有核心流程用户在user_info里认证输入的文本进text_info分类挂在text_category管理动作记录在text_manage_info首页公告读news_info。五张表没有一张是多余的也没有明显的设计缺陷。3. 核心模块拆解一段文本从输入到返回的完整分析链路这一章是整套源码最值得看的部分。论文第 5 章虽然讲到了文本分析界面和对比分析界面但没展示具体代码所以我按照项目场景下合格开发的惯用做法把登录、文本分析、对比分析三条链路的关键代码补全并拆开讲。你带着“一个请求从进入到返回”这条主线去看源码会比按文件顺序读清晰得多。3.1 登录注册与权限判定谁可以发起分析系统不是打开就能用的未注册用户只能看首页公告和站内新闻注册并登录后才能进入文本分析和文本管理页面。这个限制在 Django 里实现得比较简单登录视图用内置的authenticate和login页面跳转用login_required装饰器拦截未登录请求。核心登录逻辑大致是这样# app/views.py from django.contrib.auth import authenticate, login from django.shortcuts import render, redirect def user_login(request): if request.method POST: username request.POST.get(username, ) password request.POST.get(password, ) user authenticate(usernameusername, passwordpassword) if user is not None: login(request, user) return redirect(index) return render(request, login.html, {error: 用户名或密码错误}) return render(request, login.html)代码逻辑其实只有三件事从 POST 请求里取出用户名和密码调用authenticate校验校验通过后用login写入会话。redirect(index)里的index是 URL 配置里给首页路由起的 name这样写的好处是以后路由地址变了视图代码不用改。authenticate是 Django 内置的认证方法它会去user_info表对应的模型里比对用户名和密码哈希比对成功返回用户对象失败返回 None。这里需要注意密码在入库前必须用make_password做哈希处理绝不能存明文。注册视图里常见的写法是UserInfo.objects.create_user(username..., password...)create_user会自动完成哈希比手动create安全得多。权限判定方面管理员判断用is_superuser普通用户的管辖范围判断用request.user和模型里的外键关联。比如“用户只能看到自己提交的文本”这个需求查询时加一个filter(userrequest.user)就能实现这也是后面文本管理模块的基本原则。3.2 文本分析主流程DNN 与 BERT 双模型如何协同文本分析是本系统的核心功能论文 5.3 节说得很清楚用户进入页面后可以查看历史输入记录也可以在文本框中输入新内容系统会对内容做数据分析并给出负面信息预警。我一般会把视图拆成两个部分一部分处理 POST 请求执行分析另一部分处理 GET 请求展示历史记录。# app/views.py from .models import TextInfo from .algorithms.dnn_predict import dnn_predict from .algorithms.bert_predict import bert_predict def text_analyze(request): if request.method POST: text request.POST.get(content, ).strip() if not text: return render(request, analyze.html, {error: 输入内容不能为空}) # 调用双模型分析 label, score double_model_predict(text) TextInfo.objects.create( userrequest.user, contenttext, sentiment_scorescore, analysis_resultlabel, ) return render(request, analyze.html, {result: label, score: score}) history TextInfo.objects.filter(userrequest.user).order_by(-create_time)[:20] return render(request, analyze.html, {history: history})其中double_model_predict是算法的统一入口DNN 和 BERT 的结果在这里汇合# app/algorithms/__init__.py或视图内联函数 def text_clean(text): # 去除首尾空白、统一全半角 return .join(text.split()) def double_model_predict(text): text text_clean(text) # DNN 模型推理速度快但精度一般 dnn_label, dnn_prob dnn_predict(text) # BERT 模型推理速度慢但更准 bert_label, bert_prob bert_predict(text) # 冲突时优先采用 BERT 结果分数取两个模型的均值 label bert_label if bert_label else dnn_label score round((dnn_prob bert_prob) / 2.0, 4) return label, score逻辑说明DNN 和 BERT 分别对同一段文本输出一个情感标签比如“负面/正面/中性”和一个概率分数。两个模型结论不一致时代码里默认信任 BERT因为 BERT 在 2018 年提出后就在多项自然语言处理任务上刷新了成绩论文原文也强调“使用两种算法目的就是将舆情分析做到最准”所以让更准的模型仲裁是合理选择。分数取均值则是为了让结果更平滑避免单模型的一次偶发失误把分拉偏。参数说明dnn_predict(text)和bert_predict(text)的返回值约定是(label, probability)元组probability是一个 0 到 1 之间的浮点数。以“负面概率”口径为例分数越高代表越可能为负面这套系统里把 0.6 以上视为负面内容并给出预警提示页面标红展示0.4 到 0.6 之间是中性0.4 以下为正面。阈值可以在settings.py里定义成常量NEGATIVE_THRESHOLD 0.6这样以后调优阈值时不用去视图里找数字。历史记录取最近 20 条是因为页面空间有限且舆情管理关注的是最近一段时间的言论太老的数据意义不大。如果你希望支持时间范围筛选把order_by换成filter(create_time__range(start, end))即可。3.3 负面预警与对比分析统计结果怎么变成柱状图和饼图对比分析模块是论文 5.6 节提到的功能用户可以通过柱状图和饼图对比 DNN 与 BERT 两种算法的分析结果。这里的核心逻辑不是图表本身而是“按算法口径分别聚合统计数据”。一个典型的聚合查询是这样# app/views.py from django.db.models import Count def compare_view(request): # 按文本类别统计数量用于饼图 category_stats TextInfo.objects.values(category__category_name) \ .annotate(totalCount(id)).order_by(-total) # 按文本结果负面/正面/中性统计用于柱状图 result_stats TextInfo.objects.values(analysis_result) \ .annotate(totalCount(id)).order_by(analysis_result) return render(request, compare.html, { category_stats: list(category_stats), result_stats: list(result_stats), })values(category__category_name)里的双下划线是 Django ORM 跨表查询的写法意思是“取关联分类表的 category_name 字段”最终生成的 SQL 是LEFT JOIN text_category ON ... GROUP BY category_name。annotate(totalCount(id))等价于 SQL 里的COUNT(id)结果集会多出一个 total 字段。两个查询返回的都是字典列表可以直接 JSON 序列化丢给前端图表库。如果要做“双模型对比”你就得在text_info表里多存一个字段来标记这条结果来自哪个模型比如algorithm dnn / bert然后查询时按algorithm字段分组就能分别统计两个模型的正负面数量。论文里对比分析展示的是两种算法的效果差异所以在实际实现中大概率采用了这个方案。前端图表建议用 ECharts 或 Highcharts 渲染饼图的 data 值直接取category_stats列表柱状图的 X 轴取result_stats里的 result 值Y 轴取 total 值。图表数据来自数据库聚合结果这就保证了任何一次新的文本分析完成后刷新页面图表就会自动更新。4. 功能实现与数据流首页、文本管理和用户管理的具体落法核心分析链路讲完之后剩下的功能模块更偏业务。论文 5.1 到 5.7 节分别描述了首页展示、登录注册、文本分析、文本管理、个人信息查看、对比分析和用户管理这一章我把非分析类功能的数据流理一遍让你知道每个页面的数据是从哪些表里查出来的。4.1 首页信息聚合公告、统计卡与近期舆情怎么拼出来首页采用上中下结构头部是导航栏中部左侧显示当前用户信息和日期天气右侧显示系统总发言数、总用户数等统计数据同时展示站内公告。这个页面的数据来源很分散——用户信息从request.user拿统计数字从text_info和user_info表聚合公告从news_info表取所以视图里要一次性把所有上下文变量准备好。# app/views.py from datetime import date from django.shortcuts import render from .models import TextInfo, UserInfo, NewsInfo def index(request): context { total_text_count: TextInfo.objects.count(), total_user_count: UserInfo.objects.count(), today_text_count: TextInfo.objects.filter(create_time__datedate.today()).count(), news_list: NewsInfo.objects.order_by(-publish_time)[:5], current_user: request.user, } return render(request, index.html, context)total_text_count是全库文本总数对应首页右侧的“总发言数”today_text_count是当天新增文本数这个字段比总数更能反映舆情活跃度。news_list取最近 5 条公告order_by(-publish_time)是倒序排列[:5]在 Django ORM 里会生成LIMIT 5不会全表加载。当前用户直接放到request.user模板里通过{% if user.is_authenticated %}判断是否显示登录栏。模板侧渲染统计卡片的写法很简单!-- templates/base.html 或 index.html 片段 -- div classstat-card span classstat-label总发言数/span strong{{ total_text_count }}/strong /div ul classnews-list {% for item in news_list %} li{{ item.title }} - {{ item.publish_time|date:Y-m-d }}/li {% endfor %} /ul带|date:Y-m-d的管道符号是 Django 模板过滤器用来格式化时间戳输出。日期天气这类信息通常由 JavaScript 在浏览器端获取不经过后端数据库所以论文里说左侧显示日期和天气实际实现大概率是在模板里引一段 JS 代码完成的。4.2 文本管理关键词检索与管理状态的流转文本管理是本系统最核心的业务功能之一用户之前输入的关键词和最终分析结果都在这里展示。页面需要一个关键词搜索框还要支持管理员对文本做“未处理/已处理”的状态标记。搜索用 Django ORM 的icontains做模糊匹配状态流转用update_or_create保证幂等。# app/views.py from django.db.models import Q from .models import TextInfo, TextManageInfo def text_manage(request): keyword request.GET.get(keyword, ).strip() texts TextInfo.objects.select_related(user).all().order_by(-create_time) if keyword: texts texts.filter(Q(content__icontainskeyword) | Q(analysis_result__icontainskeyword)) return render(request, text_manage.html, {texts: texts})select_related(user)会在查询时直接 JOIN 用户表避免在模板里循环访问text.user.username时产生 N1 次查询。这个优化在数据量大时效果明显属于 Django 开发者应该形成肌肉记忆的操作。Q(content__icontainskeyword) | Q(analysis_result__icontainskeyword)生成的是WHERE content LIKE %keyword% OR analysis_result LIKE %keyword%用 Q 对象是为了把两个字段的模糊匹配拼到同一个 WHERE 条件里。状态标记的操作通常是管理员点击列表右侧的按钮触发一个 POST 请求def update_manage_status(request, text_id): status request.POST.get(status, 0) TextManageInfo.objects.update_or_create( text_idtext_id, defaults{ manage_status: int(status), manager_id: request.user.id, handle_time: now(), } ) return redirect(text_manage)update_or_create的第一个参数是查询条件defaults是更新时要写入的字段。第一次标记时如果text_manage_info里没有这条记录就会自动创建之后再次点击则是更新状态不会生成重复记录。这比先filter().exists()再决定 insert 还是 update 要简洁得多也是我在这个场景下的首选。状态字段manage_status用 0/1/2 区分未处理/已处理/忽略前端模板里用{% if item.manage_status 0 %}判断按钮样式即可。4.3 注册用户管理创建、搜索、权限设置与删除操作用户管理模块只有管理员能进入。论文 5.7 节描述了两种能力一是创建新用户输入姓名、密码和手机号即可添加二是通过列表上方的搜索栏查找用户修改信息或删除违规用户。这里的权限校验必须在服务端做不能只靠前端隐藏入口。# app/views.py from django.contrib.auth.decorators import login_required from django.contrib.auth.hashers import make_password from .models import UserInfo login_required def user_manage(request): if not request.user.is_superuser: return redirect(index) keyword request.GET.get(keyword, ).strip() users UserInfo.objects.annotate(text_countCount(textinfo)).order_by(-register_time) if keyword: users users.filter(username__icontainskeyword) return render(request, user_manage.html, {users: users})annotate(text_countCount(textinfo))统计每个用户名下的文本数量这个字段能在列表页直观展示哪个用户发言最活跃。新用户创建时密码必须经过make_password哈希再入库def user_create(request): if request.method POST: username request.POST.get(username) password request.POST.get(password) phone request.POST.get(phone, ) UserInfo.objects.create( usernameusername, passwordmake_password(password), phonephone, ) return redirect(user_manage) return render(request, user_create.html)删除用户和修改权限的操作也要放在 POST 请求里并且每次都要重新校验is_superuser。管理员只能删除普通用户不能删除自己或者另一个管理员这个校验最好在视图里写死def user_delete(request, user_id): if not request.user.is_superuser: return redirect(index) target UserInfo.objects.get(iduser_id) if target.is_superuser: return redirect(user_manage) # 拒绝删除管理员 target.delete() return redirect(user_manage)权限设置同理管理员可以通过一个复选框控制用户是否有文本管理权限。这里的权限字段可以是UserInfo模型上的一个布尔字段can_manage_text视图里target.can_manage_text request.POST.get(can_manage) true然后save()即可。5. 踩坑与排查跑这套项目前后要确认的五个细节这套系统技术栈不算新但 Django MySQL 预训练模型这个组合在本地复现时踩坑点不少。我按常见的故障现场整理五条每一条都是真实会发生的“玄学”问题其实背后原因都很具体。你照着查能省下不少排查时间。5.1 现象数据库连接报 2003 错误页面打不开启动 Django 后访问页面浏览器提示数据库连接失败终端打印OperationalError: (2003, Cant connect to MySQL server...)。这个报错的意思很直白Django 压根没连上 MySQL 服务。原因基本只有两个一是 MySQL 服务没启动二是settings.py里的HOST或PORT写错了。很多人习惯了用 Navicat 连数据库Navicat 能连不代表 Django 能连因为 Navicat 可能是在本机起的服务而 Django 配置里写的是别的地址。解决方法是先确认服务状态再检查配置。Windows 下看任务管理器里有没有mysqld.exe或者在命令行执行mysql -u root -p手动登录。排除了服务问题后再去settings.py里核对HOST是不是127.0.0.1或localhostPORT是不是3306。推荐做法是先用 Navicat 用同一套账号密码测试连接Navicat 能连而 Django 不能连那就是 Django 配置的问题了。5.2 现象BERT 模型加载要十几秒首次请求直接超时文本分析页面第一次点击分析按钮浏览器转圈很久才出结果甚至直接请求超时。这是因为 BERT 预训练模型体积大首次加载要读入几百兆参数文件在只有 CPU 的笔记本上初始化特别慢。问题不在代码逻辑而在加载时机。如果模型是在视图函数里每次请求都load_pretrained()那每次分析都要重新加载一次慢上加慢。常见做法是项目启动时一次性加载模型到内存或者用模块级全局变量做缓存。解决方法是把模型加载放到algorithms/bert_predict.py的模块顶层# app/algorithms/bert_predict.py _model None def bert_predict(text): global _model if _model is None: _model load_bert_model() # 首次加载后续请求直接复用 return _model.predict(text)另外输入文本长度最好限制在 128 到 256 个字符内BERT 的 Transformer 结构对超长文本的处理是截断和补 padding输入越长推理越慢舆情评论类文本通常都很短截断到 128 基本不影响准确率。5.3 现象中文入库后变成问号文本分析结果乱码输入中文文本进行分析页面显示正常但打开数据库表一看content字段存的全是?????。这是 MySQL 字符集没配置成utf8mb4的典型症状。原因有两个层面数据库表本身的字符集不是utf8mb4或者 Django 连接时用的字符集不对。Navicat 建表时如果没手动指定字符集默认可能是latin1中文自然存不进去。解决方法是双管齐下。建表时指定字符集CREATE TABLE text_info ( ... ) DEFAULT CHARSETutf8mb4;同时在settings.py的数据库配置里加上OPTIONSDATABASES { default: { ENGINE: django.db.backends.mysql, NAME: opinion_system, USER: root, PASSWORD: your_password, HOST: 127.0.0.1, PORT: 3306, OPTIONS: { charset: utf8mb4, }, } }还有个容易忽略的点如果表已经建好了ALTER TABLE text_info CONVERT TO CHARACTER SET utf8mb4可以把现有表改过来但已经变成问号的数据是找不回来的。所以这个坑最好在建表阶段就规避这也是为什么论文里强调数据库设计要在编码之前完成。5.4 现象页面样式全丢只剩光秃秃的 HTML本地开发时页面样式正常部署到服务器或者把DEBUG改成False之后所有 CSS、JS 全部失效页面变成了纯文本列表。原因很简单Django 的静态文件服务只在DEBUGTrue时由开发服务器自动提供DEBUGFalse后静态文件需要由 Web 服务器单独处理。很多初学者不知道这一点直接把DEBUG关了静态文件就 404 了。如果你只是在本地跑源码解决方法是保持DEBUGTrue同时确认settings.py里配了STATIC_URL /static/和STATICFILES_DIRS并且模板里用{% load static %}和{% static css/style.css %}引用静态资源而不是写死/static/css/style.css路径。如果确实需要关闭 DEBUG记着执行python manage.py collectstatic收集所有静态文件到STATIC_ROOT再交给 Nginx 之类的服务托管。5.5 现象两个模型的结果对不上对比图的饼状图和柱状图数据互相矛盾对比分析页面里DNN 统计出 30 条负面BERT 统计出 22 条负面两份图表的同一类数据对不上。管理员看着图产生了困惑。这是双模型系统的必然现象但具体原因往往是预处理不一致。DNN 的输入可能是jieba分词后用词向量编码BER T的输入是tokenizer分词后做 padding两边对同一条文本的“负面”判定阈值也可能不同。结果就是文本被两个模型分到了不同类别比例自然不一致。解决方法是统一预处理入口。把清洗、分词、截断的逻辑收敛到一个公共函数里在text_clean()之后才分别喂给两个模型。当初我踩这个坑时就是因为在dnn_predict里先做了去停用词而bert_predict没做导致同一条文本在 DNN 那边被判定为中性在 BERT 这边因为多了几个停用词被判成负面。统一预处理之后两边的输入口径一致结果差异就只来自模型本身了。如果两个模型仍然对同一条文本结论不一致就在业务层面约定一个仲裁规则比如论文里强调的“更信任 BERT”把规则写进代码对比分析页面就不会出现数据打架的问题。6. 二次开发把人工输入的文本分析升级成定时采集与可插拔算法这套系统当下的交互模式是“用户手动输入文本模型即时分析”。放在真实舆情管理场景里这个模式太被动——每天谁能盯着页面一遍遍刷新输入更现实的方案是接入数据采集任务定时拉取指定关键词下的新增言论自动入库自动分析管理员只需要每天登录系统看新增的负面舆情和处理状态。这一章讲两条改造路径都是我在原有骨架之上会优先做的事。6.1 改造思路给分析引擎加一个抽象层现在的double_model_predict是硬编码调用 DNN 和 BERT如果将来想接入第三套模型比如 XLNet 或者 ChatGLM改函数内部的逻辑就行但这会让主流程越来越臃肿。更好的做法是定义分析器基类让每个模型各自实现predict方法# app/algorithms/base.py class BaseAnalyzer: def predict(self, text): raise NotImplementedError # app/algorithms/dnn_predict.py class DNNAnalyzer(BaseAnalyzer): def predict(self, text): # 返回 (label, prob) return dnn_predict(text) # app/algorithms/bert_predict.py class BERTAnalyzer(BaseAnalyzer): def predict(self, text): return bert_predict(text)业务代码里只需要维护一个分析器列表按顺序逐个调用再按仲裁规则选结果。这样新增算法时不需要动views.py只需要新增一个继承BaseAnalyzer的类并把它注册到配置列表里。6.2 改造示例批量任务与按日趋势图采集到的新文本往往是一批一批的比如每分钟拉一次接口每次拿到几十条新评论。可以写一个 Django 管理命令用定时任务周期执行# app/management/commands/auto_analyze.py from django.core.management.base import BaseCommand from app.models import TextInfo class Command(BaseCommand): def handle(self, *args, **options): for content in fetch_latest_comments(): label, score double_model_predict(content) TextInfo.objects.create( userget_system_user(), contentcontent, sentiment_scorescore, analysis_resultlabel, ) self.stdout.write(auto analyze finished)fetch_latest_comments()可以替换成任意数据源比如爬虫入库的结果、API 推送的评论列表。配合系统 crontab 或 Windows 任务计划定期执行python manage.py auto_analyze系统就从“被动输入”变成了“主动监控”。趋势图改造更简单把 3.3 节的聚合查询改成按日期分组from django.db.models.functions import TruncDate daily_stats TextInfo.objects \ .filter(create_time__gtestart_date) \ .annotate(dayTruncDate(create_time)) \ .values(day, analysis_result) \ .annotate(totalCount(id)) \ .order_by(day)这段代码生成的结果是[{day: 2025-03-01, analysis_result: negative, total: 12}, ...]前端拿到后按日期和分类做双维度柱状图就能看到负面言论在时间轴上的变化曲线。改造完成后这套系统就从“文本分析工具”真正变成了“舆情数据看板”而底层表结构完全不用动。说到这个改造我有点犯嘀咕当初我自己做类似系统时只顾着把模型调准忽略了数据采集和定时任务的交付结果系统上线后每天没人喂数据成了摆设。后来去补调度任务又因为预处理不一致翻了车跟第 5 章说的情况几乎一模一样。从那以后每次接这种分析类项目我都会在动手写模型前先把“数据从哪来、分析完存到哪、每天什么时候自动跑”这三件事敲定再回来写代码。希望你如果打算在这套源码上继续扩展也能先想到这个层面真的能帮到你少走弯路。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑