PLFM_RADAR这个代号是我给自己最近做的一套平台数据监测系统起的名字。PLFM取PlatformRADAR则跟雷达的语义完全一致——持续发射扫描信号、接收回波、识别异常。这套系统的应用场景很简单我需要同时盯住几个平台的公开信息包括商品价格、库存状态、榜单排名、规则公告还有我们自己的运营数据靠人肉刷新根本盯不过来于是就有了这个平台雷达。如果你也在维护数据类服务、做竞品监测或者纯粹想摆脱定时刷网页这种原始操作这篇文章应该能给你一份可以照着抄的作业。整套系统从立项到跑通前后用了三周左右中间推翻过一次重来。现在回头复盘踩过的坑基本都集中在采集频率控制、信号去重、告警风暴这三件事上。我会把架构设计、关键代码、参数取舍、排障方法都拆开讲尽量说清楚每一步背后的为什么而不只是给结论。1. 需求拆解平台雷达到底要看什么1.1 一句话说清楚这个项目PLFM_RADAR本质上是一个把周期性外部数据采集指标异常识别多渠道消息推送串起来的监控系统。它解决的问题非常具体过去运营同学每天上班第一件事是打开十几个网页逐个看竞品价格有没有变、榜单有没有波动、平台有没有出新规则。这种纯手工操作效率低、容易漏而且人眼很难发现缓慢的趋势变化——比如某个关键词排名连续七天每天跌两名到第八天才发现已经跌出首页了。雷达系统把这件事自动化每个数据源按照预设频率扫描一次采集到的数据进入存储层分析层计算各种派生指标并与历史基线比对一旦触发告警规则就通过飞书、钉钉或企业微信的webhook把消息推给对应的人。重点不是抓取所有数据而是抓有用的、会变化的、能触发决策的数据。这个项目比较适合三类人参考一类是运营或产品负责人想做自己的轻量级数据监控一类是后端开发想看看一个完整的采集告警链路怎么落地还有一类是独立开发者想给自己的多个服务加一个统一观测入口。技术难度其实不高难的是对需求的收敛和参数的调优。1.2 为什么最终选了Python技术栈选技术栈这件事我纠结了两天对比过Node.js和Go。Node.js在异步IO和高并发上有优势Go在性能和部署上更省心但最后我还是选了Python理由有三条。第一生态匹配度最高。采集层有requests、httpx、BeautifulSoup、playwright分析层有pandas、numpy调度层有APScheduler展示层有Streamlit和Grafana客户端。全部是Python生态内已经验证过的方案不用跨语言拼装。第二迭代速度快。这个项目最核心的价值在分析规则的调整上Python这种动态语言改完就能跑不需要编译重启的负担。第三团队里其他同学也会看代码Python对他们来说是最低门槛的语言。Go和Node.js在性能上确实更好但一个低频率的监测系统根本到不了性能瓶颈大多数采集任务每天也就执行几十次到几百次用Python绰绰有余。顺便说一句如果哪天真到了需要高并发的场景Python也留了后路把采集器单独拆成服务用Celery做分布式任务队列或者用FastAPI暴露接口交给其他语言调用。前期不需要把架构搞得太重够用就好。1.3 数据源的边界设定做监测系统第一件事不是写代码而是把哪些数据可以采、哪些不能采界定清楚。我的原则是三条只采集公开可见的信息不碰任何需要登录凭证才能访问的数据严格遵守平台的访问频率限制不给对方服务器增加压力优先使用官方提供的API其次再考虑页面解析。这个原则很重要因为合规性是整套系统的地基。数据源的范围直接决定了采集层怎么写也影响了后续规则引擎的复杂度。我在PLFM_RADAR里把数据源分成了四类价格与库存类、内容与榜单类、公告与规则类、自有业务数据类。每一类有不同的采集频率和解析方式比如价格类需要小时级的监控公告类反而半天扫一次就够了。先把边界定清楚后面才不会写出一堆用不上的采集器。2. 架构设计从采集到告警的完整链路2.1 四层架构与数据流向PLFM_RADAR的整体架构我按数据流向分成了四层采集层、存储层、分析层、展现与告警层。每层只和相邻层通信接口定义清楚之后各层都可以独立替换。采集层是雷达的发射端负责按照调度策略访问各数据源拿到原始数据后做初步清洗统一成JSON结构写入存储层。每个数据源对应一个采集器插件插件内部只负责获取解析不关心数据处理逻辑。存储层我选了PostgreSQL主要存两类内容原始快照数据和派生指标数据。原始数据保留30天派生指标保留90天超期数据由定时任务清理避免表无限膨胀。分析层是雷达的信号处理器它周期性从存储层读取数据完成三类任务第一是计算派生指标比如价格变化率、排名走势、榜单词频第二是和基线做对比判断当前值是否落入了正常波动范围第三是触发规则引擎输出告警事件。展现与告警层在架构上是分开的展现部分用的是Grafana看板告警部分通过一个统一网关把消息路由到飞书、钉钉、企业微信或邮件。分两套子系统是为了避免看板把告警接口拖挂了这种低级事故。2.2 关键设计决策的取舍理由先说存储选型。好友推荐过SQLite和MySQL我都认真想过最后仍然坚持用PostgreSQL。SQLite确实零运维但它的并发写性能比较差采集器每隔几秒就要写入数据后期一定会卡。MySQL虽然成熟但PostgreSQL在JSON字段支持、窗口函数、时序处理上对数据分析类任务更友好。比如我需要算percent_rank()、lag()这类分析函数PostgreSQL原生支持得很舒服MySQL 8.0以后虽然也有了但部分语法细节还是有差异。监测系统最怕分析SQL写起来别扭所以存储层投资值得。再说消息队列。很多同类项目一上来就引入RabbitMQ或Kafka我最后没加。原因是当前规模根本不需要异步削峰——采集任务低频率运行分析任务以分钟级触发直接同步调用就够了。引入消息队列反而增加运维负担还要处理消息积压、消息丢失一堆破事。我的原则是架构复杂度永远晚于业务需求出现等采集源超过50个、单轮采集超过5分钟再考虑加队列也不迟。最后是配置管理。所有采集源的URL、频率、解析规则、告警阈值我都放在一个YAML配置文件里程序启动时加载到内存。之所以不用数据库存配置是因为配置文件可以用Git管理改版有记录、出问题可以回滚。这点在多次调整参数后体现出了巨大价值。3. 核心模块采集、规则与告警的核心细节3.1 采集层实现要点与调度参数采集层是雷达的地基它的设计直接决定了数据质量和系统稳定性。我拆成三个子模块调度器、采集器插件、数据清洗管道。调度器用的是APScheduler的CronTrigger配置示例大概是from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.triggers.cron import CronTrigger scheduler BackgroundScheduler(timezoneAsia/Shanghai) # 价格类数据源每小时整点采集错开前5秒 scheduler.add_job( collect_price_job, CronTrigger(hour*, minute0, second5), idprice_collector, max_instances1, coalesceTrue, ) # 公告类数据源每30分钟采集一次 scheduler.add_job( collect_notice_job, CronTrigger(minute*/30, second15), idnotice_collector, max_instances1, coalesceTrue, ) scheduler.start()这里有个细节max_instances1和coalesceTrue必开。前者防止上一次任务还没跑完就把下一次任务拉起来后者是把错过的任务合并成一次执行。对监测系统来说丢一个周期不可怕可怕的是任务堆积导致采集频率变得不可预期。采集频率怎么定我的经验公式是单个数据源最低频率不要低于对方平台允许阈值的四分之一给自己留出4倍余量。比如某个商品列表页的接口明显是5秒限制一次那我们就至少间隔20秒以上。整套系统里最频繁的是价格类数据每小时扫一次其他类型数据源的频率都在30分钟以上。实际上这个频率对运营决策来说绰绰有余——价格变化很少在十分钟内发生两次榜单数据的更新周期通常是按天算的。数据清洗管道是容易被人忽略的地方。原始数据结构五花八门有的平台返回JSON有的平台返回的是嵌在HTML里的动态数据还有的是经过混淆的编码字段。我在清洗管道里做了三件具体的事统一字段命名格式为snake_case、统一时间字段为ISO 8601字符串并附上时区、统一价格单位到分以避免浮点数误差。最后这一点特别重要所有金额在内部全部转成整数分存储只在展示层转回元。这样做的原因是浮点数比较大小会有精度问题做历史趋势分析时会出现1.9999元这种脏数据。3.2 分析层的信号识别逻辑分析层是这套雷达里最需要调的部分刚开始我用的是最简单粗暴的固定阈值比如价格低于100元就告警。跑了一周后发现这种规则误报率相当高——有些商品平时就是130元上下波动偶尔促销打折到99元这根本不是异常但我们每次都报警时间长了同事直接无视所有通知。后来我把规则改成了两层先计算历史基线再用偏离度触发告警。历史基线的计算很简单取过去7天同时段的均值作为基线再计算标准差当当前值偏离超过2个标准差时触发关注级告警超过3个标准差才触发严重告警。用标准差而不是百分比的好处是它能自适应不同数据的波动特性价格本来就波动大的品项它的标准差也大就不容易误报而平时一直很稳定的指标稍微动一下就会被捕获。排名类数据的信号识别用了另一种思路核心指标是跨天位次变化def ranking_shift_analyzer(current_rank, last_rank, history_ranks): # 计算7天内的排名中位数作为基准 median_rank sorted(history_ranks)[len(history_ranks) // 2] if current_rank 0: return none # 数据缺失不分析 if last_rank 0 or median_rank 0: return new_entry shift current_rank - last_rank if abs(shift) 5 and current_rank median_rank * 0.6: return significant_up if shift 10 and last_rank 5: return rank_drop_danger return normal原理是通过中位数做稳健基准避免极值把均值拉偏。比如某个词的历史排名是第3、4、5、50、6、5均值会被50这个异常值拉高中位数就不会。这样设计后排名监测识别新上榜显著上升危险下滑三类信号命中率比固定阈值提高了一倍多。公告类数据的分析逻辑最简单用关键词和文本指纹去重只推送新增的规则条目。实现方式是维护一个长度为64的simhash比对两两之间的汉明距离距离小于3就认为是重复内容。这个方法在处理平台更新了公告但只是改了个别措辞时很有效。3.3 告警分级与通道路由告警这件事做得不好比不告警还糟糕。我在PLFM_RADAR里做了三个级别对应不同的紧急程度和处理方式。P0是严重故障级比如核心数据源连续3次采集失败、关键指标价格跌幅超过5%或出现页面结构重大变更这类告警直接推送到飞书群的所有人同时发邮件给值班同学。P1是关注级比如排名突降、库存告急推送到工作群里但不所有人。P2是信息级只写入告警事件表第二天早上同步进日报邮件。通道路由方面飞书、钉钉、企业微信都支持webhook机器人我的统一网关就是一层简单的HTTP封装def send_alert(event): webhook_map { feishu: config[alert_channels][feishu_url], dingtalk: config[alert_channels][dingtalk_url], email: config[alert_channels][smtp_host], } if event[level] P0: payload build_feishu_card(event, mention_allTrue) post_webhook(webhook_map[feishu], payload) if event[level] in (P0, P1): payload build_dingtalk_markdown(event) post_webhook(webhook_map[dingtalk], payload) if event[level] P2: append_to_daily_digest(event)这里有个很容易踩的坑不要把告警逻辑和业务逻辑耦合在一起。我最初偷懒直接在分析函数里调用发送函数结果有一次发布代码时改了发送逻辑导致所有告警静默了12个小时。后来把告警独立成一个事件模块分析层只往告警表里写记录再由另一个定时任务消费告警表做分发彻底解耦。这多花了一天时间但换来的是后续所有改动都变得安全了。3.4 可视化看板的落地方式可视化我用的是Grafana加PostgreSQL数据源。告警系统可以没有看板但没有看板的雷达等于盲飞特别是当你想复盘某个时间段到底发生了什么时有图表一眼就能看出问题。Grafana里我配置了两类看板一类是信号概览展示各数据源最近24小时的采集成功数、失败数、平均耗时、告警事件数量另一类是指标趋势按数据源维度展示价格、排名、词频的历史曲线和告警时间点标记。配置Grafana不需要写代码关键是在SQL里把时间字段格式处理好。我们的时间统一存成timestamptz类型Grafana只需要指定timeField就能正确识别。还有一个小经验在采集成功数的地方加一个阈值线连续失败超过3次就显示红色这样值班同学打开看板第一眼就能判断系统健康状态。4. 实操实录从零搭建PLFM_RADAR4.1 环境准备与目录结构我用的是最简单的部署方案一台2核4G的云服务器Ubuntu 22.04上面跑Docker容器。整套系统分三个容器plfm-db跑PostgreSQL 15plfm-app跑采集与分析的Python应用plfm-grafana跑可视化。docker-compose是核心编排文件就不贴完整代码了重点说目录结构plfm_radar/ ├── app/ │ ├── collectors/ # 采集器插件目录 │ │ ├── __init__.py │ │ ├── price_source.py │ │ ├── ranking_source.py │ │ └── notice_source.py │ ├── analyzers/ # 分析器目录 │ │ ├── baseline.py │ │ ├── ranking_shift.py │ │ └── rules_engine.py │ ├── notifiers/ # 告警分发目录 │ │ ├── feishu_webhook.py │ │ ├── dingtalk_webhook.py │ │ └── email_sender.py │ ├── storage/ # 存储层封装 │ │ ├── db.py │ │ └── models.py │ ├── config.yaml # 全局配置 │ └── main.py # 调度入口 ├── grafana/ │ ├── provisioning/ │ └── dashboards/ └── docker-compose.yml这个目录结构是我重构后的最终形态。第一版没有collectors插件目录所有采集逻辑都堆在一个crawler.py里一个文件一千多行改一个新数据源要动其他模块很容易改出问题。做了插件化之后新增一个数据源只需要在collectors/下加一个文件在config.yaml里配好参数主程序无需改动。4.2 采集器与主调度实现核心采集代码不宜追求花哨我的报价采集器核心逻辑大概30行左右import httpx import yaml from storage.db import save_snapshot class PriceCollector: def __init__(self, source_config): self.url source_config[url] self.selector source_config[selector] self.headers source_config.get(headers, {}) self.timeout source_config.get(timeout, 15) def collect_once(self): try: resp httpx.get(self.url, headersself.headers, timeoutself.timeout, follow_redirectsTrue) resp.raise_for_status() raw parse_price_dom(resp.text, self.selector) normalized normalize_price(raw) # 统一转“分” save_snapshot( sourceself.url, metric_typeprice, valuenormalized, raw_json{snippet: resp.text[:500]}, ) return True except Exception as e: log_collect_error(sourceself.url, errorstr(e)) return False注意我用了httpx而不是requests原因是httpx支持HTTP/2和异步接口后期如果要并发采集多个源改造起来成本更低。第一次写的时候用的requests后来换数据源的时候发现有些接口是HTTP/2 only被迫整批改掉这个教训记住就好。主调度入口main.py做的事情很单一加载所有数据源的配置注册到APScheduler然后启动一个常驻循环。不过有一个事情必须在主循环里做——周期性的元数据检查和异常自愈。比如检测到某个采集器连续失败次数超过阈值就自动降低该采集频率避免放大对源站的请求压力。这个自动退避机制是应对平台风控的有效手段后面排障部分会细讲。4.3 信号分析规则配置分析规则的配置我放在同一个YAML文件里格式一目了然analyze_rules: - name: price_drop_p0 metric: price condition: zscore -3 or change_pct -5 window: 2h level: P0 - name: stock_low_warn metric: stock_status condition: current_value out_of_stock level: P1 cooldown: 6h - name: rank_drop_danger metric: ranking condition: shift -10 and last_rank 5 level: P1 cooldown: 1h这里的zscore就是前面说的标准差偏离度cooldown是告警冷却时间用来做告警抑制。比如缺货这种事如果平台半小时内补上了又缺系统会重复推送同一条消息冷却时间保证同一个指标在周期内只推一次。这个配置化思路让运营同学自己也能改规则不必每次都找我写代码。4.4 告警落地与看板验证告警这块踩了一个比较典型的坑飞书自定义机器人的签名问题。飞书webhook要求把请求时间戳和密钥拼接后做HMAC-SHA1签名第一次对接时我漏了这个步骤导致所有消息都被拒。排错方式也比较直接先手动curl测试webhook地址发现能通再看业务代码的Header构造。最后问题出在我把签名值做成了大写十六进制而飞书要求小写。这种跨平台的细节基本只能靠踩坑积累文档里不会写这些。Grafana看板验证就顺利很多配置完数据源后写一条测试SQL确保时间字段正确SELECT recorded_at AS time, source_url, value FROM metric_snapshots WHERE metric_type price AND recorded_at now() - interval 24 hours;Grafana会自动识别time列。另外一个提醒不要在Grafana的SQL里用GROUP BY做太复杂的聚合数据量大时查询会慢可以在PostgreSQL里预先创建物化视图Grafana只做简单查询。5. 常见问题与排查速查表5.1 运行两周后遇到的典型问题系统上线两周我记录了13个问题其中最典型的有6个整理成了一张速查表现象原因排查思路解决办法某个数据源偶发采集失败目标站有简单的频率限制查看采集日志的状态码分布增加随机sleep避免固定间隔触发封禁告警重复推送没有做冷却时间查看告警事件表的insert时间给每条规则加cooldown字段价格数据出现0值解析到了页面默认占位符打印原始DOM片段在清洗管道过滤0值和空值图表上时间不连续时区设置不一致检查数据库时区和调度器时区统一为Asia/Shanghai存timestamptz分析任务越跑越慢快照表没有索引查看慢查询日志对(metric_type, recorded_at)建复合索引告警全部静默告警模块异常但被try捕获吞掉检查日志有没有error级别记录告警发送失败要单独抛出并落盘这个表就是项目的体检报告建议任何监测系统上线后都要做类似的问题复盘很多坑在不同数据源之间会重复出现。5.2 关于数据源反爬与请求频率的实践心得说句实在话与其花大量精力研究如何绕过各种反爬机制不如一开始就把请求频率控制得足够礼貌。我在PLFM_RADAR里对所有数据源的请求都做了三重保险全局每秒最大请求数限制为2单个源的最小请求间隔不低于5秒失败自动退避。这组参数牺牲了一些采集时效性但换来了稳定的长期运行——我系统连续跑了两个多月没有一次被封IP或被平台重点标注。另外代码里所有HTTP请求必须设置timeout这是血泪教训。最开始有几个采集器没配timeout结果某个源服务器假死时连接一直挂在那里线程越积越多最后整个进程内存和文件描述符都被耗尽系统无响应。加了统一超时后这个问题彻底消失。5.3 告警风暴抑制的三重手段告警风暴是监测系统最常被诟病的问题我的处理思路是三条线共同作用。第一单源静默同一数据源在指定时间段内只发一条告警配置里的cooldown字段就是在干这个。第二全局静默当某个P0级事件触发后系统自动进入10分钟的观察期期间其他P1/P2告警只记录不推送避免连环爆炸消息。第三维护窗口每周日凌晨2点到4点设定为静默窗口因为平台方通常在这个时间段做系统升级会造成大量假性告警没必要惊扰值班人员。这三条线同时启用后告警数量从最初每天40多条降到了平均每天5条以内其中还包括了一半是P2信息级消息。值班同学终于愿意认真看告警了而不是把群消息一键已读。5.4 巡检与调优经验系统正常跑起来后我给自己定了一个巡检清单频率是每周一次。核心看四个指标采集成功率是否在98%以上、分析任务是否有积压、数据库磁盘使用率是否触顶、告警事件的平均处理时长变化趋势。这四个指标能覆盖大部分稳定性问题。调优方面最重要的一条经验是基线要滚动更新但不能更新太快。价格类数据的基线窗口我设置为7天每天滑动更新。窗口太短比如1天就容易把突发波动当成基线吸收掉后面真的出现异常时发现不了。窗口太长比如30天又无法适应季节性变化。7天是我的经验值不同场景可以自己调但思路是一样的——基线更新速度要远慢于你关心的异常变化速度。还有一个容易犯的错分析规则越加越多但从来不清理失效规则。我第一版写了十几条规则后来发现三分之一永远触发不了。建议每次加规则都带上有效期过期后自动停用并清理保证规则集始终精简可用。最后再说两句掏心窝的话做PLFM_RADAR这个项目最深的体会是监控系统的核心不在于技术多先进而在于少打扰人和抓得准。我一开始把规则做得很激进恨不得所有波动都提醒一遍结果大家麻木了真正的严重问题反而被淹没。后来我反复调整基线算法和告警阈值目标变成了要么不响响就一定有用。这套系统衍生出来的另一个小工具也很有用——我把数据采集层的清洗管道单独抽出来做成了一个可以给任意数据源做健康体检的脚本输入URL就能看这个接口的响应时间、数据结构稳定性、字段缺失率。采集源多起来之后这个工具成了每次接入新数据源的第一道检查关卡。如果你也想搭一套类似的平台雷达建议从一个小数据源起步先把链路跑通再慢慢加规则加看板。不要一开始就追求大而全这套系统的复杂度会随着数据源数量线性增长控制规模才能保持它的可用性。希望这篇复盘能给你省掉几天的试错时间。