1. 凌晨三点还在点刷新先算清楚手工监控的真实成本去年有段时间我的浏览习惯彻底被一个预约页面绑架了早上睁眼先点一次刷新通勤路上点一次午休点一次晚上躺床上还要再点两三次。那段时间我甚至形成了条件反射手机一震动就以为页面变了。后来我认真算了一笔账发现自己真正浪费掉的并不是那几十次点击的几十秒而是被切碎之后再也拼不回来的注意力。这篇文章讲的自动提醒工具就是在这种情况下被我一点点做出来的它只做一件事——代替我去看那个页面看到值得通知的变化就用一条消息把我叫醒。这篇内容适合三类人参考。第一类是正在被重复性页面检查消耗精力的朋友不管是预约名额、库存补货、报名开放还是工单状态本质都是同一套逻辑第二类是有基础脚本能力、想把一个临时小脚本做成能连续跑几个月的小工具的人第三类是纯粹想了解“轮询 差异判定 通知”这套范式的人。整套方案的核心关键词就三个定时轮询、变化判定、消息推送。不需要服务器集群不需要复杂框架一台常年开机的小机器或者一个免费额度的云函数就能跑起来。1.1 手工刷新最贵的不是时间是注意力碎片很多人评估这类事情的时候喜欢算“一次刷新三秒钟一天三十次也就一分半”这个算法是错的。真正的损耗在于上下文切换你在写方案的时候点一次刷新回来之后重新进入状态要花掉三五分钟你在陪家人吃饭的时候点一次刷新话题就断了。一天下来被打断十几次损失的是整块的时间而不是零散的那几十秒。我在改成自动提醒之后做了个粗略对比以前手工盯着页面的那两周每天有效工作时间大概被削掉一个半小时改成工具提醒之后我一天只在自己方便的时间段集中处理一次其余时间完全不碰那个页面。工具的价值不在于它比我点得快而在于它把“要不要看”这个决策从随机打断变成了固定节奏。还有一点容易被忽略人是会疲劳的。盯着同一个页面看两百次你会在第一百八十次的时候开始漏看——手指点下去了眼睛没扫到列表里的新增那一行。机器不会疲劳它每一次判断的条件完全一致这比速度重要得多。1.2 覆盖盲区你睡觉的那八小时才是放号高峰手工监控最大的漏洞不是频率不够而是时段覆盖不全。我统计过自己查看页面的时间分布基本集中在早九点到晚十一点而深夜里页面上发生变化的概率一点都不低。原因不难理解很多时间段的名额是别人取消后回流的取消行为本身是分散的凌晨和清晨完全可能冒出来。这就引出了整个工具设计里最关键的一个认知你不需要更快的监控你需要不间断的监控。每三分钟查一次、只在白天查一共覆盖 280 次左右每十分钟查一次、全天不间断也能覆盖 144 次。后者的有效发现率通常更高因为它把最宝贵的夜间时段包了进去而且请求量还更小对目标页面更友好。提示在动手写代码之前先花十分钟记录一下自己的作息和查看习惯据此决定监控时段。覆盖盲区的补全收益远大于把轮询间隔从三分钟压到一分钟。1.3 第三方提醒服务必须先问清楚的三件事市面上确实有现成的提醒服务我也试过几个。用不用它们不是重点重点是在把预约这件事交给别人之前你得先弄清三个问题。第一它读的是什么。是公开页面的只读信息还是需要你交出账号凭据如果是后者你等于把身份验证的全部权限交出去了一旦对方存储不当后果自己承担。我个人只接受纯只读、不涉及任何登录态的方案。第二它的刷新节奏是不是你能控制的。有些服务的提示延迟很长因为它自己也在控制成本有些则过于激进反而容易触发对方的访问限制。节奏不透明就意味着你无法判断消息的可信度。第三它的通知能不能落到你确定会看的地方。我见过不少服务只在它自己的应用里弹通知而我根本没有常开那个应用的习惯等于白搭。这一点后面第 5 节会详细展开。想清楚这三个问题之后我最终选择了自己写。不是因为我排斥现成工具而是因为这套逻辑本身足够简单自己实现一遍反而更可控。2. 工具的整体骨架采集、判定、通知三段式整套工具被我拆成三段从下到上依次是采集层、判定层、通知层。这个拆分不是为了架构好看而是为了让每一段都能单独调试、单独替换。我最初写的版本是“抓取—比较—发送”揉在一个函数里的一百多行脚本改到第二周就改不动了想换通知通道要动抓取代码想调节轮询间隔要把判定逻辑也读一遍。拆开之后三个部分各自有清晰的输入输出契约改哪一块都不影响另外两块。三段之间的数据流很朴素采集层把一次抓取的结果整理成一个纯数据结构比如一个列表每个元素是一个名额条目含日期、地点、类型、状态判定层拿着一份“上一次的快照”和“这一次的快照”做比较输出一个变化事件通知层收到事件之后决定发不发、发什么、发到哪。整条链路里只有采集层会碰到网络和外部页面判定层是纯函数通知层只负责投递。这个边界划清楚之后测试成本会低一个量级——我可以在本地拿两份手写的快照数据跑判定逻辑完全不需要联网。2.1 采集层只需要做一件事把页面翻译成可比较的数据采集层的职责边界必须收得很紧。它不应该做任何判断比如“这个名额是不是比上次更早”“这个类型我感不感兴趣”这些全部交给判定层。采集层就干一件事把这一次看到的页面翻译成一个结构化的快照然后原样交给下一层。好处是当页面改版时你只需要改一个文件里的解析逻辑其它部分完全不动。具体实现上我会优先找结构化数据。现在很多页面的渲染其实依赖后端返回的 JSON浏览器里打开开发者工具切到网络面板筛一下请求类型大概率能找到那个返回列表数据的接口。直接请求这个接口比解析 HTML 省事一个数量级字段名清晰、没有嵌套的样式干扰、结构变化时也更容易被发现。我一般会把这个接口的地址和必要的请求头抄下来用最小的请求复现一次确认返回结构和页面一致。只有实在找不到结构化接口的时候我才会退回到解析 HTML。这时候建议用稳定的选择器比如带上 data 属性的节点或者用“文本包含”的方式定位尽量不依赖层级很深的div div:nth-child(3)这种写法——那种选择器在页面加一个广告位的时候就会全盘失效。import hashlib import json def build_snapshot(raw_items): 把抓取结果整理成统一结构的列表供判定层使用 snapshot [] for item in raw_items: snapshot.append({ date: item.get(date, ).strip(), location: item.get(location, ).strip(), type: item.get(type, ).strip(), status: item.get(status, ).strip(), }) # 排序后再算指纹避免接口返回顺序波动造成假变化 snapshot.sort(keylambda x: (x[date], x[location], x[type])) return snapshot def fingerprint(snapshot): payload json.dumps(snapshot, ensure_asciiFalse, sort_keysTrue) return hashlib.sha256(payload.encode(utf-8)).hexdigest()这段代码里有两个细节值得单独说。一是排序很多接口返回的条目顺序并不固定同样的内容换了顺序直接比较就会认为“变了”。排序之后再算指纹可以滤掉这类假变化。二是字段裁剪快照里只保留真正影响判断的字段不要把整个响应原样塞进去否则对方在响应里加了一个时间戳字段你就再也收不到“没变化”这个结论了。2.2 判定层定义什么才算一次值得打扰你的变化判定层是整个工具里最需要动脑子的地方也是最容易做歪的地方。新手常犯的错误是把“快照不相等”直接等同于“有变化”结果就是收到大量无效提醒页面上的刷新时间变了、条数统计的文案变了、接口里某个字段带了毫秒级时间全都会触发一次通知。三天之后你就开始无视这些消息了工具等于失效。我的做法是把变化分成三类只有前两类才值得推送变化类型判定条件是否推送新增本次快照中存在、上次快照中不存在的条目推送消失上次存在、本次不存在说明被占用或已关闭视情况通常推送属性变化同一条目的状态字段从“可预约”变为“不可预约”推送元信息变化时间戳、总数文案、无关字段忽略具体实现上用集合差集比逐字段比对清晰得多。把每个条目转成一个可哈希的元组日期、地点、类型拼起来两份快照的元组集合做差得到的就是新增和消失。属性变化则用字典映射来比键是元组值是状态。def diff_snapshots(old_snapshot, new_snapshot): def key(item): return (item[date], item[location], item[type]) old_map {key(i): i for i in old_snapshot} new_map {key(i): i for i in new_snapshot} added [new_map[k] for k in new_map.keys() - old_map.keys()] removed [old_map[k] for k in old_map.keys() - new_map.keys()] changed [ new_map[k] for k in new_map.keys() old_map.keys() if new_map[k][status] ! old_map[k][status] ] return {added: added, removed: removed, changed: changed}这三行集合运算替代了原来几十行 if-else而且逻辑一眼能看懂。我在实际调试时发现把判定写成这样之后加新规则变得很容易——比如“只关注某个特定地点”在 key 生成的时候过滤掉其它地点就行不用动比较逻辑。2.3 通知层决定这条消息值不值得叫醒你通知层的设计目标不是“把消息发出去”而是“让这条消息在正确的时间以正确的方式被你看到”。这两件事差别很大。我第一版工具是收到变化就立刻发消息不分时段结果第一天夜里被震醒了四次其中三次是同一个变化被不同轮询重复捕获。后来我加了两道闸门时段闸门和合并闸门。时段闸门规定非紧急的变化只在早上七点到晚上十一点之间推送夜间发生的变化攒到早上一并发出合并闸门规定五分钟内的同类变化合并成一条消息而不是发五条。这两道闸门加上去之后消息量从一天几十条降到一天两三条而实际上没有漏掉任何一个关键窗口。3. 轮询节奏怎么定从“越快越好”改成“够用就好”绝大多数人第一次写这类脚本第一反应是把间隔设成十秒。我试过结果是两天之内就被目标页面拒绝了好几次而且日志里全是超时错误反而比三分钟一次漏得更多。轮询间隔是一个需要推导的参数不是拍脑袋定的。3.1 间隔的推导过程推导的核心变量有三个单个名额从出现到被占用的平均存活时长、你能接受的决策延迟、目标页面能承受的请求量。假设根据你观察到的历史经验一个名额出现后平均能存活二十分钟左右才被抢走那么理论上的最短间隔只要小于这个存活时长你就不会因为轮询太慢而系统性错过。取存活时长的十分之一到五分之一也就是两到四分钟就足够稳妥了。但如果页面本身更新有缓存比如它自己的数据每五分钟才刷新一次那你查得再勤也没有意义纯属浪费请求。所以第一件事是先测量页面的实际更新粒度在一个低速状态下连续观察半小时记录看到变化的时刻看看最小间隔是多少。这一步很多人跳过导致后面的所有调参都是盲调。我的最终配置是白天每 180 秒一次、夜间每 600 秒一次并在每次间隔上叠加一个 ±15% 的随机抖动。抖动这个细节很重要固定到秒的请求节奏在日志里会形成非常机械的图案加上抖动之后请求分布更自然也能避免多个实例同时发起请求互相干扰。3.2 请求构造头部、超时、连接复用请求本身的构造有几个实操点值得单独拎出来。首先是超时要设而且要设成短超时比如连接超时 5 秒、读取超时 10 秒。不设超时的后果是某个请求卡住之后整个循环被阻塞后面所有轮询全部停摆而你在界面上看不出任何异常——这是最隐蔽的失效方式之一。其次是条件请求。如果目标接口支持 ETag 或 Last-Modified一定要用上If-None-Match和If-Modified-Since。服务端返回 304 的时候你连响应体都不用解析既省流量又省时间对双方都友好。import time import requests HEADERS { User-Agent: Mozilla/5.0 (compatible; PersonalMonitor/1.0), Accept: application/json, text/html;q0.9, } def fetch(url, etagNone, timeout(5, 10)): headers dict(HEADERS) if etag: headers[If-None-Match] etag resp requests.get(url, headersheaders, timeouttimeout) if resp.status_code 304: return None, etag resp.raise_for_status() return resp.json(), resp.headers.get(ETag)注意 User-Agent 里我明确写了这是个个人监控脚本并留了版本号这既是一种自我标识也方便对方在需要时识别流量来源。不要伪装成浏览器去规避识别那是另一回事也容易把简单问题复杂化。3.3 失败处理状态码分类与退避策略失败处理最忌讳“一刀切全重试”。不同的失败原因要区别对待我的处理策略整理成了下面这张表状态码/异常含义处理方式200正常解析并判定304内容未变跳过判定记录心跳429请求过快立即把间隔翻倍冷却至少 10 分钟403被拒绝访问停止轮询发出自身告警人工检查5xx服务端异常指数退避重试最多 3 次连接超时网络抖动记录等下一轮不立即重试解析失败页面结构变了立刻发出自身告警这是最需要人工介入的退避我采用指数加抖动的方式第 n 次连续失败后的等待时间是base * 2^n再乘一个 0.8 到 1.2 的随机系数base取轮询间隔本身。连续失败超过五次就暂停轮询转入告警状态避免在一个已经不可用的目标上死磕。提示429 和 403 的处理方式必须区分对待。429 是“慢一点”403 是“别来了”。把 403 当成 429 处理并继续重试只会让情况变得更糟。4. 解析与去重把“看起来变了”变成“确实变了”这一段是很多人做完工具之后最不满意的地方。脚本跑通了通知也能收到但一周之后就开始嫌它吵。问题几乎都出在解析和去重这两个环节上——判定太粗、去重太弱。4.1 优先找结构化数据HTML 只做兜底前面提过优先找接口这里补充一下找不到接口时的做法。我的经验是先看页面里有没有内嵌的 JSON比如某些页面会把初始数据塞在一个 script 标签里用正则把它抠出来json.loads一下就能用这比解析 DOM 可靠得多。如果只能解析 HTML用解析库而不是正则。我见过太多用正则匹配列表的脚本在页面上多了一个空格或者换行之后就再也匹配不到了。用BeautifulSoup或lxml按属性定位节点取文本的时候统一做strip()和空白字符归一化能挡掉大部分无意义的变化。from bs4 import BeautifulSoup def parse_html(html): soup BeautifulSoup(html, html.parser) items [] for row in soup.select(tr[data-slot-id]): cells [c.get_text(stripTrue) for c in row.select(td)] if len(cells) 4: continue items.append({ date: cells[0], location: cells[1], type: cells[2], status: cells[3], }) if not items: raise ValueError(解析结果为空页面结构可能已变化) return items最后那句raise ValueError是整个函数里最重要的一行。解析结果为空是页面改版最典型的信号如果它默默返回一个空列表判定层会以为“所有名额都消失了”你要么被一堆错误的消失提醒淹没要么在无提示的状态下一直收不到消息。空结果必须当成异常处理不能当成正常结果。4.2 指纹与差集判断新增、消失、改期判定的时候我用了两层机制。第一层是整体指纹用第 2 节那个fingerprint()函数算一次如果和上次相同直接跳过不进入逐条比较。这一层挡掉了 90% 以上的轮询绝大部分时间页面根本没变化没必要做详细比对。第二层是逐条差集只在整体指纹变化时执行产出新增、消失、状态变化三个列表。两层机制的好处是计算量小而且日志里一眼能看出“这一轮是真变了还是假变了”——我会在每轮日志里记下指纹的前八位事后回溯的时候非常方便。这里有个坑要提醒日期的格式化方式会变。有的页面在名额充足时显示“2025-06-01”紧张时显示“2025年6月1日”同一个日期两种写法你的指纹就会认为变了两条完全不同的记录。解决办法是在归一化阶段就统一格式用正则把所有日期写法统一成 ISO 格式再做比较。4.3 冷却窗口与合并通知冷却窗口解决的是“同一个变化被连续捕获”的问题。假设某一轮抓到了新增名额下一轮这个名额还在、又新增了一个如果不做冷却你会连着收到两条消息其中第一条的信息已经过时了。我的做法是任何一次推送之后进入 5 分钟的冷却期冷却期内产生的新变化不立即推送而是累积到冷却结束时合并成一条“过去 5 分钟新增 3 个名额”的消息。这个策略在名额密集回流的时候特别有用我实测过一个晚上连续回流十几个名额的情况合并之后只发了两条消息信息完全没丢。另外一个细节是消息里的时间戳必须用目标时区而不是你本机的时区。这个在下一节的踩坑部分会专门讲因为我自己在这上面吃过不小的亏。5. 提醒通道怎么选让消息出现在你一定会看的地方通知通道的选择标准只有一个你多久看一次它。选一个你一小时才打开一次的应用前面所有的实时性优化都白做了。我把常见的四种通道放在一起对比过结论是最好不要只用一种。5.1 四种常见通道的对比通道到达速度可靠性配置成本适合场景邮件慢可能进垃圾箱中极低每日汇总、留档IM 机器人 Webhook快中高低主力通道手机系统推送快取决于平台中夜间强提醒短信快高有成本关键变化兜底我的配置是主通道走 IM 机器人 Webhook辅通道走邮件。Webhook 的好处是配置简单、消息能直接进到我已经常驻的应用里邮件作为辅通道的意义在于留档和交叉验证——如果哪天 Webhook 挂了我还能从邮件里发现漏掉的通知。这两个通道同时失效的概率很低。实际配置 Webhook 的时候注意几个细节。一是请求体格式大多数机器人要求特定的 JSON 结构字段名写错会返回错误但 HTTP 状态码可能还是 200所以发送之后要看响应体里的状态字段不能只看状态码。二是超时和重试通知发送失败不要重试太多次否则消息延迟会累积设成最多重试两次、间隔五秒就够了。5.2 消息正文的排版三行之内说清楚通知的正文比通道更重要。我在收到过大量冗长通知之后总结出一条第一行必须是结论后面才是细节。因为你在锁屏界面看到的就是第一行。我的消息模板长这样【发现新增名额】共 2 条最早 6 月 3 日 - 6/03 上午 某地点 A类 - 6/07 下午 某地点 B类 抓取时间 2025-06-01 07:12目标时区第一行给数量和最早日期中间给明细最后一行给抓取时间。最后一行的时间戳非常关键它让你能判断这条消息是刚刚产生的还是延迟送达的。还有一点经验不要把所有字段都塞进通知里。我第一版把每个名额的所有属性都列出来一条消息十几行结果在手机上根本看不清。后来精简到只保留日期、时段、地点、类型四个字段阅读效率提升非常明显。完整信息都写在日志里了需要细节的时候去翻日志就行。6. 让它能连续跑三个月日志、心跳与自愈这类工具有一个共同特点写出来只用一天让它长期可靠运行要花一周。绝大多数人失败在这后一步——脚本跑了两周某天悄无声息地停了而你完全不知道还以为这段时间一直没有变化。这一节讲的就是怎么让工具自己告诉你“我还活着”。6.1 日志该记什么不该记什么日志的第一原则是结构化用 JSON 行格式而不是自由文本这样事后可以用命令行工具直接筛。我每一轮轮询记录一条字段包括时间戳、轮询序号、HTTP 状态码、耗时、这次抓到的条目数量、整体指纹前八位、是否有推送、推送通道的返回状态。不该记的东西同样重要不要记录完整的响应体。一方面日志会迅速膨胀到几百兆另一方面响应里可能包含一些不应该长期留存的内容。我的做法是只记录条目的数量和指纹需要复查的时候临时打开调试开关再跑一轮。日志轮转也要配好按天切分、保留 14 天、超过 200MB 强制切。这个用一个现成的轮转工具就能解决不用自己写。6.2 心跳与死信告警心跳机制解决的是“工具死了但你不知道”的问题。我的实现很简单每一轮成功轮询之后往一个本地文件里写当前时间另外有一个独立的小任务每 30 分钟检查一次这个文件如果里面的时间已经超过 45 分钟没有更新就通过另一条通道发一条“监控已停止”的告警。这里的关键是告警要走不同的路径。如果监控脚本自己挂了它不可能发出告警所以检查任务必须独立于监控主循环而告警通道最好也和主通道不同比如主通道是 Webhook那这条告警就走邮件。同一路径上的自我检测是不可靠的这一点我在踩过两次坑之后才真正记住。6.3 部署与开机自启部署我建议用最朴素的方式一个进程配一个进程管理工具做自动重启配一个开机自启。不要一上来就上容器编排那会引入一堆与业务无关的复杂度。进程管理工具要配三件事崩溃自动重启、重启间隔限制避免疯狂重启循环、日志重定向。重启间隔限制特别容易被忽略我遇到过一次解析异常导致进程每秒崩一次没有限制的话日志文件一小时就写满了。开机自启也要验证一遍方法是真的重启一次机器而不是只在命令行里模拟。这一步我吃过亏——某些自启配置依赖交互式登录会话重启之后根本没生效而我在之后的两天里一直以为工具在跑。提示部署完成之后给自己设一个三天的提醒三天后回去检查日志的连续性。日志里如果有超过两倍轮询间隔的空洞说明中间发生过重启或阻塞需要进一步查原因。7. 七个真实踩过的坑下面这些坑我都亲自踩过其中三个是静默失效——工具在跑日志也有输出但发出来的消息是错的或者干脆没发。这类问题最伤时间因为你不容易意识到它存在。7.1 页面改版导致选择器静默失效最典型的一次目标页面调整了表格结构把原来的td改成了嵌套的 div我的选择器返回空列表。由于当时我在解析函数里写的是“返回空列表”判定层认为所有名额都消失了于是连续发了六条“名额消失”的错误通知。后来才发现根本没消失是我自己解析错了。修复方案就是前面说的空结果抛异常同时加一条规则——连续两轮解析为空、且之前非空判定为异常而不是真实变化只发自身告警不发业务通知。这条规则之后类似问题再没造成误导。7.2 时区与夏令时把时间判断搞反这个坑害我错过了一个名额。目标页面用的是当地时区我在判定“这个名额是否在我的可接受时间范围内”时用的是本机时区的时间。平时差几个小时只是让我少收到几条提醒但那次刚好碰上夏令时切换差值多了一个小时一个本来符合条件的名额被我判成了不符合直接过滤掉了。修复方案很直接所有时间比较统一转换成绝对时间戳只在最终生成通知文案的时候才转换回目标时区的可读格式并且在消息里明确标注是哪个时区。所有涉及时区的地方都在配置里写成一个显式的常量不要依赖系统默认值。7.3 会话过期和验证码在只读公开信息的前提下本来不应该涉及会话。但有些页面即便是公开信息也需要一个临时的会话标识这个标识会在几小时后失效失效之后返回的页面结构和正常页面完全不同于是触发解析异常。我的处理原则是一旦遇到需要人工验证的页面就停下来不要试图绕过。工具在这种情况下应该发出一个明确的通知告诉使用者“需要手动处理一次”然后暂停轮询。任何试图自动化处理验证环节的做法既不可靠也不合适。7.4 通知通道自己挂了有次 Webhook 的令牌过期了接口返回 401而我的代码只检查了网络层异常没检查业务层返回码。结果那两天所有通知都石沉大海日志里一切正常。修复之后通知发送函数会在两种情况下报警网络异常以及响应体里表示失败的字段。而且每天会做一次“通知通道自检”主动发一条测试消息确认通道可用。这个自检看起来多此一举但它是唯一能提前发现问题的手段。7.5 空闲时段的误判与假变化页面在夜间会显示“暂无数据”的占位行我早期把它当成一个正常的条目解析了进去。于是每天凌晨我都能收到一条“新增暂无数据”的通知。解决办法是在归一化阶段过滤掉明显的占位文案维护一个小的排除词典。7.6 并发实例导致的重复推送我在两个地方各跑了一份想着双保险。结果是每个变化都被推送了两次而且由于两份的轮询相位不同有时候一个先推送、一个后推送看起来像两次不同的变化。后来改成只跑一份另外用一个独立的心跳检查来保证可用性。7.7 依赖库升级后的行为变化某次例行升级之后HTTP 客户端对某些响应头的处理方式变了导致 ETag 缓存逻辑失效每一轮都返回 200 全量数据。这个问题的表现不是报错而是请求量悄悄涨了三倍。教训是依赖版本要锁定升级时先在一份旁路日志上跑两天比对行为是否一致再切换主力。8. 边界感这个工具该做和不该做的事写到这里技术部分基本讲完了但有一件事必须单独拿出来说因为它决定了这个工具能不能长期用下去。8.1 只读、限速、不绕过这个工具的定位非常清楚它是一个只读的观察者不是参与者。它只读取公开可见的信息不做任何提交、预约、占位之类的动作。一旦跨过这条线工具的性质就变了面临的不只是技术风险。限速是另一条必须守住的线。我给自己定的规矩是单次轮询只发一个请求间隔不低于两分钟遇到 429 立刻退避。这些数字不是为了性能调优而是为了让这个工具对目标系统的影响尽可能小。一个合理的自我约束是假设所有人都用和你一样的节奏访问这个系统还能不能正常服务如果答案是否定的那就把节奏放慢。不绕过任何访问控制同样重要。遇到需要人工验证的环节就停下来遇到 403 就停止轮询并告警遇到结构变化就人工确认。把这些处理成“明确的失败”而不是“想办法绕过去”工具会更简单也更让人放心。8.2 个人信息与凭据的最小化工具里不需要存任何账号密码不需要存任何身份信息这一点在架构设计阶段就要定死。如果你发现自己的实现里出现了“登录”“令牌”“凭据”这类东西先停下来想想是不是走错了方向。我的配置里只有一个目标地址、一个轮询间隔、一个通知 Webhook 地址其余什么都没有。日志和快照文件的留存也要克制。快照只需要保留最近两份上一份和当前一份用来做差集就够了更早的没有保留价值留着反而增加泄露面。通知内容里也不要带任何可以定位到个人的信息。说实话这套东西从第一行代码到我觉得“可以放心不管它了”前后改了大概六稿。前两稿都在追求“更快的发现速度”第三稿开始才转向“更少的误报”到第五稿才把心跳和自检补上。如果让我重新做一遍我会把顺序反过来先花半天时间把日志和告警搭好再写抓取逻辑。因为一个你不信任的工具你最终还是会回去手工刷新那前面所有的工作就都白做了。最后分享一个小技巧工具上线之后先别急着关掉手工刷新的习惯让它并行跑上一周每天对比一下“工具告诉我的”和“我自己看到的”把差异记下来。这一周里你会找出绝大部分的判定规则问题而且这些问题只有在真实数据上才会暴露出来。等连续三天没有差异你就可以放心把那个页面从收藏夹里移走了。
阅读完成 · 觉得有帮助?