ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

雷达式平台内容监控:PLFM_RADAR工具的设计与实现

雷达式平台内容监控:PLFM_RADAR工具的设计与实现 做平台侧的数据监控最烦的不是数据本身而是变化发生的时候你不知道。我经常需要同时盯着好几个平台的内容动态——看某个专题有没有上线、某个入口有没有调整、某个接口返回的字段有没有变化。手动刷新页面不仅效率低而且容易漏掉关键的时间节点。PLFM_RADAR 就是在这种背景下从一个小脚本长出来的工具它用雷达扫描的方式定期对目标平台的一批公开可访问地址进行探测记录内容快照比对差异把值得关注的变化推送到即时通讯工具里。这套东西不大但是很能解决实际问题尤其适合一个人运维、需要同时盯几个平台状态的小团队参考。下文讲一下这个工具从设计到部署的完整过程以及我踩过的几个坑。1. 从每天手动刷网页到让雷达自己去扫1.1 原始痛点多平台监控的重复劳动先交代一下背景。我当时负责几个运营专题的日常维护每天固定要看好几个页面专题页是否按时上线、活动入口的位置是不是被调整了、某个接口返回的状态字段有没有从0变成1、频道头图是不是换了。这些事听起来不大但单独拎出任何一件错过半小时就可能引起运营事故。比如一个限时活动的状态位提前翻转了如果没人及时发现用户端就会看到错误的页面又比如某个页面上的公告文案被临时撤掉如果没有对比历史版本根本不知道文案是什么时候变的。早期我采用的是最原始的办法浏览器开五六个标签页每隔半小时手动刷新一圈靠肉眼对比内容变化。这种做法有几个明显的毛病。第一是效率太低一次完整巡检要花差不多十分钟一天下来两三个小时就搭进去了。第二是覆盖不全手动刷新只能看到当前长什么样很难回溯上一版是什么样两版之间出现过什么更别提了。第三是极其依赖人的状态半夜上线、节假日调整这些时间点总不能一直守着屏幕。我也试过市面上一些现成的监控服务它们确实能解决内容变没变的问题但不够贴合我的需求。有的工具只支持完整页面快照无法精确到具体字段有的工具告警逻辑太简单页面里随便一个时间戳变化就发一条通知一天能收几百条还有的工具部署起来特别重为了监控三五个页面要起一个数据库再配一套界面明显过了。1.2 雷达模型的启发探测、回波、判读之所以给这个工具起名 RADAR是因为它的工作模式实在太像雷达了。雷达的流程是主动发射电磁波接收目标回波对回波做判读最终识别出目标的方位、速度、特征。PLFM_RADAR 也遵循同样的链路主动向目标平台发送探测请求相当于发射波束接收响应内容相当于回波再通过字段提取和差异比对来判读到底什么东西变了。这个类比不是硬套它真的帮我做了两个关键设计决策。第一探测频率要克制。雷达不会用最大功率无脑扫描因为那样既费电又造成信号拥堵同理频繁请求目标平台既不礼貌也容易触发对方的访问限制。所以我不追求每秒都在看而是根据变化的重要程度设定不同的扫描间隔——核心页面 5 分钟扫一次次要页面 15 到 30 分钟扫一次。第二回波必须经过判读才有意义。雷达不会把每一只飞过的鸟都当成敌机上报PLFM_RADAR 也不应该把每一个页面像素级别的变化都当成重大事件推送。这就是关心则察、不关心则略的道理落到实现上就是后面会讲到的关注度规则模块。1.3 项目目标与技术选型原则动手改造之前我给自己定了三条硬性设计目标低成本能够稳定运行在一台 1 核 1G 的云主机上不引入需要单独维护的中间件。可配置新增一个监控目标或者修改一套告警规则不用改 Python 代码只动配置文件。告警克制宁可少推一次也不滥推一条。告警推送一旦过多团队就会形成狼来了心态真正重要的事件反而被忽略。技术选型方面Python 3 是自然的选择因为最初的脚本就是用它写的requests、lxml、json 这些库用起来很顺手。后来在改造中引入了两个新依赖APScheduler 负责定时调度httpx 负责异步请求。有人可能问为什么不用 requests 做并发requests 的 Session 结合线程池也能并发但代码写起来比较绕httpx 原生支持 AsyncClient用 async/await 写并发逻辑非常直白。实测下来40 个探测目标requests 串行跑一轮要 8 到 10 秒换成 httpx 并发控制到 10 个请求时一轮可以压到 2 秒上下。有人觉得监控场景不在乎这 8 秒延迟但其实扫描时间直接影响调度周期的准确性——扫描太慢会挤压后续的比对和告警步骤长时间运行后整个调度链路都会被拖慢。数据存储选了 SQLite简单可靠。很多团队一提到存储就上 MySQL 或者 PostgreSQL但对这种量级的监控数据没有必要。一条探测记录加上指纹摘要平均几百字节一天扫描几千次也就几个 MB 的数据量SQLite 完全扛得住而且单文件备份、迁移都特别省事。2. 整体架构探测源、信号处理器、告警出口2.1 三层结构与数据流整个 PLFM_RADAR 的架构分三层采集层、分析层、通知层。采集层负责按配置对目标地址发起探测请求拿回原始响应分析层负责对响应做解析、提取关键字段、计算指纹、比对差异并套用关注度规则通知层负责把值得关注的差异通过指定渠道推送出去。三层的衔接由一个调度内核控制调度内核读取所有探测源的配置按各自的间隔触发一轮完整的采集 → 分析 → 通知流程。实际运行时采集层会把响应交给分析层的同时也会把这次探测的状态成功、失败、耗时记录到 probe_state 表。分析层产生 diff 记录后写入数据库然后交给通知层通知层的告警路由器根据 diff 的类型、级别和渠道配置决定是推送即时消息、发邮件还是只记录不打扰。这样分层的好处是任何一层的逻辑替换都不影响另外两层——比如你想把通知渠道从企业微信换成钉钉只需要新增一个渠道适配器采集和分析完全不用动。2.2 探测源管理用 YAML 配置一切所有探测源probe都在config/probes.yaml中定义。为什么用 YAML 而不是 JSON因为 YAML 支持注释。当探测源的配置超过二十个之后注释就是最好的文档你可以在里面写上这个页面为什么每 5 分钟扫一次那个字段为什么忽略变化过两个月再回来看依然能快速理解当初的设计意图。下面是一个简洁但真实的配置示例probes: - name: campaign_landing url: https://example.com/campaign/2025-summer method: GET headers: User-Agent: Mozilla/5.0 (PLFM_RADAR/1.0) interval: 300 parser: html_title_and_keywords rules: - name: main_title_changed field: title action: notify level: high这里的interval单位是秒代表探测间隔parser指定使用哪种解析器提取关键字段rules定义关注度规则。调度内核每轮扫描时都会先检查每个 probe 的last_run时间和interval的关系决定本轮是否需要执行。这样即使上一轮扫描超时也不会挤掉这一轮该跑的探测任务配合 APScheduler 的 cron 表达式可以做到调度窗口稳定。2.3 数据存储三张表解决所有问题数据库只有三张表设计得尽量精简probe_state记录每个探测源的名称、上次探测时间、上次执行状态成功/失败/超时、当前指纹。snapshots记录每次探测的关键字段快照和指纹只在指纹变化时写入。diffs记录比对出的差异包含探测源名称、旧值、新值、指纹、时间戳。为了做到同一事件只通知一次我在diffs表上建了一个联合唯一索引(probe_name, fingerprint)并在写入前做一次存在性检查。如果某次变化已经被记录过后几轮扫描又反复触发同样的指纹变化就不会重复告警了。这个机制非常关键后面会详细讲。3. 核心实现细节探测、指纹、规则3.1 探测请求的构造与容错写探测请求时有一个细节最容易踩坑User-Agent。很多平台在不同 UA 下会返回完全不同的页面结构甚至对没有显式 UA 的请求直接返回验证码。所以我要求每个 probe 必须显式配置User-Agent不设置全局默认值。本地调试时用默认 UA 一切正常部署到服务器后获取不到有效内容、日志里出现 418 或者验证码页面这种问题十有八九就是 UA 特征被风控识别了。容错方面我实现了三级重试连接超时5 秒后重试读取超时10 秒后重试状态码非 2xx 时重试。重试采用线性退避分别在 1 秒、3 秒、6 秒后发起。三次重试仍失败就把本次探测标记为 failed单独记录失败原因不参与差异比对。这里要特别注意绝对不能把探测失败当成内容差异来告警。目标平台短暂抖动是家常便饭如果每抖一次就发一条页面变了的告警运维人员很快就会麻木真正的异常反而被淹没。3.2 内容指纹与差异比对只关心你在乎的字段差异比对是 PLFM_RADAR 最核心的部分。一开始我直接对比两次探测的完整 HTML 文本结果被各种动态内容折磨得欲哭无泪——页面里的时间戳每次加载都会变随机 token 每次请求都不一样广告位轮播更是无规律可循。后来我调整了思路不对比原始文本而是先按解析器提取关键字段再针对关键字段计算哈希指纹最后对比指纹。这样有三个直接的好处第一非关键变化不会造成大量无意义 diff。比如页面里有一个实时时钟它的每一次变化都会被排除在指纹之外相比之下你真正关心的标题、按钮状态、字段值一旦变化指纹立刻就能反映出来。第二比对的数据量极小速度极快。第三指纹变化后可以反向定位到具体是哪个字段变了这对后续的告警展示非常有价值。目前内置了几种常用解析器html_text提取页面纯文本去标签后哈希。html_title_and_keywords提取 title 和 meta keywords。json_fields解析 JSON 响应提取配置中指定的字段路径。raw_text保留完整原始文本用于接口类探测。比如要监控某个 JSON 接口的data.status字段可以这样配置parser: json_fields fields: - data.status - data.items[0].name指纹计算使用 SHA-256把字段名和值拼接成规范字符串后哈希。比对时一旦发现新指纹与上次指纹不同就生成一条 diff 记录同时保存旧值和新值方便告警时直接展示变化内容。3.3 告警规则区分有价值的变化和噪音指纹变了不代表值得告警。不同页面、不同运行阶段你关心的字段可能是完全不同的。为此我实现了基于规则的过滤器每条 rule 可以指定字段、变化方向和动作。目前支持三种 actionnotify生成一条通知推送。record_only只记录到 diffs 表不推送。ignore完全忽略该字段的变化。举一个实际场景某个运营配置接口里有一个data.status字段取值 0 和 1 分别代表未开始和已开始。我想在它从 0 变到 1 时收到高优先级告警同时想忽略另一个动态列表data.items的频繁变化。配置样式如下rules: - name: campaign_release field: data.status expected_change: from: 0 to: 1 action: notify level: high - name: noise_items field: data.items action: ignore去重机制放在通知层实现。每条 diff 入库时带有一个指纹推送前先检查最近 60 分钟内是否推送过相同的指纹如果推送过就跳过。这叫同一事件只通知一次原则实际运营中真的太重要了。没有这个机制时一个页面的标题从 A 变成 B后续每隔 5 分钟的扫描都会发现和上次记录不一致哪怕已经记录过了照样每条都推一天能收到几百条重复告警。加了这个机制之后同样的变化只会在第一次发生时推送到群里。4. 实测运行效果与持续调优4.1 一轮扫描要跑多久延迟分布什么样我把 40 多个 probe 部署到一台 1 核 1G 的云主机上覆盖了三个平台的页面和接口扫描频率从 5 分钟到 30 分钟不等。连续跑了两周后统计了一下关键数据指标数值平均一轮扫描耗时并发 55~8 秒平均一轮扫描耗时并发 102.5 秒左右每日平均产生有效 diff20~50 条其中真正需要人工关注的占比约 20%从变化发生到收到告警的平均延迟30 秒以内延迟构成大致是调度轮询间隔平均 5 秒、扫描执行时间约 2 秒、比对与告警发送时间约 1 秒。整体可以做到在页面变化发生后 1 分钟内感知对于运营类监控来说已经足够了。4.2 误报是怎么压下来的我用了差不多一周时间才把误报率压到可以接受的水平。最常见的误报来源有三个第一是动态字段。很多页面自带时间戳、随机令牌、广告位轮播每次加载都不一样。解决办法是在解析器里指定只看哪些字段把动态部分排除在指纹之外。比如对某个活动页我只需要关注.keyword和.title其他统统忽略。第二是登录态过期。某些平台在会话失效后会返回一个跳转页面指纹随之剧烈变化产生大量无意义 diff。我在全局加了一个会话状态守卫如果某个 probe 连续三次返回的内容与历史快照相似度极低同时响应头中出现跳转特征就标记为 session_expired暂停这个 probe 的告警并单独通知管理员去更新登录态。这样避免了登录态过期 → 全部页面都告警的雪崩式误报。第三是 CDN 节点切换。目标平台切换 CDN 时响应头可能变化页面内容可能出现细微的顺序差异。解决办法还是提高指纹粒度——只关注业务字段不关注响应头字段同时把指纹计算的内容固定到稳定部分。4.3 资源占用与数据库写入优化PLFM_RADAR 的资源占用极低。日常内存占用约 120MBCPU 使用率平时几乎为 0只有每轮扫描启动的瞬间会有一个小高峰。真正值得优化的是数据库写入量。如果每个 probe 每轮都写一条快照高频 probe 在 5 分钟间隔下一天就会产生 288 条记录几十个 probe 叠加起来数据表很快就会膨胀。我的处理方式是如果指纹没有变化就只更新probe_state表中的last_seen时间不写snapshots表只有指纹变化时才写快照和 diff。这样既保证了历史可追溯性又把无意义的磁盘 I/O 减掉了一大半。上线两周后snapshots表只有不到 5000 行diffs表更小SQLite 文件总共才 20 多 MB。5. 部署踩坑记录三个让我印象深刻的教训5.1 请求频率过高触发平台限流第一次上线时我图省事把所有 probe 的间隔都设成了 30 秒。结果运行不到两个小时其中一个平台就开始返回 429 Too Many Requests紧接着连续几个小时所有请求都被拒绝。原因很简单40 个 probe 每 30 秒打一次平均每秒超过 1 个请求同一个 IP 的请求密度太集中被对方的风控判定为异常。后来我把高频探测收敛到最核心的 5 个 probe间隔调整到 60 秒其余 probe 拉长到 10 分钟以上同时在所有请求外面套了一个全局限速器保证同一时刻在途请求不超过 5 个。调整之后再也没触发过限流。这件事给了我一个教训监控工具的探测频率不是越高越好而是越契合目标平台的实际变化速度越好。一个三天才动一次的页面你每隔五分钟去扫一次纯粹是给自己找麻烦。5.2 编码解析错乱导致假 diff另一个让我头疼的问题是编码。某些平台的页面声明是 UTF-8但返回的响应头里没有 charset页面内嵌的 meta 标签声明的却是 GBK。如果直接用response.text解码会在部分字符上出现乱码指纹计算也随之失败产生大量根本不存在的内容差异。解决思路是在解析器里显式处理编码优先级从高到低分别是——probe 配置里指定的encoding字段、响应头的 charset、页面 meta 声明的 charset、最后才是 UTF-8 兜底。这个细节直接关系着指纹的稳定性如果编码不对后面所有的比对结果都不可信。5.3 调度漂移从 interval 改成 cronAPScheduler 的interval模式在某些版本里存在调度漂移表现为任务触发时间越来越晚尤其当系统负载较高或者单轮任务执行时间较长的时候。原本设置的 5 分钟间隔实际可能被拉长到 5 分半甚至 6 分钟。短期看不出问题跑几天就会明显偏离预期。解决办法是把所有高频 probe 从interval改成 cron 触发方式比如0 */5 * * * *代表每 5 分钟的第 0 秒触发。这样即使上一轮任务跑超时下一轮仍然按固定的时间窗口触发最多出现任务重叠配合一个进程内任务锁全局只允许同一 probe 并发执行一次就能解决。6. 适用场景、边界与后续扩展6.1 适合用 PLFM_RADAR 解决的场景经过这段时间的实际使用我认为它最适合以下四类场景监控运营专题页或活动页的上线/下线时间以及页面主视觉、标题的变动。监控内部配置接口的返回字段变化比如功能开关、权益状态、活动状态位。监控竞品的公开页面变化前提是严格遵守对方网站的访问规则控制好频率和并发。监控多个内部系统状态页面的可用性作为简易的健康检查工具。6.2 不适合的场景及时止步有几个场景我不建议用它第一是大规模数据抓取。PLFM_RADAR 的定位是低频、精判读不是高吞吐的内容抓取引擎。如果你需要批量下载几百个页面请用 Scrapy 或类似专用框架。第二是实时性要求达到秒级的场景。即使告警链路优化到极致从变化发生到告警到达依然需要几秒甚至几十秒的调度延迟如果业务要求毫秒级感知应该走消息订阅或回调机制。第三是需要账号登录态的强权限内容监控因为登录态维护成本很高一旦过期还会产生大量误报性价比不高。6.3 后续可以做的扩展方向如果你也想把类似工具用到生产环境我建议从这几个方向继续完善把解析器做成完全配置化支持 CSS Selector 字段提取这样适配一个新页面结构时完全不用写代码。增加变化类型聚类能力用简单的规则或机器学习模型自动把 diff 分为内容变化、结构变化、样式变化分别走不同的告警策略。把通知渠道插件化。目前我支持企业微信、钉钉和邮件后续可以扩展飞书、Server酱等。加一个极简 Web 面板展示每个 probe 的健康状态、最近 diff 流以及历史变化的时间轴视图。对我来说PLFM_RADAR 最大的价值是把主动关注变成了被动接收让我从反复刷新页面的低效劳动中解放出来。以前遇到某个平台是不是悄悄改了东西这种灵魂拷问只能靠记忆和猜测现在直接翻告警记录和 diff 流水就能还原整个过程。如果你也有类似的监控需求不妨从一个小脚本开始按这套思路一步步加上解析、规则和通知很可能不到两周就能拥有一套顺手的小工具。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进