retry-after:怎么请爬虫慢一点,以及 Google 到底认不认它
retry-after 是服务器请客户端「过一会儿再来」的响应头,跟着 429、503 或重定向一起发。本篇给出两种合法值格式、一张状态码对照表、一段最小服务端配置,以及 Google 抓取文档留下的那个边界——它点名了 500/503/429,却从没说会读 retry-after。

retry-after 是服务器对客户端说「现在别来,过一会儿再来」的那个响应头。它跟着 429、503 或一次重定向一起发出,值要么是一个秒数,要么是一个 HTTP 日期。对爬虫来说,这是「慢一点」和「先退开」之间的区别,也是 Google 自己的抓取文档里点了名、却从没承诺会读的那一个限速信号。
retry-after 到底管什么、不管什么
它是一个挂在响应上的排期提示,不是命令。RFC 9110 的定义只有一句:「服务器发出 Retry-After 头,用来告诉客户端在发下一个请求之前应当等多久。」「应当」这两个字是有分量的:礼貌的爬虫会照做,协议里没有任何东西能强迫一个爬虫照做。
只有两种响应场景真正用得上它。跟在 503(服务不可用)后面时,它说明这个服务预计还要多久才能恢复;跟在任何 3xx 重定向后面时,它给出客户端跟随 Location 之前至少要等的时间。两种场景语法一样。
它是请求对方等一等,不是逼对方等一等。
对做站的人来说,它的用武之地很窄也很具体:上线发布、数据库扛不住、计划内维护的这十几分钟里,源站没法正常服务,你可以返回一个错误码,再用 retry-after 请爬虫晚点回来,而不是在同一个主机上反复敲。你不能拿它给整站的抓取速率设上限——那是另一种控制,这一章只讲这个逐响应发出的头。
这里有一个必须写在最前面的边界,它决定了后面怎么读:Google 的抓取文档点名了 500、503、429 这三个码会降低抓取速率,但它没有写 Googlebot 会读 retry-after。我们在 2026 年 10 月 5 日核过那一页,整个页面里搜不到这个头。我们也没有拿一个活着的 retry-after 去做过抓取实验,所以这一章教你的是语法和取舍,不是「Google 一定会这样反应」。
Google 文档列的是哪几个状态码会减速,没有列 retry-after —— 这个空白要自己知道。
它还有个常见的混淆对象:robots.txt 里的 Crawl-delay。两者解决的不是一回事。Crawl-delay 是长期请求一个固定间隔、作用于整个 UA,而 Google 从来就不支持它;retry-after 是对一次失败请求的回答,说的是「就这一刻不行」,不是「永远慢点」。要全天候压住一个乱抓的爬虫,retry-after 是错的工具;要在一次十分钟的发布里活下来,它是对的。
还一个理由让它该被用窄:错误响应本就不该被缓存。把一个 retry-after 挂到可缓存的 200 上,等于请共享缓存记下一个早就过期的等待时间。只把它发在真正临时的那些响应上,并且确认你的 CDN 会把响应头透传下去、而不是在边缘把它剥掉。一个在源站上一层就死掉的头,从爬虫那边看,和从没发过没有区别。
再往后一步看,这个头真正值钱的地方不在「发出去」,而在「发得准」。你要先知道正常时候的抓取长什么样,才知道一次 503 到底改变了什么——否则你只是往日志里丢了一个看不出后果的字段。这也是为什么这一章把它排在「抓取预算」和「读日志」之后来讲:它是那条链最后一个动作,不是第一个。
按这个顺序做
四步,每一步都有一个能核的完成标志。顺序重要,是因为状态码承载了大部分含义,retry-after 只是在它后面补一句细节。
- 先定状态码。判断这是临时容量问题(
503)、你自己在限流(429),还是重定向(3xx)。能用一句话说清这个场景,这一步就算完成。 - 诚实地估等待时间。很短的已知窗口用秒数,某个确定的时刻用 HTTP 日期。这个值得是你真愿意让客户端遵守的,不是随手凑的整数。
- 把它挂到那条响应上。和状态码在同一条响应里发出——挂在
200上的这个头没有意义。手动发一次请求,两个都看得到,才算完成。 - 事后读自己的日志。确认客户端真的等了,而不是这个头只是摆设。能指出两条请求的间隔不小于你发出去的值,才算完成。
第四步是多数团队会跳过的。这个头设起来便宜、也最容易设错,只有日志能告诉你它到底改没改变什么。
交付物:状态码、值格式和一段配置
带走三样东西:哪些情况配哪个状态码、两种合法的值格式、一段最小的服务端代码。先用表选码,再写值。
| 场景 | 状态码 | retry-after 值 | Google 写明的效果 |
|---|---|---|---|
| 计划内维护,结束时间已知 | 503 | 秒数,如 120 | 5xx 会让抓取临时放慢 |
| 你自己在限制请求速率 | 429 | 秒数或日期 | 按服务器错误处理,会放慢抓取 |
| 临时容量问题 | 500 | 秒数 | 放慢抓取,程度与出错 URL 数量成正比 |
| 暂时不该被跟随的重定向 | 3xx | 秒数或日期 | 不是限速信号,只给最小跟随延迟 |
| 页面永久没了 | 404 / 410 | 不要加 | 对抓取速率无影响;429 以外的 4xx 不减速 |
值只有两种合法写法,混用是最常见的错。秒数是一个非负整数:Retry-After: 120 就是两分钟。HTTP 日期是一个绝对时间戳:Retry-After: Fri, 31 Dec 2026 23:59:59 GMT。RFC 9110 两种都允许,但不允许相对说法、不带单位、也不允许小数。
// Express:发布窗口内请爬虫等两分钟
app.use((req, res, next) => {
if (maintenanceWindow) {
res.set('Retry-After', '120');
return res.status(503).send('Temporarily unavailable');
}
next();
});
这段代码把头和状态码一起发出去,这是唯一正确的配对方式。一个没有 retry-after 的 503 仍然会让守规矩的爬虫放慢,头只是告诉它还等多久;一个没有合适状态码的 retry-after 则什么都不做。
做错了会怎样
我们见过的坏实现里,绝大多数是这三种,而且没有一种会在普通浏览器测试里露出来。
- 头挂错了响应。把 retry-after 当成全局头挂到每一条响应上,包括
200。客户端在那里会忽略它,而真正需要它的那条响应可能反而没带上。 - 值不是两种合法格式之一。
2 minutes或1.5这类值解析不了。写120,或者写完整的 HTTP 日期。 - 等待时间说了谎。五秒就恢复的
503配一个Retry-After: 3600,等于教会一个守规矩的客户端离开一小时。码和等待时间该描述同一件事。
要多少等待,就写多少等待——信你的客户端会照你说的做。
常见问题
Googlebot 到底认不认 retry-after?
Google 的抓取文档没说它认,我们也没测过,所以我们不能替它承诺。Google 写明的是:大量 500、503、429 会降低整个主机名的抓取速率。把 retry-after 当成那个信号的补充,而不是当成 Google 公开承诺过的控制开关。
限流该用 429 还是 503?
源站确实服务不了用 503,你在主动限制请求速率用 429。两者对 Google 来说都算服务器错误,所以怎么选其实是在对一个真人运维讲实话。别用 403 或 404 来限流——Google 的状态码页写得很直接:429 以外的 4xx 对抓取速率没有任何影响。
等待时间写多长合适?
短到场景真正允许的最短,计划内故障最多到一天左右。Google 警告说,错误响应持续超过一到两天,URL 可能被移出索引,所以「把 retry-after 写很长」并不能替你把页面恢复这件事扛过去。断得比一两天还久,该修的是源站,不是这个头。
怎么确认爬虫照做了?
找同一个 user agent 发来的两条请求,间隔不小于你发出去的值。方法在 用服务器日志看谁真的来过那一篇里;这个头的效果在浏览器里是看不见的。
会不会因为这个头被踢出索引?
短时间不会。Google 的警告针对的是错误响应持续多天,不是这个头本身:「如果 Googlebot 连续多天在同一个 URL 上看到这些状态码,那个 URL 可能被移出索引。」头只告诉爬虫什么时候回来,真正被 Google 计数的是状态码。窗口压短、值写得诚实,两者就不会打架。
走 CDN 还生效吗?
只有当 CDN 把这个头透传下去才生效。多数边缘默认会转发不认识的响应头,但缓存规则和头改写规则可能把它删掉。用一次直连源站的请求和一次走边缘的请求对比一下。如果边缘自己加了一套过载重试逻辑(有几家确实这么做),源站写的值可能根本到不了爬虫。
最后把边界再说一遍:这个头改变的是礼貌客户端下一次请求的排期,别的什么也改变不了。它修不了一个慢源站,也挡不住一个选择无视它的爬虫。如果你的问题其实是「抓取量到底算不算我的问题」,先从 抓取预算是不是你的问题看起;而要把你服务出来的这些抓取最终变成能被收录、能被搜索词命中的页面,那正是 QueryWin 在做的事。
本文属于 QueryWin 实操手册 · 第 3 阶


