
简介这是一套开源免费的舆情监测分析系统完整源码及配套数据库面向需要快速搭建本地舆情平台的技术人员、中小企业或高校研究者可用于网络舆论采集、情感分析与热点发现。包内含2000个文件以1757个JavaScript脚本、131个CSS样式、64个HTML页面为主另有XML、JSON配置及Shell部署脚本等压缩包共45.13MB便于本地化部署与二次开发。系统覆盖数据采集、处理、分析到可视化展示全流程内置丰富图表插件与后台管理界面数据库可支撑海量舆情数据存储查询。目前已有726人学习下载适合具备一定开发基础、希望低成本获得可定制舆情工具的用户。1. 开源免费的舆情系统源码数据库先搞清楚它解决什么问题做运维或负责信息监测的人大概率被问过一句话“网上最近有没有人讨论我们”搜索引擎手动搜一遍能顶一周顶不了三个月。开源免费的舆情系统源码加数据库就是把站点采集、内容清洗、全文检索、告警通知变成一套跑在自己服务器上的工具不用按席位订阅商业平台数据也留在自己手里。适合有开发背景、想快速验证舆情处理闭环的人也适合要搭内部信息监测能力的团队。前提是你愿意维护它站点改版、关键词变化、数据清洗这些事总得有人管。2. 系统架构与选型为什么是“采集器队列双存储”而不是一把梭真正在线上跑过的舆情系统早期版本大多是单机脚本requests 循环抓页面、正则抽正文、一次性写到 MySQL。能跑但三个问题绕不过去抓取和入库耦合一个站点超时就拖垮全部增量更新没有状态重复数据和漏抓同时存在检索只能靠 SQL LIKE关键词一多就卡。这也是后来几乎所有开源方案都转向“采集器消息队列双存储”的原因。下面按数据链路、选型、采集参数三个层面拆开讲。2.1 核心数据链路从页面到可检索数据中间隔了几层一条典型链路长这样定时调度器触发采集任务采集器按站点配置抓列表页和详情页把内容规整成统一的消息结构投递到 Redis 队列消费者从队列取消息先做去重和清洗然后分两条路走结构化字段写 MySQL正文和标题写 Elasticsearch最后 Web 控制台从 ES 做检索从 MySQL 做统计和告警判断。设计上每一层职责单一互相不拖累。比如 ES 写不进去消息在队列里堆积MySQL 里的数据依然是完整的不会出现“半个库里全是坏数据”的局面。这个分层结构不是我拍脑袋想的而是目前多数开源舆情系统的通用骨架先把这条链路画在纸上再去看任何一套源码都能按图索骥。我一般建议拿到源码先画链路图再动手部署比先跑代码再猜结构省时间。2.2 开源方案怎么选Python、Java、Go 三条路的取舍我接触过的开源舆情系统大体分三条技术路线先看对比再谈选择。路线典型技术栈二次开发成本适合场景Python 系Scrapy/requests Celery Redis ES Flask低脚本灵活中小团队、采集规则多变、快速验证Java 系Spring Boot 爬虫框架 ES MySQL中工程化强已有 Java 团队、要对接企业级权限体系Go 系Go cron ClickHouse中单机性能强大数据量、偏统计分析的舆情大屏对多数想先跑起来看效果的团队我建议选 Python 系。理由很直接采集器本质上是“定义规则处理异常”的活Python 生态里选择器、编码处理、模拟登录这些组件都现成Celery 自带定时和重试后面接文本处理和情感分析也顺手。Java 系适合已经有微服务基座的公司但为了一个舆情项目引入整套 Spring Cloud前期投入会明显变大。选型还有个隐形指标团队最熟哪门语言就选哪条路舆情系统是需要长期改的让最熟的栈维护最好改的代码。2.3 核心参数增量窗口、调度周期、站点规则怎么定采集器最容易写错的地方不是抓取本身而是“抓多少、多久抓一次、抓完存哪”。常见的做法是把站点规则外置不写死在代码里每一条规则包含入口 URL、列表页选择器、详情页链接提取方式、发布时间字段的解析方式。页面改版时改数据库记录就能生效不用重新发版。调度周期要匹配目标站点的更新频率。新闻站可以 10 到 15 分钟一轮论坛和公众号类一天两轮就够。增量窗口一般设为最近 6 到 12 小时首次部署时先做一次全量抓取之后每次都按发布时间窗口过滤避免把历史文章反复拉回库。反爬处理常见的有请求间隔随机化、Cookie 维护和失败重试频率控制宁慢勿快单站点并发调到 2 到 4 就能满足多数场景不用一次性把线程拉满。3. 数据库设计与初始化先看表和索引再谈跑起来一套舆情系统的数据库部分通常指 MySQL 里的业务表、Elasticsearch 里的索引映射以及两者之间的同步逻辑。先建好表再谈功能顺序不能反。我有一次跳过建表直接跑服务Web 端一直在报错排查半天才发现初始化脚本里的关联表没有执行成功白白浪费了一个晚上。3.1 核心表结构与字段设计站点表、文章表、事件表、告警表MySQL 侧至少要有四张核心表。站点配置表把采集规则结构化存放文章明细表负责存每次抓到的原始数据事件聚合表用于把同一主题的报道归并到一起告警规则表存放关键词组合和通知方式。站点配置表的核心字段是 name、home_url、crawl_rule 和 enabled。crawl_rule 用 JSON 文本存选择器比如列表页链接、标题 XPath、发布时间解析格式格式如下。CREATE TABLE site ( id int NOT NULL AUTO_INCREMENT, name varchar(64) NOT NULL COMMENT 站点名称, home_url varchar(255) NOT NULL COMMENT 首页地址, crawl_rule text COMMENT 解析规则JSON, enabled tinyint NOT NULL DEFAULT 1 COMMENT 0停用 1启用, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT站点配置表;文章明细表是整库数据量最大的表设计重点在去重和检索。url 必须建唯一索引content_md5 做内容指纹去重publish_time 建普通索引供按时间过滤。为了控制单表体积可以预留按月分区的字段位。CREATE TABLE article ( id bigint NOT NULL AUTO_INCREMENT, site_id int NOT NULL COMMENT 所属站点, title varchar(255) NOT NULL, content mediumtext, url varchar(512) NOT NULL, author varchar(64) DEFAULT NULL, publish_time datetime NOT NULL, crawl_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, sentiment_score tinyint NOT NULL DEFAULT 0 COMMENT -1负面 0中性 1正面, content_md5 char(32) NOT NULL, status tinyint NOT NULL DEFAULT 0 COMMENT 0待处理 1已入库, UNIQUE KEY uk_url (url(255)), KEY idx_pub_time (publish_time), KEY idx_site (site_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT文章明细表;url 字段做唯一索引时注意长度限制MySQL 5.7 默认索引长度上限是 767 字节utf8mb4 下 255 个字符会超所以要写成 url(255) 这种前缀索引。这是我在初始化时被卡得最久的一个点后面避坑章节再展开。事件表和告警规则表相对简单事件聚合表保存事件名称、首次出现时间、最新出现时间和关联文章数告警规则表保存关键词组合、情感分数阈值和通知方式。建表语句如下。CREATE TABLE event ( id bigint NOT NULL AUTO_INCREMENT, event_name varchar(255) NOT NULL, first_seen_at datetime NOT NULL, latest_seen_at datetime NOT NULL, article_count int NOT NULL DEFAULT 0, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT事件聚合表; CREATE TABLE alert_rule ( id int NOT NULL AUTO_INCREMENT, rule_name varchar(64) NOT NULL, keyword_group text COMMENT 关键词组合JSON, sentiment_threshold tinyint NOT NULL DEFAULT 0, notify_channel varchar(32) NOT NULL COMMENT email/webhook, enabled tinyint NOT NULL DEFAULT 1, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT告警规则表;两张表都留了 JSON 字段存扩展参数keyword_group 存的是数组支持“品牌词 AND 负面词”这种组合后面避坑章节会讲为什么要这样配。3.2 去重与增量更新url 唯一索引和内容指纹 md5 的组合用法入库前的去重分两层。第一层用 url 唯一索引数据库自带的约束会挡住完全重复的链接第二层用内容指纹因为有些站点会把同一篇文章放到不同的 url 下或者列表页和详情页的链接带不同跟踪参数。常见的做法是对去掉空白字符后的正文做 MD5入库前按 content_md5 查一次命中就跳过。增量更新则靠 status 字段配合时间窗口完成。status 标记这条记录是否已经被事件聚合消费过status0 的数据由后台任务批量拉去 ES 和事件表采集器在抓列表页时对每条链接对比 publish_time 与当前时间超过增量窗口的文章不做详情抓取。窗口值要按站点分开配新闻站 6 小时论坛站 12 小时既保证时效又避免重复拉取。def is_duplicate(article, db): # 第一层url 唯一索引冲突检查 if db.exists(SELECT 1 FROM article WHERE url%s, article.url): return True # 第二层内容指纹查重正文去空白后算 md5 md5 hashlib.md5(re.sub(r\s, , article.content).encode(utf-8)).hexdigest() if db.exists(SELECT 1 FROM article WHERE content_md5%s, md5): return True return False先查 url 再算 md5能省掉大部分重复计算。对已经产生的脏数据写一次性脚本按 content_md5 分组保留每组最早的一条其余标记删除。这个脚本我建议保留在 scripts 目录里后面还会用到。3.3 全文检索Elasticsearch 索引映射与中文分词配置MySQL 只负责事务性存储真正的搜索体验来自 ES。索引映射需要重点处理中文分词用 IK 分词器的 ik_max_word 模式做索引搜索时用 ik_smart匹配率和召回率都能照顾到。以下是我常用的映射配置。{ settings: { number_of_shards: 3, number_of_replicas: 1, analysis: { analyzer: { ik_auto: { type: custom, tokenizer: ik_max_word } } } }, mappings: { properties: { title: { type: text, analyzer: ik_auto }, content: { type: text, analyzer: ik_auto }, site_id: { type: integer }, publish_time: { type: date, format: yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis }, sentiment_score: { type: byte } } } }索引里只放检索需要的最小字段集title、content、site_id、publish_time、sentiment_score 基本够用。不要想着把 MySQL 的整行都同步到 ES后期字段变更会很难受。同步逻辑一般放在消费者里入库成功后往 ES 发一条写入请求失败则放入重试队列等 ES 恢复后再补。另外安装 IK 插件必须匹配 ES 版本版本不对插件会直接加载失败ES 日志里会报分词器找不到这个坑后面会再提到。4. 把源码在本机跑通部署步骤与最小冒烟测试拿到一套源码加数据库脚本第一件事不是读代码而是让它在本地跑起来。跑通之后再对照代码看流程效率和理解深度都会高很多。下面的部署步骤以 Python 系方案为例这套流程对大多数同类项目都适用。4.1 环境准备与目录结构先确认版本再谈安装环境要求一般是 Linux 服务器或开发机Python 3.10 以上、MySQL 8.0、Redis 6 以上、Elasticsearch 7.17。ES 版本要特别注意8.x 的默认安全配置和 7.x 差异很大部分开源方案没有适配我建议先按 7.17 走能少趟很多坑。目录结构通常长这样。├── app/ │ ├── __init__.py │ ├── config.py │ ├── crawler/ # 采集器模块 │ ├── tasks.py # Celery 定时任务 │ ├── models.py │ └── web/ # Web 控制台 ├── scripts/ │ └── init_db.sql # MySQL 初始化脚本 ├── es_mapping.json # ES 索引映射 ├── requirements.txt └── README.md先安装依赖再初始化配置别反过来。requirements.txt 里一般会写全 Flask、Celery、Scrapy、elasticsearch、pymysql 等包直接 pip install -r 就能装齐。装完之后用 python -c import flask, celery, scrapy 验证一遍缺哪个补哪个。4.2 改好配置文件再启动数据库连接、Redis 和采集周期三个必改项配置文件集中在 app/config.py 里需要改的通常是数据库连接、Redis 连接、ES 地址、采集周期和增量窗口。下面是我常用的配置模板。# app/config.py MYSQL_HOST 127.0.0.1 MYSQL_PORT 3306 MYSQL_USER yq MYSQL_PASSWORD yq_pass_2024 MYSQL_DB yuqing REDIS_HOST 127.0.0.1 REDIS_PORT 6379 REDIS_DB 0 ES_HOST http://127.0.0.1:9200 ES_INDEX article # 采集周期单位分钟 CRAWL_INTERVAL 10 # 增量窗口单位小时超过该时间的文章不再抓取详情 INCREMENT_WINDOW_HOURS 6几个容易忽略的参数Redis 连接要确认有没有密码有密码要加 REDIS_PASSWORD 配置不然启动时连不上会一直报错CRAWL_INTERVAL 别小于站点实际更新频率太小只会给目标站点增加压力还可能触发反爬限制增量窗口第一次可以放宽到 12 小时跑两天后视重复率调小。日志级别建议从 info 起步Celery 任务失败时能看到完整堆栈排查问题全靠它。4.3 初始化 MySQL 库表和 ES 索引两条命令搞定初始化 MySQL 时先建库再导入 SQL 脚本。脚本里包含建表语句和一批默认站点配置导入后可以直接用于测试。命令如下。# 登录 MySQL 建库 mysql -uyq -pyq_pass_2024 -e CREATE DATABASE IF NOT EXISTS yuqing DEFAULT CHARACTER SET utf8mb4; # 导入初始化脚本 mysql -uyq -pyq_pass_2024 yuqing scripts/init_db.sql # 创建 ES 索引 curl -X PUT http://127.0.0.1:9200/article \ -H Content-Type: application/json \ -d es_mapping.json两条命令执行完检查一下MySQL 里应该有 site、article、event、alert_rule 四张表ES 返回 acknowledged 为 true。任何一个环节报错先看数据库连接权限和 ES 内存这两个是新手翻车重灾区。4.4 启动采集任务与 Web 控制台四个进程按顺序来整套系统最少要起四个进程Celery worker 负责消费抓取任务、Celery beat 负责定时触发、Flask 负责 Web 控制台、ES 和 MySQL 作为外部依赖。启动顺序是先外部依赖再 worker再 beat最后 Web。# 启动 Celery worker4 个并发消费队列 celery -A app.tasks.celery_app worker -l info -c 4 # 启动定时调度 celery -A app.tasks.celery_app beat -l info # 启动 Web 控制台 python app/web/run.py --port 8080启动后做一次最小冒烟测试手动触发单站点抓取验证整条链路。如果采集目标需要登录态先在 site 表配置 Cookie或用项目自带的登录模块先跑一次否则抓回来的全是登录页。# 只抓一个站点验证采集到入库 python -m app.crawler.smoke --site-id 1 # 去 ES 里看是否有数据 curl -X POST http://127.0.0.1:9200/article/_search?pretty \ -H Content-Type: application/json \ -d {query:{match_all:{}},size:10}看到 ES 返回文档列表就说明链路通了。这时候 Web 控制台里应该能按关键词搜到数据告警规则也能触发测试通知。第一版就这么跑起来别急着加功能先把链路养一个晚上第二天看数据量是不是稳定增长。5. 部署后的五个高频坑从内存翻车到告警误报跑通只是开始真正折磨人的是稳定运行阶段。我梳理了五个在部署和运维开源舆情系统时会反复遇到的问题每一条都是真实踩过的按现象、原因、解决来写。5.1 ES 内存翻车集群变红数据写入全部失败现象部署没两天ES 日志开始报 OOM集群健康状态从 green 变成 red消费者线程报连接拒绝Redis 队列里的消息越堆越多。原因ES 默认的 JVM 堆内存只有 1GB服务器内存稍大一点也会被默认配置坑到另外机器上同时还跑了 MySQL 和 Redis内存竞争会直接触发 OOM。解决改 ES 的 jvm.options把堆内存设为机器物理内存的一半上限不要超过 32GB。改完重启用下面这条命令看集群状态。curl http://127.0.0.1:9200/_cluster/health返回的 status 是 green 才算正常。顺手把不需要的 xpack 安全特性关掉还能省下一块内存。5.2 数据疯狂翻倍去重没生效文章重复入库现象运行一周后查 article 表总行数比实际文章数多了一倍多。按标题看还能接受按内容看全是重复。原因部分站点会在 url 里加跟踪参数同一篇文章生成十几个不同链接单纯靠 url 唯一索引挡不住另外增量窗口设得太大列表页反复翻出历史文章。解决入库前先对正文去空白后算 MD5按 content_md5 查重命中就跳过同时缩小增量窗口到站点真实更新周期。对已有脏数据用这条 SQL 找出重复最严重的组。SELECT content_md5, COUNT(*) FROM article GROUP BY content_md5 HAVING COUNT(*) 1 ORDER BY COUNT(*) DESC LIMIT 20;按组保留最早一条其余标记删除。这个坑几乎每个接新站点的项目都会遇到先查再抓是底线。5.3 页面一改版解析规则失效采集回来全是空标题现象某个站点连续三天没有新增数据手动跑采集脚本发现抓回来的记录全是空标题和空正文。原因目标站点改版列表页 DOM 结构变化原先写死的 XPath 和 CSS 选择器全部失效。选择器在代码里写死时这个问题要改代码才能恢复。解决把解析规则外置到 site 表的 crawl_rule 字段页面改版时只更新数据库记录。再写一个规则测试脚本入参是站点 id出参是抓取结果预览每周跑一遍谁改版了立刻能发现。选择器尽量用语义化 class少用层级很深的路径能延长规则的生命周期。5.4 Redis 队列积压不报警三天后才发现现象某次 ES 挂了半天恢复后 Redis 队列里积压了几十万条待处理消息消费者处理速度跟不上告警也不响三天后才从监控图上看到异常。原因队列长度没有监控Celery 消费者并发数默认 4ES 恢复后积压消息一时半会消费不完而且失败任务一直在重试挤占正常任务。解决给 Redis 队列加长度监控超过阈值就发通知Celery 并发数调到 8 到 16失败任务做最多三次重试三次后进死信队列单独处理不再反复占资源。排查时先看队列长度命令很短。redis-cli llen celery这条命令我建议贴在运维手册第一行是长年使用频率最高的排查命令。5.5 告警误报太多关键词命中就发消息现象告警规则里配了“下跌”“亏损”“关闭”等词结果正常财报新闻也触发告警运营一天收几十封通知最后没人看。原因关键词太宽泛没有和品牌词、人物名做组合也没有用情感分数过滤。负向词命中不一定是负面事件。解决告警规则改成“品牌词 AND 负面词”的组合形式再叠加情感分数阈值负面情感低于 0.5 的文章不告警。再加上告警合并窗口同一主题半小时内只发一条通知误报率能降一大截。6. 进阶用法让舆情系统从能跑到好用基础链路稳定后真正拉开差距的是数据处理和告警体验。我一般按三个方向迭代情感分析、热点聚合、运行验证每一个都不需要大改架构。情感分析是舆情系统的核心体验。如果源码里没带直接在消费者任务里加一个轻量判断对正文返回情感分。from snownlp import SnowNLP def auto_sentiment(text): score SnowNLP(text).sentiments if score 0.4: return -1 if score 0.7: return 1 return 0阈值按业务调敏感行业可以把负向阈值放宽到 0.5。情感结果回填 sentiment_score 字段前端筛出负面列表告警也基于它做二次过滤能解决上一章提到的误报问题。热点聚合可以用 jieba 分词加关键词频率统计按小时窗口聚出高频词组配合 event 表做归并日报和周报就能自动生成了。运行验证上我养成了一个习惯每天上班先看三个数字昨天新增文章数、Redis 队列长度、失败任务数。这三个数字像系统的体温计任何一项异常都意味着链路里有环节出问题。之前因为没看队列积压某套系统静默失败三天没人发现从那以后我把这三个数字做进监控面板每天早上到公司先扫一眼再决定当天要不要处理数据问题。舆情系统的核心价值不是代码本身而是持续稳定运行的采集和输出。站点会改版接口会变动关键词会过时这套东西得有人维护才养得活。理解了架构跑通了链路再按自己的业务场景调整情感阈值和告警组合这套开源免费的方案才真正变成自己的工具。希望帮到你。本文还有配套的精品资源点击获取