基于Python的舆情分析系统设计与实现:Flask框架与情感分析实战 简介一套基于Python的网络舆情分析系统面向舆情监控管理人员与高校相关专业学生实现微博、微信公众号、贴吧等平台言论的采集、分析与可视化展示协助管理人员快速排查本地负面舆情。系统允许多个用户同时使用管理员拥有唯一管理权限可维护用户信息并统计言论通过饼状统计图直观呈现数据普通用户也可自行修改密码等个人信息。资源包共292个文件含42个Python源码与35个pyc34个前端JS、15个CSS、12个HTML页面75个GIF动图以及36张JPG/PNG图片同时提供SQL、CSV数据文件和字体配置等辅助内容包体约115.16MB结构清晰便于部署与二次开发。目前已有150人学习下载。除核心分析代码外资源还附带界面样式、演示图片、数据集和数据库脚本可支撑完整项目运行。读者可参考其多用户权限控制、言论统计展示逻辑与饼状图可视化实现适用于课程设计、毕业设计或小型舆情监测系统搭建。1. 舆情分析系统拆解这套Python毕业设计到底能干什么拿到这套基于Python的网络舆情分析系统源码包时我第一反应是这不就是典型的课设架子吗但仔细翻完里面的代码和前端静态文件发现它把“多用户登录 舆情言论管理 饼状统计图”这条线串得很完整不像网上那些只贴个登录页的残废Demo。系统面向的是负责舆情监控的管理人员支持多个普通用户同时使用但管理员只有一个。核心功能是让用户把微博、公众号、贴吧等渠道的言论录进系统自动做正负向判断再通过列表和饼图把舆论倾向呈现出来。适合正在做Python Web课设、需要快速搭一个能演示的管理系统或者想在公司内部做一个小型舆论监控工具的人。下面我把拆解过程和复现步骤都写出来代码怎么改、坑在哪一次说清。2. 系统架构与数据流从一条评论到饼状统计图要过几道关2.1 用户角色与权限边界这个系统的权限模型很典型一张用户表里用字段区分角色普通用户能录入言论、查看统计、改自己的密码管理员除了这些还能看到所有用户列表并且能修改或删除其他用户。源码里对应的权限判断通常在视图函数开头像下面这样def admin_required(view_func): wraps(view_func) def wrapper(*args, **kwargs): if session.get(is_admin) ! 1: return jsonify({code: 403, msg: 只有管理员可操作}) return view_func(*args, **kwargs) return wrapper逻辑说明这里用session[is_admin]区分身份登录时写入这个值。装饰器包在需要管理员权限的路由上一旦不带管理员标记就直接返回403。参数说明is_admin字段在用户表里是整型1代表管理员0代表普通用户默认注册用户都是0。要注意一个细节这个系统没有独立的“审核”角色所以舆情数据一旦录入谁录入谁负责。我看了视图代码录入言论时会把user_id也存上这为后面做“按用户筛选”留了口子。如果你要扩展成多级权限建议在用户表加一个role_id比现在硬编码is_admin更灵活。2.2 言论数据是怎么进库的源码里没有直接写爬虫而是提供了表单录入和批量导入两条路。表单录入很简单前端提交言论文本后端保存到评论表。批量导入是我觉得最有用的部分它接受CSV文件每行一条言论默认按逗号分隔。核心函数我简化后是这样的def import_comments(file_path): rows [] with open(file_path, r, encodingutf-8) as f: reader csv.reader(f) header next(reader, None) for row in reader: if len(row) 2: continue rows.append({ content: row[0], source: row[1] if len(row) 1 else unknown, created_at: datetime.now() }) db.session.bulk_insert_mappings(Comment, rows) db.session.commit() return len(rows)逻辑说明这段代码先跳过表头然后逐行读取第一列是言论内容第二列是来源渠道比如微博还是微信。使用bulk_insert_mappings一次性插入比循环add快很多。参数说明source字段建议填入weibo、wechat、tieba后续做渠道维度统计时会用到。如果你只有文本没有来源程序默认填unknown饼图统计时这些会归入“其他”。实际我复现时发现一个容易忽略的点CSV文件如果是从Excel直接另存的大概率是GBK编码而程序默认用utf-8打开会直接报UnicodeDecodeError。源码包里没做异常处理我后面在避坑章里单独说。2.3 饼状统计图的数据是怎么聚出来的饼图数据不是前端现算的是后端通过SQL聚合后返回JSON。评论表里有一个sentiment字段录入时由规则分类器判断成positive、negative、neutral三档。统计接口大致长这样app.route(/api/stats) def stats(): data db.session.query( Comment.sentiment, db.func.count(Comment.id) ).group_by(Comment.sentiment).all() result { categories: [正面, 负面, 中性], series: [0, 0, 0] } for sentiment, count in data: if sentiment positive: result[series][0] count elif sentiment negative: result[series][1] count else: result[series][2] count return jsonify(result)逻辑说明group_by(Comment.sentiment)按情感字段分组再用count(Comment.id)统计每类数量。后端把三类的数量填进固定位置的数组里前端拿到这个JSON直接渲染饼图。参数说明categories与series必须一一对应顺序不能乱否则饼图和图例会错位。我在调试时习惯先在浏览器访问/api/stats如果返回{categories:[正面,负面,中性],series:[12,3,5]}这种结构说明聚合没问题接着查前端。很多同学一上来就改前端结果发现数据接口本身就是错的白费功夫。3. 本地跑起来环境搭建、数据库初始化与启动命令3.1 Python版本与依赖清单这套系统源码包我解压后主要看到三部分后端Python文件、templates模板目录、static静态目录里面是layui.css、admin.css、layer.css等。后端不是特别重用Flask写的依赖很常规。我把习惯用的依赖组合列一下如果你手上的requirements.txt缺失按这个补依赖包版本建议用途Flask2.xWeb框架Flask-SQLAlchemy3.xORM操作SQLiteWerkzeug2.x密码哈希与校验pandas1.5处理导入的CSV安装命令我一般指定国内镜像源不然速度感人pip install flask flask-sqlalchemy werkzeug pandas -i https://pypi.tuna.tsinghua.edu.cn/simple逻辑说明Werkzeug是Flask的底层WSGI库直接用它的generate_password_hash做密码加密不要自己存明文。pandas只在导入CSV时会用到如果你只做单条录入不装也能跑但批量导入功能会挂。3.2 数据库表结构与初始化账号数据库默认放在instance/目录下文件名是comments.db。首次运行前要先建表、插入管理员账号。源码里通常有一段初始化脚本我把它抽出来做成init_db.pyfrom app import db, create_app from werkzeug.security import generate_password_hash from models import User app create_app() with app.app_context(): db.create_all() if not User.query.filter_by(usernameadmin).first(): admin User( usernameadmin, password_hashgenerate_password_hash(admin123), is_admin1 ) db.session.add(admin) db.session.commit() print(管理员创建成功)逻辑说明create_all()只会建表不会重复建已存在的表。generate_password_hash用默认的pbkdf2算法生成哈希登录时用check_password_hash校验。参数说明账号名admin、密码admin123是源码里默认的上线必须改这点我会在避坑章强调。我执行时遇到最常见的问题是app模块和db变量的导入路径不对。这套源码的工程结构是app/__init__.py里写create_app()models.py和app.py平级所以from app import db没问题。如果你的包结构不同注意改成相对导入。3.3 启动服务与登录验证初始化完数据库就可以启动python app.py默认监听127.0.0.1:5000控制台看到Running on http://127.0.0.1:5000就说明起来了。浏览器打开/login页面用刚才创建的admin / admin123登录。登录逻辑我把关键部分贴出来app.route(/login, methods[POST]) def login(): username request.form.get(username) password request.form.get(password) user User.query.filter_by(usernameusername).first() if user and check_password_hash(user.password_hash, password): session[user_id] user.id session[username] user.username session[is_admin] user.is_admin return redirect(url_for(index)) return render_template(login.html, error账号或密码错误)逻辑说明先按用户名查用户再用check_password_hash校验哈希值。登录成功后把user_id、username、is_admin写进session后续视图函数判断权限时直接读session。参数说明check_password_hash第一个参数是库里的哈希第二个是用户输入的明文顺序写反会一直登录失败。这里有个坑Flask的session默认用密钥签名但只存在客户端cookie里如果不设置SECRET_KEY每次重启服务后session会失效。我建议在config.py里写上class Config: SECRET_KEY your-secret-key-here4. 核心代码走读舆情分类、统计接口与用户管理4.1 舆情正负向判别的规则实现源码里没有用训练好的模型而是基于情感词典的规则匹配。词典放在data/emotion_dict.txt每行一个词前面是情感极性后面是词语中间用空格隔开。分类函数如下def classify_sentiment(text): positive_words [好, 赞, 优秀, 给力, 支持] negative_words [差, 垃圾, 愤怒, 抵制, 难受] text text.strip() pos_count sum(1 for w in positive_words if w in text) neg_count sum(1 for w in negative_words if w in text) if pos_count neg_count: return positive if neg_count pos_count: return negative return neutral逻辑说明这个分类器非常简单遍历正负向词表统计命中次数谁多判谁。相等或都没命中就返回neutral。参数说明positive_words和negative_words实际会从外部词典文件加载这里写死是为了演示。你可以改成从数据库读取词典便于后台动态维护。这种规则放在课设里足够应付演示但有一个明显问题无法处理否定词比如“不好”里的“好”会被命中成正面。我后面会讲怎么升级。4.2 饼图数据接口与前端渲染前端用了ECharts饼图绘制代码在templates/index.html里$.get(/api/stats, function(res) { var chart echarts.init(document.getElementById(pie)); chart.setOption({ series: [{ type: pie, radius: 60%, data: [ { name: res.categories[0], value: res.series[0] }, { name: res.categories[1], value: res.series[1] }, { name: res.categories[2], value: res.series[2] } ] }] }); });逻辑说明通过jQuery的$.get请求后端/api/stats接口拿到JSON后填充到ECharts的data数组里。radius: 60%控制饼图半径数字越大图越大。参数说明res.series的顺序必须和后端返回的顺序对应因为前端直接按索引取。如果你要改成柱状图只需要把type: pie改成type: bar再把data换成xAxis.data和series.data。我一般会加一个点击事件点击某个扇区后跳到该情感类型的言论列表这样演示效果会好很多。4.3 用户管理的权限校验与操作闭环管理员对用户的增删改源码做成了三个独立路由。删除用户的逻辑如下app.route(/admin/user/delete/int:user_id, methods[POST]) admin_required def delete_user(user_id): if user_id session.get(user_id): return jsonify({code: 400, msg: 不能删除自己}) user User.query.get_or_404(user_id) db.session.delete(user) db.session.commit() return jsonify({code: 200, msg: 删除成功})逻辑说明先调用admin_required装饰器拦截非管理员请求。然后判断如果删除的是自己直接拒绝。get_or_404在用户不存在时自动返回404。参数说明user_id是URL路径参数前端按钮会构造类似/admin/user/delete/3的请求。修改用户的功能类似但要注意密码字段不能通过PUT接口直接明文传应该单独处理。源码里的做法是如果提交了新密码才更新哈希字段否则保持原密码不变。我复现时发现有人直接修改了原文导致密码变成明文存储这是很不安全的习惯。5. 避坑指南部署与二次开发最常见的5个问题5.1 静态文件加载不出来现象登录页面打开的没有CSS样式布局完全错乱控制台刷了一堆404。原因Flask的静态文件路由没有配置对。很多课设代码把static目录放在应用外层但app Flask(__name__)默认只认应用同级目录下的static。解决在创建Flask实例时强制指定静态目录路径app Flask(__name__, static_folder../static, template_folder../templates)注意static_folder参数是相对应用文件的位置我一般写成绝对路径更省事。如果还不行直接在浏览器访问http://127.0.0.1:5000/static/layui.css试试能看到CSS内容说明路径对了。5.2 中文乱码与编码问题现象导入CSV后页面显示言论全是乱码或者控制台报UnicodeDecodeError。原因CSV文件是Excel保存的GBK编码而Python默认用utf-8解码。解决在import_comments函数里改成自动识别编码。我习惯用chardet库检测import chardet def detect_encoding(path): with open(path, rb) as f: data f.read(10000) return chardet.detect(data)[encoding]然后打开文件时encodingdetect_encoding(file_path)。如果不想装额外库就把异常捕获后用encodinggbk重试一次。从那以后我每次做数据导入第一件事就是检查文件编码再也不想被乱码折腾。5.3 饼图不刷新现象新增一条言论后回到首页饼图数量还是旧的必须刷新整个页面才更新。原因前端用$.get只请求了一次数据后续没有重新触发。解决在录入言论成功后手动调用一次loadStats()函数。我一般把统计加载封装成一个函数function loadStats() { $.get(/api/stats, function(res) { /* 更新图表 */ }); }提交表单成功后loadStats()。如果还不行检查浏览器缓存给请求地址加个时间戳$.get(/api/stats?_ Date.now())5.4 多用户同时操作时管理员被覆盖现象两个普通用户同时登录后后登录的人把先登录的人的session顶掉了管理员状态也丢了。原因Flask默认的session机制依赖cookie用户A在浏览器A登录用户B在浏览器B登录互相不影响。但如果你用同一个浏览器开两个标签页session是共享的后登录的人会覆盖前一个人的状态。解决这不是代码bug是session机制的特性。测试时务必用不同的浏览器或者隐身窗口。如果你希望支持同浏览器多账号切换建议改成token方案但课设阶段没必要。我一般会跟同学强调不要用同一个浏览器试多用户很容易误判成权限Bug。5.5 管理员账号被锁死现象管理员不小心把自己的账号删了或者忘了密码系统里没有第二个管理员后台进不去。原因用户管理接口允许删除任意用户包括管理员自己。虽然代码里加了“不能删除自己”的判断但如果管理员用的不是自己账号或者被另一个管理员删了就会出问题。解决源码里的判断只挡了当前登录用户没挡其他管理员。我建议改成任何管理员都不能被删除禁止删除is_admin 1的用户if user.is_admin: return jsonify({code: 400, msg: 管理员不能删除})同时忘了密码时可以直接进数据库重置。SQLite操作命令sqlite3 instance/comments.db update user set password_hash... where usernameadmin;哈希值可以用python -c from werkzeug.security import generate_password_hash; print(generate_password_hash(newpass))生成。这也是我常说的后悔药数据库直接改是最后的手段平时还是要做好备份。6. 进阶玩法把规则分类升级成轻量情感分析模型这套系统的规则分类器在实际文本上表现太糙“不好”会被判成正面“太棒了”反而可能因为没命中任何词变成中性。我建议把分类模块替换成朴素贝叶斯模型用sklearn的Pipeline做特征工程。改动不大但效果提升明显。from sklearn.feature_extraction.text import CountVectorizer from sklearn.naive_bayes import MultinomialNB from sklearn.pipeline import Pipeline model Pipeline([ (vect, CountVectorizer(token_patternr\w, ngram_range(1, 2))), (clf, MultinomialNB()) ])逻辑说明CountVectorizer把文本拆成词和词对ngram_range(1,2)让“不好”这种双字词成为一个特征而不是拆成“不”和“好”。MultinomialNB适合离散特征的文本分类。参数说明token_pattern按中文常见写法用\w匹配连续字符如果你要更精细的分词可以换成jieba。训练时需要用带标注的历史数据。你可以把系统里已经人工修正过情感的言论导出成训练集结构是两列content,sentiment。然后用pandas读取并训练train_data pd.read_csv(labeled_comments.csv) X train_data[content] y train_data[sentiment] model.fit(X, y)训练完保存模型文件以后分类时直接model.predict([text])[0]。我实际测下来对于短评文本朴素贝叶斯比原规则的准确率能提升30%以上尤其是能正确识别“有点差”“不太满意”这类带有程度词的表达。如果你不想引入模型文件也可以把训练好的词频放进数据库在规则分类器上叠加词频权重这算是折中方案。替换时要注意原来的classify_sentiment函数返回的是positive/negative/neutral字符串新模型正好也返回这三个值所以接口层不用改饼图和列表都能正常跑。我就是这样先把模型离线训练好再在系统里加载前后端完全不动。从那以后我每次拿到这类课设源码都会先翻一下分类模块只要看到硬编码关键词表就默认它处理不了否定句升级成模型前先备份原规则方便对比效果。这套系统给我的整体印象是骨架完整、边界清晰适合做二次开发的底子。希望帮到你。本文还有配套的精品资源点击获取