☰
Python网络舆情分析系统源码解析:从环境搭建到情感分析实战
2026/10/13 2:36:56 网站建设 项目流程

简介:这是一套面向高校学生与Python开发者的网络舆情分析系统完整项目,可作为毕业设计、课程设计或实战练手参考,帮助解决从数据采集到可视化展示的全流程实现问题。压缩包共287个文件,约75.63MB,包含42个py源码文件、12个html页面、34个js脚本、15个css样式及数据库sql脚本,另有gif演示、jpg截图与模型pb文件等,前后端代码与数据库脚本一应俱全。项目基于Python后台框架,前端采用html,开发环境为pycharm,数据库配合Navicat管理,界面美观、操作简单、功能齐全,且经过严格调试可稳定运行。目前已有206人学习下载,读者可从中获取完整源码、数据库脚本与部署说明,理清舆情系统的模块划分与目录结构,掌握依赖安装与运行调试思路,适合需要快速搭建项目或对照学习的中级开发者。

1. 从一份“python的网络舆情分析系统(完整源码+说明).zip”说起:它到底能解决什么问题

你手上如果正好有一份名为“python的网络舆情分析系统(完整源码+说明).zip”的压缩包,大概率会经历三个阶段:解压后一脸懵、照着说明跑不起来、跑起来发现数据是假的。这不是你水平不行,而是这类“完整源码”项目天然存在一个断层——它把采集、清洗、分析、可视化全塞进一个工程里,却没有告诉你每个模块的边界在哪、参数为什么这么设、换一个平台数据源会崩在哪。

网络舆情分析系统的核心链路其实很固定:数据采集 → 文本清洗 → 情感/主题分析 → 可视化与预警。它适合两类人:一是想拿一个完整 Python 工程练手、把 pandas、requests、jieba、snownlp、flask 串起来的学生或转行者;二是需要快速搭一个内部舆情看板的运营或产品岗,不追求工业级吞吐,只求能跑通、能改、能看懂。这份源码的价值不在“完整”,而在它提供了一个可拆解的最小骨架——你可以把采集层换成自己的数据源,把分析层换成更准的模型,把展示层换成公司现有的 BI 工具。

但前提是,你得先知道这个骨架的关节在哪。下面按“环境怎么搭 → 数据怎么进来 → 分析怎么做 → 界面怎么出 → 坑怎么避”的顺序,把这份源码里最值得复现的部分拆开讲。

2. 把源码跑起来:python环境搭建与依赖安装的实操路径

2.1 先确认你的 python 版本和包管理方式

这类舆情系统源码通常写于 Python 3.6~3.9 之间,用 requirements.txt 管理依赖。如果你本地是 Python 3.12,直接 pip install 大概率会在 snownlp 或 jieba 的编译环节翻车。我一般会先用 conda 建一个隔离环境,把版本锁在 3.8:

conda create -n yuqing python=3.8 conda activate yuqing python -V

逻辑说明:舆情系统里常用的 snownlp 依赖旧版 numpy,而 numpy 在 3.9 之后对 C 扩展的编译要求变严。锁 3.8 不是玄学,是省去后面一堆“要安装缺失的节点,请先在你的 python 环境中运行 pip install”的报错。参数上,环境名随意,但 python 版本建议 3.8 或 3.9,不要低于 3.6,否则 f-string 和部分异步写法会报语法错。

如果你习惯用 vscode 配置 python 开发环境,记得在 settings.json 里把默认解释器指向这个 conda 环境,否则终端里 pip 装上了、编辑器里还是找不到包。

2.2 依赖安装:先装 numpy,再装其余

requirements.txt 里通常包含 requests、pandas、numpy、jieba、snownlp、flask、pyecharts、sqlalchemy。直接一把梭 pip install -r requirements.txt 在部分网络环境下会卡在 numpy 源码编译。稳妥做法是先单独装 numpy 和 pandas 的 wheel:

pip install numpy==1.21.6 pandas==1.3.5 -i https://pypi.tuna.tsinghua.edu.cn/simple pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple

逻辑说明:numpy 1.21.6 是最后一个对 Python 3.8 提供完整预编译 wheel 的稳定版本之一,先把它钉死,后面 snownlp 就不会触发源码编译。参数上,-i 指定镜像源只是加速,不影响包内容;如果你公司有内网源,换成内网地址即可。装完后用 pip list 核对 jieba、snownlp 是否都在,缺哪个单独补。

提示:如果 pip install 报“Microsoft Visual C++ 14.0 is required”,说明你在 Windows 上触发了源码编译。回到上一步,确认 numpy 是 wheel 安装而不是源码安装。

2.3 数据库和配置文件的初始化

源码里一般有一个 config.py 或 settings.py,里面写着数据库连接、采集关键词、定时任务间隔。常见的是 SQLite 做默认存储,MySQL 做生产存储。先看 config 里的 DATABASE_URI:

# config.py 片段 DATABASE_URI = 'sqlite:///yuqing.db' # 默认本地文件,零配置 # DATABASE_URI = 'mysql+pymysql://root:password@127.0.0.1:3306/yuqing?charset=utf8mb4' KEYWORDS = ['品牌名', '产品名'] # 采集关键词,改成你要监控的词 CRAWL_INTERVAL = 300 # 采集间隔,单位秒

逻辑说明:SQLite 适合先跑通,不用装 MySQL 服务;等你确认分析逻辑没问题,再切 MySQL。KEYWORDS 是舆情系统的入口参数,改错这里后面所有图表都是空的。CRAWL_INTERVAL 设太小会被目标站点限流,设太大实时性差,300 秒是多数内部看板的折中值。

初始化数据库通常靠一个 init_db.py 或 flask shell 里的 db.create_all():

python init_db.py # 或者 flask shell >>> from app import db >>> db.create_all()

跑完检查目录下有没有生成 yuqing.db 文件,有就说明表结构建好了。这一步失败多半是模型定义里字段类型和数据库不兼容,比如 SQLite 不支持某些 MySQL 特有字段,换回 SQLite 默认配置即可。

3. 数据从哪来:采集层的最小实现与反爬边界

3.1 用 requests + BeautifulSoup 跑通单页采集

舆情系统的采集层通常分两种:一种是针对新闻站点或论坛的 HTML 解析,一种是针对开放 API 的 JSON 拉取。源码里最常见的是前者。下面是一个可独立运行的最小采集函数:

import requests from bs4 import BeautifulSoup import time def fetch_page(url): headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ' 'AppleWebKit/537.36 (KHTML, like Gecko) ' 'Chrome/120.0 Safari/537.36' } resp = requests.get(url, headers=headers, timeout=10) resp.encoding = resp.apparent_encoding # 防止中文乱码 soup = BeautifulSoup(resp.text, 'html.parser') items = [] for node in soup.select('.article-item'): # 选择器按目标站点改 title = node.select_one('.title').get_text(strip=True) content = node.select_one('.content').get_text(strip=True) pub_time = node.select_one('.time').get_text(strip=True) items.append({'title': title, 'content': content, 'pub_time': pub_time}) return items if __name__ == '__main__': data = fetch_page('https://example.com/news') for d in data[:3]: print(d['title']) time.sleep(1) # 单次采集后休眠,降低被封风险

逻辑说明:headers 里的 User-Agent 不是可选项,很多站点对空 UA 直接返回 403。resp.apparent_encoding 比 resp.encoding 更可靠,能自动识别 GBK 和 UTF-8。soup.select 里的 CSS 选择器必须按你实际要采的站点改,源码里写死的选择器换一个站点就失效,这是最常见的“跑起来没数据”原因。time.sleep(1) 是礼貌采集,也是保命手段。

参数上,timeout=10 是单次请求上限,超过就抛异常;如果你采的是慢站点,可以调到 20,但不要不设,否则线程会挂死。

3.2 把采集结果写进数据库而不是 CSV

很多源码为了演示方便,把采集结果存成 CSV,然后分析层再读 CSV。这在单机演示没问题,但一旦你要做增量采集和去重,CSV 就是灾难。正确做法是采集完直接入库,用 URL 或标题做唯一索引:

from sqlalchemy import create_engine, Column, Integer, String, Text, DateTime from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker from datetime import datetime Base = declarative_base() class Post(Base): __tablename__ = 'posts' id = Column(Integer, primary_key=True) title = Column(String(255), unique=True) # 标题唯一,天然去重 content = Column(Text) pub_time = Column(String(64)) created_at = Column(DateTime, default=datetime.now) engine = create_engine('sqlite:///yuqing.db') Base.metadata.create_all(engine) Session = sessionmaker(bind=engine) def save_posts(items): session = Session() for it in items: if not session.query(Post).filter_by(title=it['title']).first(): session.add(Post(**it)) session.commit() session.close()

逻辑说明:unique=True 让数据库层帮你做去重,比在 Python 里维护一个 seen 集合更可靠,因为重启程序后集合就没了。created_at 记录入库时间,和 pub_time 区分开,后面做“采集延迟”分析时有用。参数上,String(255) 对标题够用,正文用 Text 不限制长度。

注意:如果你采的是同一站点的多个栏目,标题可能重复但内容不同,这时候唯一索引要改成 URL 或 title+pub_time 组合,否则会丢数据。

3.3 采集频率与反爬的边界

源码里通常写一个 while True 加 sleep 的循环,或者用 APScheduler 做定时。我一般会把采集频率控制在目标站点 robots.txt 允许的范围内,并且对同一域名做串行采集,不做并发。原因很简单:舆情系统不是爬虫竞赛,你要的是稳定拿到数据,不是把对方打挂。如果源码里用了多线程采集,先把它改成单线程,跑稳了再考虑加代理池——但代理池本身又是一个坑,后面避坑章节会讲。

采集层还有一个容易被忽略的点:编码。中文站点有 GBK、GB2312、UTF-8 三种常见编码,requests 的 apparent_encoding 能覆盖大部分,但遇到混合编码的页面还是会出乱码。这时候可以在入库前做一次清洗,把不可见字符和乱码替换掉,具体在下一章清洗环节讲。

4. 文本清洗与情感分析:jieba 分词和 snownlp 的真实表现

4.1 清洗:去 HTML 标签、去表情、去重复

采集回来的正文往往带着 HTML 标签、@某人、话题标签、表情符号。不清洗直接分词,jieba 会把标签当成词,snownlp 的情感分也会被带偏。一个够用的清洗函数:

import re def clean_text(text): text = re.sub(r'<[^>]+>', '', text) # 去 HTML 标签 text = re.sub(r'@[\w\u4e00-\u9fa5]+', '', text) # 去 @提及 text = re.sub(r'#([^#]+)#', r'\1', text) # 话题标签保留内容 text = re.sub(r'\[.*?\]', '', text) # 去表情占位符 text = re.sub(r'http[s]?://\S+', '', text) # 去链接 text = re.sub(r'\s+', ' ', text).strip() # 合并空白 return text

逻辑说明:正则顺序有讲究,先去标签再去 @ 和链接,否则标签里的属性可能被误伤。话题标签保留内容是因为话题词本身有分析价值。表情占位符在微博类数据里很常见,去掉后情感分析更准。参数上,\u4e00-\u9fa5 是中文 Unicode 范围,覆盖常用汉字。

清洗完的文本建议再做一个长度过滤,少于 5 个字的直接丢弃,因为太短的文本情感分析没有意义。

4.2 jieba 分词与自定义词典

jieba 默认词典对通用文本够用,但舆情系统里往往有品牌名、产品名、行业术语,这些词被切碎后主题分析会失准。做法是加载自定义词典:

import jieba jieba.load_userdict('userdict.txt') # 每行一个词,可带词频和词性 text = '这款产品的续航表现超出预期' words = jieba.lcut(text) print(words) # 输出示例:['这款', '产品', '的', '续航', '表现', '超出', '预期']

逻辑说明:userdict.txt 里每行格式是“词语 词频 词性”,词频可省略。把品牌名加进去后,分词结果里品牌名会作为一个整体出现,后面做词频统计和主题聚类时就不会被拆散。参数上,jieba.lcut 返回列表,jieba.cut 返回生成器,数据量大时用 cut 省内存。

停用词表也要加,把“的、了、是、在”这类词过滤掉,否则词云里全是无意义的高频词。

4.3 snownlp 情感分析的准确率边界

snownlp 是这类源码里最常用的情感分析库,优点是零训练、开箱即用,缺点是准确率在 70%~80% 之间,且对反讽、双重否定、行业黑话几乎无感。用法很简单:

from snownlp import SnowNLP def sentiment_score(text): if not text or len(text) < 5: return None s = SnowNLP(text) return s.sentiments # 0~1,越接近 1 越正面 # 示例 print(sentiment_score('这个功能太难用了')) # 约 0.1 print(sentiment_score('非常好用,推荐')) # 约 0.9

逻辑说明:sentiments 返回的是概率值,不是绝对标签。我一般会设两个阈值:大于 0.6 算正面,小于 0.4 算负面,中间算中性。这样比直接 0.5 切分更稳,因为 snownlp 在 0.5 附近抖动很大。参数上,阈值可以根据你的业务调整,舆情预警通常把负面阈值设高一点(比如 0.35),宁可多报不可漏报。

如果你发现 snownlp 在你的领域数据上表现太差,可以考虑换成基于 BERT 的微调模型,但那需要标注数据,成本高一个量级。对于内部看板,snownlp 加人工抽检就够了。

4.4 主题分析:TF-IDF 和 LDA 的取舍

源码里做主题分析通常用两种:TF-IDF 提取关键词,LDA 做主题聚类。TF-IDF 简单快,适合做词云和热词榜;LDA 能给出主题分布,但需要调参,且结果不稳定。我的建议是先用 TF-IDF 跑通,确认数据质量没问题,再上 LDA。

from sklearn.feature_extraction.text import TfidfVectorizer corpus = [' '.join(jieba.lcut(clean_text(t))) for t in texts] vectorizer = TfidfVectorizer(max_features=100, stop_words=stopwords) tfidf = vectorizer.fit_transform(corpus) keywords = vectorizer.get_feature_names_out() print(keywords[:20])

逻辑说明:max_features=100 控制输出维度,太大反而看不出重点。stop_words 传入停用词列表。注意这里先用 jieba 分词再用空格连接,是因为 TfidfVectorizer 默认按空格切词,不先分词的话中文会被当成一个整体。参数上,ngram_range 可以设 (1,2) 来捕捉“续航 表现”这样的二元词组,但会增大计算量。

LDA 的话,sklearn 的 LatentDirichletAllocation 需要指定 n_components(主题数),这个数没有标准答案,一般从 5 开始试,看主题是否可解释。主题数太多会碎片化,太少会混在一起。

5. 可视化与预警:flask + pyecharts 把分析结果变成看板

5.1 用 flask 起一个最小 Web 服务

源码里的展示层通常是 flask 加模板渲染,或者 flask 提供 API、前端用 echarts 渲染。先跑通一个最小 flask 应用:

from flask import Flask, render_template, jsonify from sqlalchemy import create_engine import pandas as pd app = Flask(__name__) engine = create_engine('sqlite:///yuqing.db') @app.route('/') def index(): return render_template('index.html') @app.route('/api/sentiment') def sentiment_data(): df = pd.read_sql('SELECT pub_time, sentiment FROM posts', engine) df['date'] = pd.to_datetime(df['pub_time']).dt.date trend = df.groupby('date')['sentiment'].mean().reset_index() return jsonify(trend.to_dict(orient='records')) if __name__ == '__main__': app.run(debug=True, host='0.0.0.0', port=5000)

逻辑说明:/api/sentiment 返回按日期聚合的平均情感值,前端拿到后画折线图。pd.read_sql 直接读数据库,省去手动拼 SQL。参数上,host='0.0.0.0' 让局域网内其他机器也能访问,debug=True 只在开发时开,生产要关掉。端口 5000 是 flask 默认,冲突就换。

5.2 pyecharts 生成情感趋势图和词云

pyecharts 比 matplotlib 更适合做 Web 看板,因为它输出的是 HTML,可以直接嵌到 flask 模板里。生成情感趋势图:

from pyecharts.charts import Line from pyecharts import options as opts def sentiment_line(dates, values): line = ( Line() .add_xaxis(dates) .add_yaxis('情感均值', values, is_smooth=True) .set_global_opts( title_opts=opts.TitleOpts(title='舆情情感趋势'), yaxis_opts=opts.AxisOpts(min_=0, max_=1) ) ) return line.render_embed()

逻辑说明:is_smooth=True 让折线平滑,视觉上更容易看出趋势。yaxis 锁定 0~1 是因为情感值本身就在这个区间,不锁的话 pyecharts 会自动缩放,反而看不出波动幅度。render_embed() 返回 HTML 字符串,直接传给模板变量。

词云用 pyecharts 的 WordCloud,数据来自 TF-IDF 或 jieba 词频统计。注意词云里要过滤掉停用词和单字,否则会出现大量无意义的“的”“了”。

5.3 预警:阈值触发与消息推送

舆情系统的预警逻辑通常是:当负面情感占比超过阈值,或者某个关键词出现频率突增时,触发通知。最小实现是在定时任务里跑一个检查:

def check_alert(): df = pd.read_sql('SELECT sentiment FROM posts WHERE created_at > date("now", "-1 day")', engine) negative_ratio = (df['sentiment'] < 0.4).mean() if negative_ratio > 0.3: send_notification(f'负面舆情占比 {negative_ratio:.1%},请关注')

逻辑说明:这里只统计最近一天的数据,避免历史数据干扰。negative_ratio 阈值 0.3 是经验值,可以根据行业调整。send_notification 可以是邮件、企业微信机器人或钉钉机器人,具体实现取决于你公司的工具链。参数上,date("now", "-1 day") 是 SQLite 的日期函数,MySQL 里换成 DATE_SUB(NOW(), INTERVAL 1 DAY)。

注意:预警频率要控制,不要每次检查都发,否则会变成骚扰。我一般会加一个冷却时间,同一类预警 30 分钟内只发一次。

6. 避坑与排查:这份源码跑不起来时先看这五条

6.1 现象:pip install 卡在 Building wheel for snownlp

原因:snownlp 的 setup.py 在部分环境下会触发源码编译,而编译需要 C 编译器。解决:先确认 numpy 是 wheel 安装,然后单独 pip install snownlp --no-build-isolation,或者直接下载 snownlp 的 wheel 文件本地安装。如果还不行,换 Python 3.8 环境重试。

6.2 现象:采集回来的中文全是乱码

原因:requests 自动识别的编码和目标站点实际编码不一致,常见于 GBK 站点被识别成 ISO-8859-1。解决:手动指定 resp.encoding = 'gbk' 或 'utf-8',或者在清洗函数里加一步编码检测。如果入库后才发现乱码,用 Python 的 chardet 库对原始字节做检测再解码。

6.3 现象:情感分析结果全是 0.5 左右

原因:snownlp 对短文本和中性文本的区分度低,或者输入文本没有清洗干净,混入了大量无意义字符。解决:先做长度过滤(少于 5 字丢弃),再检查清洗函数是否去掉了 HTML 和表情。如果还是不行,考虑换用 SnowNLP 的贝叶斯模型重新训练,但需要标注数据。

6.4 现象:flask 启动后访问 5000 端口无响应

原因:可能是端口被占用,或者 host 设成了 127.0.0.1 导致局域网无法访问,也可能是防火墙拦截。解决:先 lsof -i:5000 查占用,换端口;host 改成 0.0.0.0;检查防火墙规则。如果是在服务器上跑,还要确认安全组是否放行。

6.5 现象:数据库里数据在涨,但图表不更新

原因:前端请求的 API 有缓存,或者 pandas 读取的是旧连接。解决:在 API 返回头里加 Cache-Control: no-cache,或者在 flask 里每次请求都新建数据库连接。另外检查前端是不是写死了初始数据,没有重新请求接口。

7. 进阶技巧:把情感分析从“能跑”推到“能用”

如果你已经跑通了上面所有步骤,接下来最值得投入的不是加功能,而是验证情感分析的准确率。snownlp 在通用语料上大概 75% 准确率,但在你的垂直领域可能只有 60%。我一般会做一件事:从数据库里随机抽 200 条,人工标注正面/负面/中性,然后和 snownlp 的结果做混淆矩阵。如果负面召回率低于 70%,就说明漏报严重,预警形同虚设。

提升路径有两条。第一条是规则兜底:把行业里明确的负面词(比如“投诉”“退款”“故障”)做成词典,命中就直接标负面,不依赖模型。第二条是换模型:用 bert-base-chinese 加一个分类头,在标注数据上微调。后者需要至少 1000 条标注数据,但准确率能到 90% 以上。我自己的习惯是先用规则兜底跑一个月,积累够标注数据再上 BERT。

还有一个容易被忽略的点:时间窗口。舆情是有时效的,三天前的负面和今天的负面权重完全不同。我一般会在情感均值上做一个指数衰减,越近的数据权重越高。具体做法是在 pandas 里加一列 weight = exp(-days/7),然后算加权平均。这个改动很小,但看板的敏感度会明显提升。

最后说一个血泪教训:不要在没有备份的情况下直接改源码里的数据库模型。我有一次把 Post 表的 title 字段从 String(255) 改成 Text,SQLite 不支持直接改列类型,结果整张表重建,数据全丢。后来我养成了一个习惯:任何模型改动前先 cp yuqing.db yuqing.db.bak,后悔药不贵,但得提前备。希望帮到你。

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

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

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

立即咨询