简介:这是一套基于Python与Django的网络舆情分析系统毕业设计项目,完整包含项目源码、数据库脚本、毕业论文文档与答辩PPT。项目面向计算机专业毕业生、有Python基础的开发者,以及需要快速搭建舆情分析演示系统的学习者,帮助理解真实Web系统的分层结构。压缩包共290个文件,大小约93.5MB,其中42个Python源码文件承载后端业务逻辑,34个JavaScript与多个HTML/CSS文件构建前端交互界面,75个GIF动图用于操作过程演示,另外还有SQL数据库脚本、Word论文、PowerPoint答辩稿,以及配置文件与日志文件,目录设计清晰,便于直接部署和二次开发。目前已有458人学习下载。通过深入研究这套项目,读者可以系统掌握Django框架下的MVC分层、ORM数据库映射、用户登录与权限控制、数据可视化等关键技能,同时能够借鉴其中舆情数据展示、报表模块和后台管理界面的实现方式,快速迁移到自己的课设或毕设中,也可在此基础上进行功能扩展和重构。
1. 从一条负面热搜到报表,Python 网络舆情分析系统在解决什么
舆情分析这事,外行觉得是“抓数据”,内行知道难的是“抓完怎么变成结论”。一句“某品牌手机发布后口碑逆转”,背后可能是微博评论、新闻稿、论坛帖、短视频文案几万条文本,人工看不完,Excel 也拖不动。这套用 Python 搭起来的网络舆情分析系统,核心链路并不复杂:爬虫定时采集 → 清洗去重 → 情感打分 → 入库 → ECharts 可视化。真正决定系统能用不能用的,是数据库脚本怎么设计、情感阈值怎么定、采集频率怎么控。这篇文章按从业者从头搭一遍的顺序来写,覆盖源码里最容易被跳过但最要命的三件事:数据模型、文本清洗、情感阈值。适合正在做毕业设计、课程设计,或者公司内部想快速看到口碑风向的工程师。不是所有舆情都要上大数据平台,一台 Linux 机器加 Python 3.8,足够撑起日增万条数据的轻量分析。
2. 先把数据模型定下来:数据库脚本与项目目录怎么搭
2.1 数据库脚本:舆情表该有哪些字段
拿到项目先别急着跑爬虫,把数据库脚本打开看一眼。舆情分析系统最忌讳把原始文本和分析结果揉在一张表里,常见做法是三张核心表:news_article存原始采集数据,news_sentiment存情感分析结果,news_word_freq存热词统计。这样后期做趋势查询、增量更新、PPT 截图取数,都不用大面积改代码。
-- 舆情原始数据表 CREATE TABLE `news_article` ( `id` INT NOT NULL AUTO_INCREMENT, `title` VARCHAR(255) NOT NULL COMMENT '标题', `content` MEDIUMTEXT COMMENT '正文内容', `source` VARCHAR(64) DEFAULT 'unknown' COMMENT '来源站点', `url` VARCHAR(500) DEFAULT '' COMMENT '原文链接', `publish_time` DATETIME DEFAULT NULL COMMENT '发布时间', `crawl_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '抓取时间', `md5_hash` CHAR(32) NOT NULL COMMENT '去重指纹', PRIMARY KEY (`id`), UNIQUE KEY `uk_md5` (`md5_hash`), KEY `idx_publish_time` (`publish_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;md5_hash字段是整个去重机制的关键。爬虫每次抓取前对标题加正文拼接后取 MD5,插库时靠唯一索引直接挡住重复数据,比先 SELECT 再 INSERT 快得多,也避免并发采集时写入重复。idx_publish_time索引是为后面按小时、按天聚合走势图准备的,没有这个索引,数据量过 10 万条后GROUP BY DATE(publish_time)会明显变慢。
情感结果表建议做冗余设计,把情感分数、情感类别、关键词快照都冗余进去,查询时不用 JOIN 原表:
CREATE TABLE `news_sentiment` ( `id` INT NOT NULL AUTO_INCREMENT, `article_id` INT NOT NULL COMMENT '关联原文ID', `sentiment_score` DECIMAL(4,2) DEFAULT 0 COMMENT '情感得分 0~1', `sentiment_label` VARCHAR(16) DEFAULT '中性' COMMENT '积极/消极/中性', `keywords` VARCHAR(500) DEFAULT '' COMMENT '关键词,逗号分隔', `analyze_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_article_id` (`article_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;字段类型值得注意两点:content用MEDIUMTEXT而不是TEXT,因为新闻长文可能超过 64KB,尤其网页正文扒下来带着多余空白字符时更容易超限;DECIMAL(4,2)存情感得分,能存 0.00 到 99.99,对 0 到 1 之间的概率值绰绰有余,浮点误差也小于 FLOAT。
2.2 项目源码目录:采集、清洗、分析、展示四层分离
毕业设计或者小型团队项目里最常见的坏味道,是把爬虫、清洗、分析全写在一个main.py里。后期想跑定时任务、换可视化方案,牵一发动全身。我一般推荐的目录拆法是四层:
project/ ├── crawler/ │ ├── news_spider.py # 列表页抓取 │ └── detail_spider.py # 详情页正文抓取 ├── analysis/ │ ├── clean.py # 文本清洗:去标签、去停用词 │ ├── sentiment.py # 情感分析入口 │ └── wordcloud.py # 热词统计 ├── db/ │ ├── init.sql # 建表脚本 │ └── mysql_client.py # pymysql 封装 ├── web/ │ ├── app.py # Flask 提供查询接口 │ └── templates/ └── main.py # 定时调度入口这样分层的好处是,每一层都可以单独测试。比如clean.py写好后,在命令行直接python -c "from analysis.clean import clean_text; print(clean_text('<p>测试</p>'))"就能验证,不必把整个系统跑起来。db/mysql_client.py封装统一的get_connection()和execute()方法,避免每个模块自己写一遍连接参数。
2.3 环境与依赖:python 3.8 起步的安装顺序
先明确依赖清单,再动手装环境。舆情系统不像 Web 框架那么吃版本,但pymysql、requests、jieba、snownlp、pyecharts这几个包的组合在 Python 3.8 到 3.11 下都比较稳。如果你看到项目文档里写的是 Python 3.6,也别急着降版本,把snownlp升到最新版即可。
pip install requests pymysql jieba snownlp pyecharts flask装包时最常见的报错是ModuleNotFoundError: No module named 'xxx',这通常不是包没装上,而是当前终端激活的 Python 环境和项目要求的不是同一个。用python --version和which python先确认环境路径。另一个高频坑是pyecharts渲染时提示缺echarts静态文件,这是因为版本从 1.x 升级后不再内置静态资源,改用在线 CDN,服务器没有外网时需要在pyecharts配置里设置本地资源路径,这个问题留到第五章展开。
提示:
requirements.txt里如果固定了pyecharts==1.9.1,别轻易升 2.x,接口变动很大,旧代码里的Page()和add参数大部分要重写。
3. 用 Python 爬虫把舆情文本抓进来:请求头、频率与去重
3.1 一个最小可用的采集脚本(requests + 新闻列表页)
舆情采集不需要花哨的框架,requests加BeautifulSoup是性价比最高的组合。抓取逻辑分两步:先解析列表页拿到详情页 URL,再逐个抓详情页正文。这里给一个列表页最小脚本:
import requests from bs4 import BeautifulSoup HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/120.0.0.0 Safari/537.36" } def parse_list_page(url): resp = requests.get(url, headers=HEADERS, timeout=10) resp.encoding = "utf-8" # 部分站点是 gbk,需要按实际调整 soup = BeautifulSoup(resp.text, "html.parser") news_links = [] # 根据目标站点的实际结构选择:可以按 class 精确匹配,也可以退而求其次 for a in soup.select("a[href*='/news/']"): href = a.get("href") if href and href.startswith("http"): news_links.append(href) return list(set(news_links)) # 列表页内去重 if __name__ == "__main__": links = parse_list_page("https://example.com/news") print(f"发现 {len(links)} 条待抓链接")resp.encoding = "utf-8"这行容易漏。requests会优先用响应头里的charset判断编码,但不少中文站点响应头没写或写错,导致resp.text乱码。更稳妥的做法是resp.apparent_encoding自动探测,不过会损失一点性能,数据量大时建议手动指定。选择器a[href*='/news/']是故意放宽的,新闻站点列表页的链接通常都带/news/特征,这样写比死磕精确 CSS 类名抗页面改版。
3.2 反爬和频率控制:time.sleep 与 User-Agent 池
舆情采集对实时性要求不算苛刻,慢一点没关系,但被封了整条采集链路就断了。控制采集频率是刚需,常见做法是每次请求之间随机休眠 1 到 3 秒,同时维护一个 User-Agent 池轮换。
import random import time from itertools import cycle UA_POOL = [ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Firefox/121.0", "Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) Safari/605.1.15", ] def safe_request(url, retry=3): ua_cycle = cycle(UA_POOL) for attempt in range(retry): try: resp = requests.get( url, headers={"User-Agent": next(ua_cycle)}, timeout=15 ) if resp.status_code == 200: return resp elif resp.status_code in (403, 429): time.sleep(10 * (attempt + 1)) # 被限流则指数退避 except requests.RequestException as exc: print(f"第 {attempt + 1} 次请求失败: {exc}") time.sleep(5) return Nonesafe_request把请求封装成可重试的单元,业务代码只管调用。这里的关键参数是403/429状态码后的等待时间,第一次失败等 10 秒,第二次 20 秒,第三次 30 秒,给站点恢复时间。cycle(UA_POOL)是 Python 内置的工具,无限循环轮换 UA,比随机取更均匀,避免短时间窗口内大量命中同一个 UA。
3.3 对比 Selenium:轻量采集为何够用
| 维度 | requests + BeautifulSoup | Selenium |
|---|---|---|
| 执行速度 | 快,每秒可发多个请求 | 慢,每个页面要等浏览器渲染 |
| 反爬能力 | 依赖请求头和频率控制 | 能过部分 JS 渲染校验 |
| 资源占用 | 轻,单进程可跑几千个 URL | 重,每个实例吃数百 MB 内存 |
| 适用场景 | 静态列表页、详情页 | 动态加载、需点击翻页的页面 |
舆情系统中 90% 的新浪、网易、搜狐类新闻页都是服务端渲染,详情页正文在 HTML 里直接存在,用requests就能解析。只有当目标站点是Ajax动态加载内容、翻页和点击都靠 JS 驱动时才需要上 Selenium。我一般会先写个小脚本测一个列表页能否在resp.text中搜到正文关键词,搜不到再升级方案。
4. 文本预处理到情感判读:jieba 分词与 SnowNLP 的配合
4.1 清洗环节:去 HTML 标签、去停用词、去重复
爬下来的正文里全是<p>、<script>、转义符和空白字符,直接丢给情感分析模型会严重影响准确率。清洗是舆情分析里投入产出比最高的一步,做得好,后面情感判断准确率能提升 10 个百分点以上。
import re import jieba def clean_text(raw_html): # 去掉 script 和 style 包裹的整块内容 text = re.sub(r"(?s)<(script|style).*?</\1>", "", raw_html) # 去掉所有 HTML 标签 text = re.sub(r"<[^>]+>", "", text) # 反转义: & " 等 text = text.replace(" ", " ").replace("&", "&").replace(""", "\"") # 压缩连续空白为单个空格 text = re.sub(r"\s+", " ", text).strip() return text def cut_words(text, stopwords_file="stopwords.txt"): words = jieba.lcut(text) stopwords = set() with open(stopwords_file, encoding="utf-8") as fp: for line in fp: stopwords.add(line.strip()) # “的、了、在、是”等无意义词,以及单个字符,全部过滤 return [w for w in words if w not in stopwords and len(w) > 1]正则里(?s)让.能匹配换行,否则<script>里的跨行内容会残留下来。反转义的顺序也有讲究,先处理 再压缩空白,避免转换后产生多余的连续空格。jieba.lcut默认全模式比jieba.cut返回更全的候选词,但噪声也大,舆情场景下更推荐lcut配合停用词表做过滤。停用词表可以从 GitHub 上找公开的“中文停用词表”,通常一两千行,覆盖量级足够。
4.2 情感得分与阈值:SnowNLP 输出的 0.5 不等于中性
SnowNLP 的情感分析返回的是一个 0 到 1 之间的概率值,越接近 1 越积极,越接近 0 越消极。但新手最容易踩的坑是拿 0.5 当分界线:得分 0.49 算消极,0.51 算积极。实际使用时,0.4 到 0.6 这个区间几乎都是含糊表达,应该归为中性。阈值设计直接影响最后报告的口径。
from snownlp import SnowNLP def analyze_sentiment(text): s = SnowNLP(text) score = s.sentiments # float 0~1 if score >= 0.6: label = "积极" elif score <= 0.4: label = "消极" else: label = "中性" # 保留两位小数,配合数据库 DECIMAL(4,2) return round(score, 2), label阈值 0.6/0.4 不是拍脑袋。SnowNLP 的训练语料偏电商评论,对新闻文本先天不敏感,很多客观陈述会被打成消极。把中性区拉宽到 0.4 到 0.6,能明显减少“误报消极”的情况。想要更精细,可以自己标注几百条数据后用SnowNLP的Bayes类重新训练,但成本偏高,项目源码里给到的阈值如果不带验证数据,建议先用默认。
常见误用是把SnowNLP(text).sentiments直接存库,不做阈值映射。这样出的报表很难讲:0.55 算好还是坏?做 PPT 时没有故事线。更好的实践是库里同时存sentiment_score和sentiment_label,报表直接按 label 分组计数。
4.3 热词与聚类:TF-IDF 提取关键词,让舆情焦点可见
情感只能看出“是好事还是坏事”,看不出“大家在讨论什么”。这一步用 TF-IDF 提取每篇文章的关键词,按词频汇总,能做词云、出热词排行,也是最后 PPT 里最能体现工作量的图表。
from jieba.analyse import extract_tags def extract_keywords(text, top_k=10): # allowPOS 限定名词、动词、形容词,过滤掉虚词 keywords = extract_tags( text, topK=top_k, withWeight=True, allowPOS=("n", "ns", "nt", "vn", "v", "a") ) return [(word, round(weight, 4)) for word, weight in keywords] # 示例:对清洗后的文本提取 for keyword, weight in extract_keywords(clean_text(article)): print(f"{keyword}: {weight}")extract_tags本质是 TF-IDF 的 jieba 实现,allowPOS参数把词性限定在名词、地名、机构名、动词和形容词上,避免返回“我们”“这个”之类的高频代词。withWeight=True返回权重,做词云时直接把权重映射到字号,比单纯按出现次数排序更能反映主题集中度。
5. 让结果讲人话:pyecharts 可视化与数据库脚本回写
5.1 情感趋势图:pyecharts 的最小可用代码
舆情系统最后出口是图和报表。用 pyecharts 画时间维度上的情感趋势折线图,是所有可视化里信息密度最高的。下面这段代码直接读 MySQL 数据并按天聚合:
from pyecharts.charts import Line from pyecharts import options as opts import pymysql def load_trend_data(days=7): conn = pymysql.connect(host="localhost", user="root", password="123456", database="opinion", charset="utf8mb4") cursor = conn.cursor() sql = """ SELECT DATE(a.publish_time) AS d, SUM(s.sentiment_label = '积极') AS pos, SUM(s.sentiment_label = '消极') AS neg FROM news_article a JOIN news_sentiment s ON a.id = s.article_id WHERE a.publish_time >= DATE_SUB(NOW(), INTERVAL %s DAY) GROUP BY DATE(a.publish_time) ORDER BY d """ cursor.execute(sql, (days,)) rows = cursor.fetchall() cursor.close() conn.close() return rows rows = load_trend_data(7) line = ( Line() .add_xaxis([str(r[0]) for r in rows]) .add_yaxis("积极", [r[1] for r in rows]) .add_yaxis("消极", [r[2] for r in rows]) .set_global_opts(title_opts=opts.TitleOpts(title="近 7 日舆情情感走势")) ) line.render("trend.html")SUM(s.sentiment_label = '积极')是 MySQL 的表达式用法,布尔条件成立返回 1,不成立返回 0,直接按天统计出积极和消极数量,比先取数再在 Python 里组长更省事。DATE_SUB(NOW(), INTERVAL %s DAY)参数化动态日期,配合%s占位符传递 days,防止拼接参数时引发 SQL 注入风险。render("trend.html")会生成一个独立 HTML 文件,内含全部渲染逻辑,直接浏览器打开即可,也能嵌入 PPT 演示前的本地展示。
5.2 全流程跑通的冷启动顺序:从建库到出图
拿到一个项目源码包,最怕读了一堆代码不知道从哪下手。舆情系统的冷启动顺序是固定的:
- 执行
db/init.sql,建库建表。先确认 MySQL 已启动,并且charset=utf8mb4未报错。 - 修改
db/mysql_client.py里的连接参数,账号、密码、端口逐一核对。 - 单跑爬虫模块,抓 50 条数据入库,
SELECT COUNT(*) FROM news_article;验证写入成功。 - 单跑分析模块,对刚才入库的文本做清洗和情感分析,回写
news_sentiment表。 - 最后启动 Flask 服务或直接运行可视化脚本,出 HTML 报告。
最后一步的常见坑:pyecharts生成的 HTML 图形不显示。检查打开的文件路径是否含中文或空格,浏览器对本地文件路径的处理容易出问题;换成英文路径基本能解决。另外如果你的部署机器没有外网,需要在图表生成那里加一行.add_js_libs([])或在set_global_opts里指定本地 echarts 资源路径,否则图表控件加载不出来。
把news_sentiment表的sentiment_label字段按天分组算出积极占比,再拉出消极占比最高的前 10 条原文章标题,这两张图配上一段原因分析文字,就是完整的一份舆情周报核心页。论文的 LW 部分把这三个步骤展开写清楚:数据建模、文本清洗与情感阈值设计、可视化验证,PPT 则按“背景 → 架构 → 关键技术 → 成果展示 → 总结”五页组织,这套系统的可交付材料就齐了。
本文还有配套的精品资源,点击获取