网页抓取与站点读取智能体。
让你的智能体被封的,几乎从来不是你的总请求量——而是对单一来源的并发数,以及你的读取里有多大比例是重复读;而这两样都是某个你还没写的库的性质。一个总速率看着很合理的智能体机群,照样能把一台小服务器打垮,因为真正要紧的速率是按来源算的,而智能体压根不知道自己有多少个同胞正在读同一个页面。先把取数层造出来:一个共享的、带再验证的缓存、一份智能体无权上调的按来源预算,以及一张「宁取批量 API、不取零售页面」的路由表。礼貌不住在提示词里。
先决定你到底要不要抓。
同一批数据通常有三种访问形态可选,而它们对双方的代价差了几个数量级。这一步选错了,下游无从修补。
- 一条批量通路。数据转储、数据馈送、每日快照、一套付费高吞吐 API。大站点越来越多地就卖这个,有些还公开要求 AI 公司用它来替代抓取。只要它存在,一个定时任务就能替掉整个爬虫,而本手册里的每一个问题都随之消失。
- 一套查询 API。限了速、有文档、要认证,而密钥背后有一个联系人。按调用付费,你同时买到的是一段关系:被封会以一封邮件的形式到来,而不是一个 403。
- 去取页面。兜底的那一种,也是唯一没有合约的那一种。下面的一切都是为了让它站得住脚。
请把这个选择按数据源逐一写下来,写进代码,做成一张取数层会去查的路由表——目标对应获授权的方式。一个在运行时挑 URL 的智能体,对「你的组织为哪条通路付了钱」没有任何表征,所以这个偏好必须在「请求被发出的那个地方」表达出来。这是智能体抓取里最常见的那个结构性失败:合约明明存在,而智能体走了零售那条路,因为零售那条路返回 200。
请在动架构之前、而不是之后,先看条款和 robots.txt。robots 是否适用于智能体流量尚有争议,但「无视它」会是一份投诉开篇摆出的那条事实;而「某个大模型是替一位用户去取的」,不是你想在一封邮件里第一次为之辩护的立场。
把取数层插在智能体与网络之间。
智能体不许持有 HTTP 客户端。给它一个工具,而该工具的实现拥有身份、缓存、预算、重试与日志——因为这里每一样都是你需要被强制执行、而不是被请求遵守的性质。
agent --fetch(url)--> fetch service
|- route table bulk feed? API? page?
|- cache lookup fresh / revalidate / miss
|- per-origin gate token bucket, 1 concurrent
|- identity UA + contact URL + run id
|- fetch timeout, size cap, redirect cap
|- log origin, time, run id, status
'- normalize HTML -> text + extracted links
有四条性质是从这个形状里、而不是从任何别的形状里掉出来的。智能体超不出一份它拿不到的预算。重试风暴变得不可能,因为那道闸在重试的下游。每一次请求都可归属到一次运行,而这正是让一份滥用申诉能在一小时内答得出来的东西。而缓存是跨整个机群共享的,这是你手里能拿到的最大一笔流量削减。
让缓存是共享的、持久的、带再验证的。
按运行划分的缓存几乎毫无价值。智能体抓取里的重复发生在跨运行与跨智能体之间:十次主题相邻的研究运行会打中同样那二十个权威页面,而一次重试会把第一次尝试读过的东西全部重读一遍。一个进程生命周期的缓存,这些一个都接不住。
- 以规范化后的 URL 做键。剥掉追踪参数、把重定向链解析一次并存下终点 URL,并在主机允许的地方把尾斜杠与大小写规范化。表面上的缓存未命中里,有一半是同一个页面的四种写法。
- 把验证器存下来并真的用它。保留
ETag与Last-Modified,并发送条件请求。一个 304 对源站几乎不花什么,让你在新鲜度上保持诚实,而它正是那个让「高重读率」变得可接受而非滥用的机制。 - 把取数缓存与抽取缓存分开。把原始字节与解析后的文本产物存在不同的键下,这样改进你的抽取器就不会把整个网重爬一遍。这正是那个会把一次解析器修复变成一次事故的错误。
- 按易变性来定寿命,而不是用一个 TTL。一个文档页、一个定价页与一个状态页,新鲜度要求能差三个数量级。一个全局 TTL 要么在端出过期答案、要么在重取静态内容;见索引新鲜度与失效。
请按来源测缓存命中率,并把低值当成你自己系统里的一个缺陷、而不是关于网络的一条事实。在一个成熟的研究机群上,多数取数应该是命中或 304,而命中是唯一一种对所有人都免费的读取。
按来源做预算,并默认一次只发一个请求。
总速率限额是错的单位,而它恰是每个人都会去造的那一个。每秒十个请求摊在一千个主机上是看不见的;而同样每秒十个对准一台小服务器,就是一次宕机。预算必须按可注册域名算,在整个机群上强制执行,并存放在智能体触达不到的地方。
- 默认每个来源一个并发请求。带一点短延迟的串行读取,对几乎每一项智能体任务都够用,而它就是「没人注意到」与「被呼起来处理」之间的差别。只在逐个主机上、有意识地、对你有关系的主机才往上调。
- 让预算是机群全局的。一个按进程的限额乘上五十个 worker,就不是限额。这需要一个共享计数器,而那是本手册真正要求的唯一一件基础设施。
- 按信号退避,而不是只按报错退避。带
Retry-After的 429 与 503 是显式的,但来自某一个来源的延迟上升是更早的信号,也是那个能让你在报错之前就停下的信号。请把延迟攀升当成一个软 429。 - 永远不要在满并发下重试一次超时。智能体抓取的经典宕机是一个慢源站加上一套重试策略:变慢会让在途请求成倍增加,而那又让它更慢。重试纪律在这里比在任何别处都更要紧,因为你正在压垮的那个东西不是你的。
- 给抓取本身设上限,不只给速率。每次运行、每个来源、每项任务的最大页数,加上一个深度上限与一个总字节上限。一个顺着链接走的智能体没有天然的停点,而它一定会找到一个带无限分页的日历。
标识自己,并让流量可被追溯。
匿名正是那个把一场容量投诉变成一次归因调查的东西,而那次调查对双方都是贵的那部分。
- 一个诚实的 user-agent,带联系 URL,并让它解析到一个说明「这是什么流量、怎么让它停下」的页面。靠轮换 user-agent 与住宅代理去规避封锁,会把你从「一个有缺陷的服务」挪到「一个在规避管控的行为方」,而那是要跟你客户的法务团队进行的另一种对话。
- 每一次请求都带一个运行标识,对接收方不透明,而你能解析。这正是让你能从一个时间戳与一个 IP 答出「哪一次运行、哪个租户、哪条提示词」的东西。
- 有获授权的身份机制的地方就用它。签名智能体一类的方案,让站点能以密码学方式核验这份流量是你的,从而把你放进「被允许」那一类、而不是「可疑」那一类。见机器人核验与智能体访问。
- 尊重封锁。当一个来源封了你,就停下,并把这个决定交给人。一个绕开 403 的智能体,是在替你做未授权访问,而取数层正是那个能把这件事拦住、而不是拿来讨论的地方。
取回来的页面是不可信输入,而一次抓取是你会运营的、量最大的注入面。智能体读到的一切都必须与它的指令隔离开,而取数层是天然的收口处:在那里剥掉脚本、把内容包进一个明确标记的信封、并拒绝让页面文本以不带标签的形态进入一个能调用工具的上下文。见提示词注入与浏览器智能体的失败模式。
去监控源站看见的那个东西。
你的看板量的是任务成功率与 token 开销。而源站量的是「来自你那些网段的每小时请求数」,而你栈里没有任何东西在算这个数——这正是为什么第一个注意到你抓取的人,通常是被抓的那个人。
- 一张图:按来源的每小时请求数,机群范围。降序排。从取数日志出发一个下午就能做出来,而它是唯一一道能把检测挪进你组织内部的控制。
- 对「上量的新来源」告警。一个你机群从未读过的主机,突然吃进几千个请求,那要么是一个没人评审过的新数据源、要么是一个在打转的智能体。两者都要人来看。
- 盯住按来源的 4xx 与 5xx 率。来自某一个主机的错误率在攀升,那是这个主机在自我防卫。请把它当成一个停止信号、而不是一个重试信号。
- 每月复核前二十个来源。任何一个你读得又重又频的主机,都是「升级到一条批量通路或一个 API 密钥」的候选——而那会把你最大的风险换成一份合约。
如果本手册你只交付一样东西,就交付那个取数服务:一个机群全局的按来源令牌桶、并发设为一,一个会发条件请求的共享缓存,以及每一次出站调用上的一个运行标识。这里其余的一切都是调参。这三样,会把一个「终将出现在某人事故报告里」的智能体机群,变成一个守规矩的客户端——而它们做到这件事的地方,是提示词永远触达不到的那一处。