半夜四个报警,两个数据库争用、一个读副本失败、一批 metrics 神秘空白。我先翻部署日志和 diff,翻了几遍啥也没解释。哎。prod-api 和 prod-web 在同一分钟从 3.6K 涨到 17-18K。这哪是部署,这是外部流量变了。
这头牛,我今天非钻穿不可。到底什么流量。
先按 script/hour 拉了个表:
script_name hour invocations
prod-api 00:00Z 3,614
prod-web 00:00Z 3,597
prod-api 01:00Z 18,492
prod-web 01:00Z 17,238
两个 worker 同刻×5。D1 主库单线程,读数从 15-28K/hr 涨到 37-46K/hr,超过那 36K 天花板。下游队列堵了,拒绝、副本错误、空 metrics 全是它。三个看起来互不相干的症状,其实是同一个下游卡住之后的三种投影。
我一开始还心存侥幸,猜是不是新上的功能埋了雷。结果 50 秒 live tail 抓 187 个请求,一眼就精神了:
prod-web · live tail · 50s · 187 requests
155 requests:
ASN: one hosting provider
IPs: at least 8 addresses
Geo: single country
UA: Chrome/121 · Chrome/122 · Chrome/123 · Chrome/124 · Chrome/125 · macOS build
Path: /jobs/{id} × 152, /companies/{slug} × 29
Asset requests: 0
robots.txt: 0
CSS/JS/font/favicon: 0
155/187 来自一个托管商 VPS 段,至少八个地址,同一个国家。UA 转了五个 Chrome 版本,再加个 macOS 版,没一个 bot UA。你说气不气,装得挺像人。可下一页日志就露馅了:零 CSS、零 JS chunk、零字体、零 favicon——一个真实浏览器不可能不拉这些东西。robots.txt?人家从头到尾没要过,你加不加都一样。它只打 /jobs/{id} 和 /companies/{slug},按 ID 顺序,每秒四页,节奏稳得跟节拍器似的,像一个不用眼睛的客户端在翻目录。
这个破绽,是我见过的爬虫里最没法遮的。UA 能轮换,IP 能买,可不请求静态资源的客户端……要么它压根不渲染,要么它就根本是个脚本。日志里这信号现成的、白捡的。伪造它?得跑一整个 headless 浏览器把每页都 load 一遍,你猜它们舍不舍得花这个成本?舍不得。我自己做 job 爬取的时候,恰好认得这种只拉 HTML、忽略 CSS 的客户端——省钱版爬虫,就这点格局。
顺带说一嘴,盲扫器也这毛病:零静态资源。区别是盲扫器 404 一大堆,因为它乱猜 .env、phpinfo、.git/config;定向爬虫 404 极少,人家只走真实存在的 URL,一步一步扒。这表我顺手列了:
| 流量 | 404 率 | 静态资源 | 请求特征 |
|---|---|---|---|
| 真实用户 | 低 | 正常 | HTML + CSS + JS + font + favicon,顺序合理 |
| 盲扫器 | 极高 | 零 | 乱猜路径,扫完就走 |
| 定向爬虫 | 极低 | 零 | 按 URL 族有序遍历,速度稳定 |
看到了吧,零静态资源是这两条枝的共同根。有个做静态站的朋友发我看他的 DDoS 面板:458 个放行、2.0k 个阻止、3.6k 个挑战。放行的那 458 个里,相当一部分是 AWS 来探 .env、phpinfo、.git 的,一个样式表都没拉。
看到这种流量,第一反应是加缓存——长 TTL、边缘缓存、stale-while-revalidate。我一开始也往这想。推一把就发现没戏。这俩 URL 族里几十万个不同 ID,每个请求都是首访,第二次永远不会来。缓存白等,除了多一跳没别的用。你不可能缓存一个从不重放的东西。
所以最后只能限流。但不按 UA 限——UA 是攻击者换着玩的,你今天封 Chrome/124,它明天上 Chrome/126。按 ASN 或 IP 前缀限。网络路径它改不掉,这才是它的真身份。后来跟一个做反爬的朋友捋了个更细的方案:先拿资产请求比例做免费预筛——每个 IP 的静态资源占比短窗口里不达标的先限;真拿不准的再花钱查信誉源,两个独立源都说是机房 IP 才判死;然后 /24 块按邻居中已标比例分级,低于 10% 只限单 IP,过 30% 整块限。每天重算一回就够,粗带宽,小漂移翻不了天。
还有个闷亏:verified-bot 字段,我以为能直接查,结果在我们这个 plan 上是 enterprise 才有的。早知道就省二十分钟——我卡在那翻订阅页面,不是翻代码。
还有两个工具教训想写下来。算了,今天先到这,下次再补。
最后,扯个跟这事半毛钱不着的——半夜盯着 live tail 看,突然想到以前抓包抓到过一个只拉 HTML 的省钱客户端,当时觉得这行为挺聪明。现在站防守这边,发现省钱的另一面就是好认。其实哪有什么藏得好,只有懒得看。嘿嘿,下次见。
