简介:这是一套面向计算机、电子信息、应用数学等专业学生的Python舆情监控系统完整源码,适用于课程设计、期末大作业或毕业设计等学习场景。项目整合了网络舆情信息抓取、多维度情感分析、时序趋势预测算法与交互式可视化看板等核心模块,采用结构化程序设计,兼顾代码可读性与可扩展性,并基于Matplotlib与ECharts提供双重可视化方案,数据集覆盖社会热点事件的多平台文本样本。资源包共142个文件,约45.19MB,以27个py源码、36个txt语料与说明、23个zbak备份、12个jar及10个class等为主,另含java、xml、ipynb、json等配置与实验文件,结构完整便于按模块查阅。目前已有78人学习下载。读者可借此掌握非结构化舆情数据的采集、情感挖掘与趋势推演全流程,理解机器学习方法在真实文本分析中的落地思路,并获得可复用的可视化组件与工程组织参考。
1. 舆情监控系统到底在监控什么:从一条负评到一张预警大屏
电商运营凌晨两点被电话叫醒,原因是某款主推商品评论区在三个小时内涌进两百多条差评,等人工发现时链接权重已经掉了一半。这类场景催生了基于 Python 的舆情监控系统:它定时抓取指定平台或渠道的公开文本,做清洗、情感判定、热度聚合,再把结果落到可视化看板和预测模型上,让运营在情绪扩散前拿到信号。这套源码方案适合两类人:一类是想把 python 数据分析与可视化 真正跑成一条流水线的后端或数据工程师,另一类是手里有业务数据、想快速搭出可视化大屏和预警逻辑的产品或运营同学。它不解决“拿到平台私有接口”的问题,也不承诺预测百分百准确,它解决的是从原始文本到可决策指标之间那段最耗人力的脏活。下面按数据采集、分析建模、可视化预测、避坑、进阶五段拆开讲,每一步都给到能直接抄的代码和参数。
2. 数据采集与清洗:把散落文本变成结构化舆情表
2.1 采集层选型:为什么用 requests + 定时调度而不是重型框架
舆情系统的第一道坎不是算法,是稳定拿到数据。常见做法有两种:一是用 Scrapy 这类爬虫框架,二是用 requests 配合调度器自己控节奏。我一般会选后者,原因是舆情采集的目标站点往往结构简单、字段固定,Scrapy 的中间件和去重机制反而增加调试成本,而 requests 加一个轻量调度就能覆盖大部分场景。真正需要框架的是大规模分布式抓取,那是另一个量级的问题。
采集频率是第一个要定的参数。评论区这类高频变动数据,建议 5 到 15 分钟一轮;新闻和论坛帖可以放宽到 30 分钟。频率定太高会触发目标站点的访问限制,定太低又失去预警意义。下面是一个最小可用的采集函数,带重试和随机间隔:
import requests import time import random from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry def build_session(): session = requests.Session() retry = Retry( total=3, # 最多重试3次 backoff_factor=1.5, # 退避系数,重试间隔递增 status_forcelist=[429, 500, 502, 503] ) adapter = HTTPAdapter(max_retries=retry) session.mount("http://", adapter) session.mount("https://", adapter) session.headers.update({ "User-Agent": "Mozilla/5.0 (compatible; OpinionMonitor/1.0)" }) return session def fetch_page(session, url, timeout=10): try: resp = session.get(url, timeout=timeout) resp.raise_for_status() return resp.text except requests.RequestException as e: print(f"采集失败 {url}: {e}") return None def polite_sleep(base=2.0, jitter=1.0): time.sleep(base + random.uniform(0, jitter))这段代码里total=3控制重试上限,backoff_factor=1.5让每次重试等待时间按倍数增长,避免短时间内反复冲击目标站点。polite_sleep里的随机抖动是血泪经验:固定间隔的请求特征太明显,加一点随机性更接近真人浏览节奏。采集到的原始 HTML 不要直接丢进数据库,先落一份本地文件或对象存储,方便后续回溯和重新解析,这是后悔药级别的设计。
2.2 清洗与字段标准化:把 HTML 变成能分析的 DataFrame
拿到原始文本后,下一步是去标签、去表情、去重复,再统一成结构化字段。舆情数据最核心的字段有六个:发布时间、来源渠道、正文、作者标识、互动量(点赞/评论/转发)、原始链接。清洗阶段最容易翻车的是时间格式,不同渠道的时间戳格式五花八门,有毫秒级、有“三小时前”这种相对时间,必须统一转成标准 datetime。
import re import pandas as pd from datetime import datetime, timedelta def clean_text(raw): if not raw: return "" text = re.sub(r"<[^>]+>", "", raw) # 去HTML标签 text = re.sub(r"[\U0001F300-\U0001FAFF]", "", text) # 去Emoji text = re.sub(r"\s+", " ", text).strip() # 压缩空白 return text def parse_relative_time(text): now = datetime.now() match = re.search(r"(\d+)\s*(分钟|小时|天)前", text) if not match: return None num, unit = int(match.group(1)), match.group(2) delta = {"分钟": timedelta(minutes=num), "小时": timedelta(hours=num), "天": timedelta(days=num)} return now - delta[unit] def build_dataframe(records): df = pd.DataFrame(records) df["正文"] = df["正文"].apply(clean_text) df["发布时间"] = pd.to_datetime(df["发布时间"], errors="coerce") df = df.dropna(subset=["正文", "发布时间"]) df = df.drop_duplicates(subset=["正文", "作者标识"]) df["互动量"] = pd.to_numeric(df["互动量"], errors="coerce").fillna(0) return df.reset_index(drop=True)clean_text里的 Emoji 正则范围覆盖了常见表情符号,如果你的数据里表情有特殊含义(比如情绪判断),可以保留而不是删除。drop_duplicates用正文加作者双字段去重,比单用正文更稳,因为同一句话可能被不同账号转发。清洗完的 DataFrame 建议直接落 Parquet 格式,比 CSV 小且读取快,后续分析阶段直接读这个文件,不要每次重新爬。
提示:采集前先确认目标站点的 robots 协议和公开数据范围,只处理公开可见内容,不碰需要登录才能访问的私有数据。
3. 情感分析与热度聚合:让机器判断一条评论是夸还是骂
3.1 情感判定方案对比:词典法、传统机器学习、预训练模型怎么选
情感分析是舆情系统的核心判断环节,选型直接决定后续预警的准确率。常见三条路线:基于情感词典的规则法、基于 TF-IDF 加逻辑回归的传统机器学习、以及基于预训练模型微调。词典法速度快、可解释性强,但遇到反讽和网络新词就翻车;传统机器学习需要标注数据,但训练成本低、推理快;预训练模型准确率最高,但需要 GPU 资源和标注样本。
我的建议是分阶段走:项目初期用词典法快速跑通链路,积累一批标注数据后切换到传统机器学习,数据量超过五千条且对准确率有硬要求时再上预训练模型。下面是一个词典法加规则修正的最小实现,适合冷启动:
POSITIVE_WORDS = {"好", "满意", "推荐", "喜欢", "不错", "给力", "赞"} NEGATIVE_WORDS = {"差", "垃圾", "失望", "退货", "骗", "慢", "烂"} NEGATION_WORDS = {"不", "没", "别", "无"} def sentiment_score(text): score = 0 tokens = list(text) for i, token in enumerate(tokens): if token in POSITIVE_WORDS: weight = 1 if i > 0 and tokens[i-1] in NEGATION_WORDS: weight = -1 # 否定词翻转极性 score += weight elif token in NEGATIVE_WORDS: weight = -1 if i > 0 and tokens[i-1] in NEGATION_WORDS: weight = 1 score += weight return score def label_sentiment(score): if score > 0: return "正面" elif score < 0: return "负面" return "中性"否定词处理是词典法里最容易被忽略的细节,不喜欢如果按单字匹配会先命中“喜欢”再加“不”,结果判成正向,所以必须做前一字判断。这个实现只做相邻否定,实际项目中还要处理“不是很满意”这类双重修饰,可以扩展成滑动窗口。词典需要根据你的业务领域持续补充,电商场景要加“物流”“客服”相关词,金融场景要加“兑付”“逾期”这类词。
3.2 热度聚合与时间窗口:怎么算出一条舆情的传播势能
单条评论的情感值意义有限,真正有预警价值的是聚合后的热度趋势。热度不能只看数量,要结合互动量和时间衰减。常见做法是定义一个热度分:热度 = 互动量加权和 × 时间衰减因子。时间衰减用指数衰减,半衰期设 6 到 12 小时比较合理,太短会让历史数据失去参考,太长又反应迟钝。
import numpy as np def heat_score(df, half_life_hours=8): now = df["发布时间"].max() delta_hours = (now - df["发布时间"]).dt.total_seconds() / 3600 decay = np.exp(-np.log(2) * delta_hours / half_life_hours) df = df.copy() df["热度"] = df["互动量"] * decay return df def aggregate_by_window(df, freq="1h"): df = df.set_index("发布时间") grouped = df.resample(freq).agg( 总量=("正文", "count"), 负面量=("情感", lambda x: (x == "负面").sum()), 热度=("热度", "sum") ) grouped["负面率"] = grouped["负面量"] / grouped["总量"].replace(0, np.nan) return grouped.fillna(0)half_life_hours=8意味着 8 小时前的互动量权重减半,这个参数要根据你的业务节奏调:快消品舆情变化快,可以设 4 到 6 小时;政策类舆情发酵慢,设 12 到 24 小时更合适。resample("1h")按小时聚合,如果数据量小可以改成"1D"。聚合结果里的负面率是预警的核心指标,通常负面率连续两个窗口超过 30% 就值得人工介入。
4. 可视化与预测模型:从看板到预警的最后一公里
4.1 可视化大屏:用 ECharts 还是 Plotly,数据怎么对接
可视化环节有两个选择:前端用 ECharts 做交互大屏,或者用 Python 的 Plotly/Dash 快速出图。如果团队有前端资源,ECharts 更灵活,适合做可视化大屏;如果只有数据人员,Dash 能在一个 Python 文件里搞定看板和回调。数据对接上,建议后端用 FastAPI 暴露聚合接口,前端定时轮询,不要直接把 DataFrame 塞进模板。
from fastapi import FastAPI import pandas as pd app = FastAPI() @app.get("/api/trend") def get_trend(channel: str = "all", hours: int = 24): df = pd.read_parquet("clean_data.parquet") df = df[df["发布时间"] >= pd.Timestamp.now() - pd.Timedelta(hours=hours)] if channel != "all": df = df[df["来源渠道"] == channel] agg = aggregate_by_window(df, freq="1h") return agg.reset_index().to_dict(orient="records")这个接口的hours参数控制时间范围,channel支持按渠道过滤,返回的是列表结构,前端 ECharts 直接消费。注意pd.Timestamp.now()用的是服务器时间,如果数据跨时区要统一转 UTC 再比较。看板上至少放四块:总量趋势折线、负面率柱状、渠道分布饼图、热词云。热词云用 jieba 分词后统计词频,过滤掉停用词再渲染。
4.2 预测模型:Prophet 和 XGBoost 在舆情场景的分工
预测部分要分清两个任务:一是预测舆情热度的时间走势,二是预测某条内容是否会变成负面爆点。前者是时间序列问题,用 Prophet 或 ARIMA;后者是分类问题,用 XGBoost 或逻辑回归。Prophet 对缺失值和异常值容忍度高,适合舆情这种不规则采样的数据;XGBoost 适合用互动量、作者历史、文本情感值等特征做二分类。
from prophet import Prophet import pandas as pd def forecast_heat(df, periods=12): ts = df.reset_index()[["发布时间", "热度"]].rename( columns={"发布时间": "ds", "热度": "y"}) ts = ts.dropna() model = Prophet( changepoint_prior_scale=0.1, # 趋势灵活度,越大越敏感 seasonality_mode="additive", daily_seasonality=True, weekly_seasonality=True ) model.fit(ts) future = model.make_future_dataframe(periods=periods, freq="1h") forecast = model.predict(future) return forecast[["ds", "yhat", "yhat_lower", "yhat_upper"]]changepoint_prior_scale=0.1是 Prophet 最关键的调参项,默认 0.05 偏保守,舆情数据突变多,调到 0.1 到 0.3 之间能让模型更快响应趋势变化。daily_seasonality和weekly_seasonality打开是因为舆情有明显的时间规律,工作日白天和晚间是高峰。预测结果里的yhat_upper可以作为预警上界,实际热度突破上界就触发告警。
XGBoost 那条线需要构造特征表,核心特征包括:发布后一小时内互动增速、作者历史负面率、文本情感分值、是否含敏感词、发布时段。标签用“是否在 24 小时内进入负面榜前 10%”。特征工程比模型调参更重要,这块做扎实,逻辑回归也能出不错的效果。
注意:预测模型的输出只作为辅助参考,不要做成自动处置的唯一依据,人工复核环节不能省。
5. 避坑与排查:舆情系统上线后最容易翻车的五个地方
5.1 采集被封:现象是请求返回 403 或验证码,原因是频率过高或请求头特征单一
这是最常见的翻车点。现象是运行几小时后采集成功率骤降,日志里大量 403。原因通常是固定间隔请求加上默认 User-Agent 被识别。解决办法是加随机间隔、轮换 User-Agent、必要时引入代理池。但要注意,代理池的维护成本很高,小规模项目优先降频而不是上代理。另外,采集失败要有告警,不能静默失败,否则你以为系统在跑,其实数据早就断了。
5.2 情感误判:现象是明显负面评论被判成中性,原因是词典覆盖不足或否定词处理不全
网络用语更新快,“栓Q”“退退退”这类词词典里没有,规则法直接漏判。解决办法是建立误判样本回收机制,把人工复核中发现的错判样本定期补充进词典或作为新训练数据。如果用的是预训练模型,也要做领域微调,通用模型对行业黑话的识别率并不高。排查时先看混淆矩阵,确认是漏判还是误判,再针对性补数据。
5.3 时间对齐错误:现象是趋势图出现未来时间点或数据堆积在某一刻,原因是时区没统一
采集端用本地时间,数据库存 UTC,分析端又按本地时间聚合,三个环节时区不一致就会出鬼。解决办法是全链路统一用 UTC 存储,只在展示层转本地时间。排查方法是随机抽几条记录,从采集时间戳一路对到看板显示时间,看在哪一步偏移。这个问题隐蔽性强,往往要等趋势图明显异常才被发现。
5.4 预测过拟合:现象是历史拟合很好但未来预测偏差大,原因是特征里混入了未来信息
时间序列预测里最容易犯的错误是把未来才知道的字段当特征,比如用全量数据的均值做归一化。解决办法是严格按时间切分训练集和验证集,归一化参数只用训练集计算。Prophet 相对不容易出这个问题,但 XGBoost 那条线必须做时间序列交叉验证,不能用随机切分。排查时看验证集和测试集的误差差距,差距过大就是过拟合信号。
5.5 看板数据延迟:现象是前端显示的数据比实际晚几小时,原因是聚合任务和采集任务没对齐
采集是每 10 分钟一轮,聚合是每小时一次,看板又是每 30 分钟拉一次,三层节奏不匹配就会让数据看起来延迟。解决办法是把采集、聚合、接口刷新做成一条链,聚合任务在采集完成后触发,接口缓存时间设短一点。排查时在看板上加一个“数据更新时间”字段,一眼就能看出延迟在哪个环节。
6. 把预警阈值调准:一个用历史回测确定告警线的具体技巧
预警阈值拍脑袋定是舆情系统最大的浪费,定高了漏报,定低了天天误报,运营很快就对告警麻木。我一般会用历史数据做回测来定阈值,具体做法是:取过去 30 天的聚合数据,按小时窗口算出负面率和热度分,然后统计这些指标的分布,把告警线设在历史 95 分位附近,再根据业务容忍度微调。
import pandas as pd import numpy as np def backtest_threshold(df, metric="负面率", quantile=0.95): values = df[metric].dropna() threshold = np.quantile(values, quantile) # 统计超过阈值的窗口占比,评估误报频率 exceed_ratio = (values > threshold).mean() return { "阈值": round(threshold, 4), "历史超限占比": round(exceed_ratio, 4), "样本数": len(values) } def evaluate_alert(df, threshold, metric="负面率", consecutive=2): df = df.copy() df["超限"] = df[metric] > threshold # 连续N个窗口超限才触发,降低单点误报 df["触发"] = df["超限"].rolling(consecutive).sum() == consecutive return df[df["触发"]]quantile=0.95是起点不是终点,如果业务对漏报更敏感就降到 0.90,对误报更敏感就提到 0.98。consecutive=2表示连续两个窗口超限才告警,这个参数能过滤掉大部分单点波动。回测时要把已知的重大舆情事件标出来,看阈值能不能覆盖这些事件,覆盖不到就说明阈值偏高。这套方法我在几个项目里用过,比拍脑袋定的阈值误报率能降一半以上。
阈值定完之后不是一劳永逸,业务节奏变了、渠道变了、用户表达方式变了,阈值都要重新回测。我自己的习惯是每月跑一次回测脚本,把结果和上月对比,偏差超过 20% 就重新调。这个习惯帮我躲过了好几次因为阈值失效导致的漏报。希望帮到你。
本文还有配套的精品资源,点击获取