自建信号评分雷达:多平台关键词监控与告警系统实战
1. 为什么我要做 PLFM_RADAR一次“热点扑空”逼出来的自用工具做内容运营和竞品监测的人大概都经历过这种场景某个话题在晚上九点突然起量你第二天早上才刷到某条关键竞品消息已经传遍行业群你还在手动翻搜索页。我踩过几次这样的坑之后决定自己搭一套“平台雷达”式的信号监控系统就是后来被我命名为 PLFM_RADAR 的这套东西。它本质上是一个多平台关键词信号采集、评分与告警工具把分散在新闻、社交、问答、垂直社区里的信息像雷达扫描空域一样持续收集、清洗、分析达到预设阈值就把告警推送到手机让我不再依赖运气发现热点。“雷达”这个比喻不是临时起意。雷达系统做的事情是发射脉冲、接收回波、在杂波里识别目标、持续跟踪目标航迹PLFM_RADAR 做的事情完全同构采集任务就是发射脉冲各平台返回的数据就是回波但平台数据不会直接告诉我“这条会火”所以我加了加工层把来源权重、传播速度、情感倾向、内容匹配度综合成目标置信度评分超过阈值就进入重点跟踪名单。这个工具不复杂但它解决了我最痛的三个问题盯盘频率太低、判断标准太主观、事后复盘无据可查。1.1 手动盯盘的两个致命问题先说原来的工作流。我维护了二十多个核心关键词每天早中晚三次去几大平台搜索页看结果。听起来还算勤快实际上问题很严重。第一是时效性欠账。一个话题从起量到刷屏往往只有两三个小时我一天三次巡检大概率错过黄金窗口。有一次竞品发布重要调整我是在次日行业公众号转载后才看到那时候首发阵地早就被别人占了。第二是判断标准模糊。靠肉眼判断“这条有没有扩散潜力”基本取决于当时的精神状态和个人偏好完全没有统一的客观尺度。同一个关键词我和同事看搜索结果排序会得出完全不同的优先级。我还试过用 Excel 汇总各家平台的搜索量坚持两周就放弃了。人工介入的环节越多漏报概率越大而且无法回答“为什么当时会漏”。真实需求其实很朴素把巡检频率从一天三次提升到分钟级把“是否值得关注”的逻辑固化到脚本里不依赖个人感觉把结果主动推到眼前而不是被动等我去翻。PLFM_RADAR 就是奔着这三个需求去的。1.2 雷达的隐喻全向扫描加重点跟踪真实雷达系统里有几个关键概念扇区扫描、杂波滤除、目标截获、航迹跟踪。PLFM_RADAR 全部对应得上。扇区扫描对应多数据源轮询杂波滤除对应标题去重与情感/词法过滤目标截获对应评分超阈值的判定航迹跟踪对应“事件簇”的持续监测。用雷达的框架想问题最大的好处是它逼着你回答一个核心问题哪些信号是真的值得跟踪的目标哪些只是噪声。这个问题恰恰是很多现成工具答不好的。市面上的关键词提醒、RSS 监控工具大多只能做到“出现关键词就通知你”等于雷达把几百个目标全部标成敌机最后你依然不知道重点盯谁。PLFM_RADAR 把重心放在信号质量的判断上采集反而是相对容易的部分。工具的名字带 RADAR就是提醒我自己不要做一台只会响的闹钟要做一台能分清敌我的雷达。2. 整体架构设计信号源、处理链路与推送链路PLFM_RADAR 整体分三层信号源层、处理层、输出层。信号源层负责从各平台拿原料处理层负责把原料转成结构化的、可评分的信号输出层负责让信号以合适的方式触达我。层与层之间通过 SQLite 数据库和 Redis 缓存衔接单机部署维护成本很低。下面把每一层拆开讲。2.1 信号源设计关键词、站点类型与更新频控信号源是雷达的眼睛设计不好后面全白搭。我把信号源分成三类。第一类是搜索型源调用平台公开搜索接口或解析搜索页输入关键词返回一批最新结果特点是指向性强但容易被频控。第二类是列表型源比如榜单页、首页推荐位不依赖关键词也能拿到内容适合做“广域扫描”缺点是噪声相对大。第三类是订阅型源比如 RSS、Atom解析稳定、负担小但平台覆盖范围有限。PLFM_RADAR 的默认配置里这三类源的比重约是 4:3:3。关键词数量需要克制。我一开始贪多配了上百个词结果每轮轮询要十几分钟频控不敢放开整体延迟很大。后来压缩到二十个核心词加十个宽泛词宽泛词专门负责“扫描模式”反而抓到了不少长尾信号。更新频控我建议按站点类型设置热门新闻源五分钟一次社交搜索源十分钟一次榜单类半小时一次。太激进容易触发反爬太保守又会错过爆发窗口这个节奏是我实测下来比较稳的平衡点。2.2 热度计算曝光、传播速度与情感倾向的加权模型评分引擎是 PLFM_RADAR 的脑子。我实现了一个可配置的加权公式默认权重如下评分 来源权重 × 0.25 传播速度分 × 0.35 情感倾向分 × 0.2 内容匹配度 × 0.2传播速度分是核心我记录的是“某个关键词在时间窗内新增了多少条相关讨论”。如果过去一小时的新增数量超过前四小时均值的 3 倍速度分就能打到 80 分以上。这个设计来自真实世界的传播曲线绝大多数热点爆发前都有一个陡峭上升段捕获这个上升段比捕获最终峰值更有价值。等价类比是钓鱼的人不会等鱼上了岸才收竿而是在鱼咬钩的瞬间提竿传播速度分抓的就是“咬钩瞬间”。情感倾向分依赖情感词典先用通用词典跑基础分类再用业务自定义词典修正区分中性、正面、负面。真实环境里负面信息的扩散速度普遍更快所以我给负面倾向加了少量加权。至于匹配度我用的是关键词命中密度标题命中记高分正文命中记中分评论命中记低分这样能避免一篇文章只是在文末顺带提了一句关键词就被误判成高热度。2.3 告警输出推送渠道、Web面板与历史回溯告警输出做了三个通道即时消息推送、邮件摘要、Web 面板。即时消息推送面向超高置信度信号评分超过 80 且连续两次采样都在上升才触发邮件摘要每天早晚各一封汇总当天的高分信号和事件簇变化Web 面板则给所有评分超过 60 但未触发告警的内容留一个复核入口。三级通道的意义在于减少打扰如果每个轻微波动都弹消息接收者很快就会麻掉真正紧要的告警反而被淹没。历史回溯也在这层实现。每次评分都会写入 SQLite信号明细表和告警记录表通过 signal_id 关联。表结构并不复杂信号明细表存标题、链接、来源、关键词、各子项分数、总分、命中时间告警记录表存告警等级、处理状态、备注。有了这张表我可以在半小时内翻出“上周四这条告警为什么触发”原始数据和评分子分都在。这个设计让团队复盘不再靠印象说话而是直接查数据。3. 从零搭建一份可运行的 PLFM_RADAR这部分是干货实操。我用的技术栈非常简单Python 3.10、Requests、BeautifulSoup、SQLite、Redis、APScheduler推送走通用 Webhook。没有上消息队列没有上容器编排也没有写微服务。这套组合单机部署很稳维护成本几乎为零非常适合个人和小团队复制。3.1 技术选型为什么是 Python 加 SQLite 而不是买现成监控有人问为什么不直接用商业舆情监控 SaaS。两个原因费用和自定义度。按关键词数量计费的产品一个月下来不便宜而且我的评分逻辑要反复调整在别人系统里连评分公式都碰不到更别说实验新的权重。自己写代码每一步都能控制改一版权重只需要改一个函数跑一批历史数据就能验证效果这种自由度是商业产品给不了的。Python 的优势是生态成熟Requests 加 BeautifulSoup 就能覆盖绝大多数页面解析场景写评分逻辑也很顺手。Redis 和 SQLite 的分工我纠结了一下最后定为Redis 存最近一小时的去重窗口和实时计数器SQLite 存全量明细。Redis 的过期特性和原子写入特别适合滑动窗口去重SQLite 则负责稳妥落盘数据量级在几十万条以内完全没有压力。这份取舍的核心原则是让每一类数据待在最擅长处理它的地方不要为了技术炫技而引入不必要的复杂度。3.2 采集器实现请求、解析与去重先上采集器示例代码。为了让代码直接可跑这里用公开 RSS 源做演示换成其他数据源的接口只需要改解析函数。import hashlib import requests import xml.etree.ElementTree as ET from redis import Redis REDIS Redis(host127.0.0.1, port6379, db0) def fetch_rss(url): resp requests.get(url, timeout10, headers{ User-Agent: PLFM-RADAR/0.1 (personal monitor) }) resp.raise_for_status() root ET.fromstring(resp.content) items [] for entry in root.iter(item): title entry.findtext(title, default) link entry.findtext(link, default) pub_time entry.findtext(pubDate, default) if title and link: items.append({title: title, link: link, pub_time: pub_time}) return items def unique_dedup(title): sig hashlib.md5(title.encode(utf-8)).hexdigest() if REDIS.set(sig, 1, nxTrue, ex3600): return True return False这段代码干了两件事拉取 RSS 条目再用标题 MD5 写入 Redis 做一小时去重。set方法的nxTrue保证了只有键不存在时才写入配合ex3600完成滑动窗口去重。为什么用标题而不是链接做去重签名因为同一事件会被不同站点转载URL 各不相同标题却高度相似用标题能更准确地判断“同源内容”。当然风险也明显两篇完全同题的独立文章会被误判为重复所以我真正跑生产的版本把“标题加域名前缀”一起做签名再留一列is_duplicate标记供人工复核。3.3 评分器实现衰减函数与阈值判定采集只是拿到原料评分器才决定信号的命运。下面的函数接收一条清洗后的信号输出 0 到 100 的分数并配合一个带时间衰减的告警判定。import math def score_item(item, recent_counts): source_weight item.get(source_weight, 1.0) speed_score 0.0 if recent_counts: window_avg sum(recent_counts) / len(recent_counts) last_peak recent_counts[-1] if window_avg 0: ratio last_peak / window_avg speed_score min(100, ratio * 20) sentiment_score item.get(sentiment_score, 0.5) * 100 match_score item.get(match_score, 0.8) * 100 total (source_weight * 25 speed_score * 0.35 sentiment_score * 0.2 match_score * 0.2) return round(min(total, 100), 2) def should_alert(score, threshold75, minutes_elapsed0): if score threshold: return True decayed score * math.exp(-0.02 * minutes_elapsed) return decayed thresholdshould_alert里这个指数衰减函数是个小技巧。真实场景里一条信号在刚出现时没有立刻冲到阈值但持续在升温直接抛弃会漏报。我给分数加了一个“记忆衰减分”随时间自然下降但下降速度比原始分慢相当于雷达里的目标记忆跟踪。假设一条信号初次评分是 70距离阈值 75 只差 5 分如果它持续升温30 分钟内衰减到约 55% 的折算分也能触发告警这比死板的硬阈值灵活得多。验证评分器是否合理我有一套自己的办法拿历史上明确爆过和明确没爆的 100 条数据跑一遍看高分组是否覆盖了所有爆过的话题低分组是否避开了大部分没爆的话题。评分器本质上是一个分类器召回率和准确率都要看不能只盯着某一个数字。3.4 通知器实现Webhook 推送与失败重试告警推送我走通用 Webhook好处是钉钉、企业微信、飞书都能接只需要改一个 URL。核心推送函数如下import requests def send_alert(payload): webhook_url https://your.webhook.endpoint data { msgtype: text, text: { content: ( f[PLFM_RADAR] 信号告警\n f标题: {payload[title]}\n f评分: {payload[score]}\n f来源: {payload[source]}\n f链接: {payload[link]} ) } } resp requests.post(webhook_url, jsondata, timeout5) return resp.status_code模板看起来简单细节都在坑里。第一必须带原文链接否则收到告警还要满世界找原始内容效率会大打折扣。第二消息里带评分人工复核时才知道这条告警的置信度依据是什么。第三推送要处理失败重试。Webhook 偶发超时很正常我的做法是发送失败的告警先写进本地failed_alerts.db调度器每五分钟重读一次补发成功后删除对应记录。别小看这步没有它丢告警只是时间问题而丢告警在雷达体系里是最不能接受的失误。3.5 启动与验证五步跑通最小化闭环模块就位后我按下面的步骤验证整个系统。先用内联函数人工构造一条假数据直接调用评分器和通知器确认 Webhook 能收到消息。第二步挂起调度器把阈值临时降到 30喂真实数据流确认采集、评分、推送整条链路是通的。第三步恢复正式阈值观察一小时统计触发数量和告警质量。第四步手工把几条已知的热点旧数据跑一遍回放确认能复现出告警。第五步确认无误后把调度器注册为系统服务设置开机自启。from apscheduler.schedulers.blocking import BlockingScheduler scheduler BlockingScheduler(timezoneAsia/Shanghai) scheduler.add_job(poll_all_sources, interval, minutes5, max_instances1) scheduler.add_job(send_digest, cron, hour8,20, minute0) scheduler.start()这里要重点解释max_instances1。这个参数保证上一次任务没跑完时调度器不会并发启动下一次对采集类任务极其重要。并发轮询表面上是提速实质上是给自己找麻烦容易被数据源封禁容易绕乱 Redis 的去重窗口还容易把同一批数据重复入库。低频、可靠、不惹事才是采集任务该有的样子。4. 调参与踩坑别让雷达变成“噪音发生器”PLFM_RADAR 上线头两周最大的问题不是没信号而是信号太多。我以为阈值设到 75 已经很保守结果每天收到几十条告警点开一看将近一半是无效信息。这一段专门记录我踩过的坑和最后怎么把误报压下来的。4.1 常见故障及排查速查表下面是我整理的一张速查表按踩坑频率排序故障现象可能原因解决方案某个固定源一直没有新数据请求被频控或页面结构变化先手动请求看状态码再检查解析器是否过期同一批内容反复告警去重键设计不当改用“标题域名”做签名或在评分里加重复惩罚Webhook 偶发丢消息网络超时或接口限流增加失败重试队列先落盘再外发分数虚高但不值得关注情感词典误判为业务场景补充否定词与程度词规则调度器偶尔漏跑上一次任务执行超时给每轮采集加超时控制必要时拆分子任务排查顺序也有讲究。我建议按“数据有没有 → 去重是否生效 → 评分是否合理 → 推送是否到达”一层层来不要一上来就怀疑算法公式。我吃过一次亏花了一个下午查评分模型最后发现只是某个源的反爬策略改了页面结构解析器解析不到标题了。很多问题看着像算法问题根子上其实是数据采集的问题。4.2 关键词词库的三种校准方法词库校准是评分质量的关键我总结出三种方法。第一种是漏报回看每漏掉一个热点立刻反查当时的原始数据看它命中了哪些词、没命中哪些词然后补同义词和组合词。第二种是假阳性周复盘每周把当周所有告警复核一遍给每条打“有效”或“无效”标记。无效告警集中的词说明太宽泛或者权重偏高需要收紧。第三种是建立否定词库有些词在特定行业里是常规词汇而不是热点信号比如行业内术语命中它们应当大幅度降权而不是加分。这三种方法配合操作效果很直观。我给一个具体数字起步阶段告警准确率只有 45% 左右坚持一个月校准后稳定在 75%。这个提升没有靠任何高级算法纯粹是持续用真实数据回喂词库。校准过程没有捷径它就是把系统每次误判和漏判当成免费的学习样本一遍一遍迭代。4.3 误报绕不过去的两个来源与对策误报的两个主要来源一个是“间歇性刷屏”另一个是“同一事件被多次报道”。间歇性刷屏的典型特征是某个话题每隔十几分钟就有一小波讨论只看当前时间窗速度分会被拉得很高但放到一整天看其实是平稳的。我的对策是把速度分的时间窗从一小时拉长到两小时同时引入“日环比”辅助指标如果当天总讨论量没有显著高于前一天的同一时段就把速度分打折。这个改动一下子把“伪潮汐”带来的误报降了一半。同一事件的多次报道更难处理。一个突发事件被几十家媒体转载雷达眼里就是几十条高分信号全推出来就是灾难。我的做法是在评分器里加“事件簇”合并两条内容标题相似度超过 0.7 就归为同一事件簇只保留簇内最高分那条发告警其余作为跟踪记录落库。文本相似度用简单的字符重叠率实现没有上复杂模型效果已经足够重复告警至少缩减了六成。5. 实测效果复盘与后续扩展思路PLFM_RADAR 目前跑在一台按量付费的低配服务器上CPU 占用常年不到 5%内存占用三百兆上下雨我无瓜地默默盯着二十个数据源。它不会抢资源也不会天天刷存在感但真正需要它的时候告警总会在第一时间出现。5.1 一个月试运行的关键数据与复盘运行四周后我做了完整复盘。日均有效告警从上线第一周的四十条降到第八周的八条左右漏报次数从每周三四次降到每两周不超过一次。有效告警的定义是“人工复核后确实值得跟进的内容”漏报的定义是“事后确认成为热点但系统没有第一时间告知的事件”。这条曲线让我确信阈值和词库的持续校准比任何一次性调优都重要。复盘还发现一个有意思的现象高价值告警集中在傍晚六点到晚上十一点这个时段。原因很简单这个时段用户活跃度最高传播速度分更容易被触发。于是我把晚上的评分阈值略微下调了五个点白天的阈值保持不变让系统在活跃时段更敏感在安静时段更克制。这种“一日内动态阈值”只改了几行代码但感知到的告警质量提升非常明显。5.2 后续想增加的能力与一步步推进计划后续的改进方向我列了三个按优先级排序。第一个方向是情绪识别的升级。现在的词典法在反讽、玩梗、谐音表达面前还是容易翻车等手头标注数据积累到一定量我打算微调一个轻量级文本分类模型替换掉现在的词典打分。第二个方向是拓展数据源形态。目前以文本为主后续想接入短视频评论区但先要解决接口合规和存储量的问题不能为了加数据源而引入不可控风险。第三个方向是做一个更聪明的趋势预测模块不满足于“现在很热”还希望给出“正在升温”“进入平台期”“已经开始降温”三种状态让运营同事拿到告警后能直接判断该花多少精力跟进。最后分享一个我坚持用到现在的小习惯每周固定留二十分钟做一次雷达自检把所有告警日志快速过一遍同时拿几条已知热点的历史数据做回放确认系统依然能复现告警。这个小动作成本极低但能及时发现解析器失效、词库漂移这类隐蔽问题。工具的价值在于它能长期稳定地替你盯盘而长期的稳定来自你愿意持续做小步调优。