搜索抓取

蜘蛛重复抓取同一个页面时,服务器该回 200 还是 304

蜘蛛会反复回访已经抓过的 URL,服务器可以用 Last-Modified 和 ETag 配合条件请求,在内容没变时返回 304。这篇文章讲清两个响应头各自的作用、常见的配置错误,以及怎么用命令行验证站点是否处理正确。

搜索抓取

蜘蛛重复抓取同一个页面时,服务器该回 200 还是 304

一个 URL 被收录之后,并不代表蜘蛛就不再来了。它会按自己的节奏回访,确认内容有没有变化、链接有没有更新、页面是否还在。每次回访都是一次完整的 HTTP 请求,如果服务器每次都把整份 HTML 重新发一遍,带宽和响应时间都会白白消耗。

蜘蛛为什么反复来

原因并不复杂。页面上的内容可能被编辑过,内链可能增删,页面可能被下线。蜘蛛无法预知这些事情,只能通过再次请求来判断。对内容更新频繁的站点,这种回访会更密集;对长期不变的页面,它也会隔一段时间确认一次,只是间隔会拉长。

这意味着服务器面对的不只是新访客,还有大量“内容没变”的重复请求。如果能在这一层做点优化,站点在高频抓取时的压力会小不少。

条件请求:让服务器只说“没变”

HTTP 协议早就为此准备了机制。蜘蛛第二次请求某个 URL 时,可以带上之前收到的验证信息,问服务器“这个版本还是最新的吗”。如果没变,服务器回一个不带正文的 304,蜘蛛就知道继续沿用上次的版本。

Last-Modified 与 If-Modified-Since

服务器在响应里给出 Last-Modified,表示内容最后修改的时间。蜘蛛下次请求时把它放进 If-Modified-Since,服务器比较这个时间与文件当前的修改时间:如果一致,返回 304。

这个头的精度是秒,对大多数内容页够用。但如果同一秒内多次改动,可能判断不出来。另外要注意,这个时间必须是稳定可信的文件时间,不能每次请求都取当前时间。

ETag 与 If-None-Match

ETag 是一个由服务器生成的标识,可以基于文件内容哈希,也可以基于 inode 加时间。蜘蛛下次请求时带上 If-None-Match,服务器比对当前的 ETag,一致就返回 304。

ETag 的精度更高,也能覆盖 Last-Modified 表达不了的情况,比如内容改了又改回来。两者可以同时使用,协议规定 ETag 优先。

几个容易踩的坑

  • 动态生成的 Last-Modified。有些框架每次请求都输出当前时间,蜘蛛每次都拿到一个新时间,条件请求永远不成立,304 也就无从谈起。
  • ETag 里带随机数或进程 ID。多台后端服务器生成的 ETag 不一致,请求被负载均衡打到另一台时,就会误判为内容已变,反复返回完整页面。
  • 返回 304 却带着正文。这种实现会让蜘蛛困惑,最好检查一下响应头与实际响应体确认。
  • 内容变了,验证信息却没更新。这种情况更麻烦,蜘蛛会一直以为页面还是旧版本,新内容迟迟不被识别。
  • 用 no-store 或者绕过缓存。部分 CDN 与反向代理的配置会强制每次回源,条件请求在链路上被丢掉。

静态页面与动态页面的差别

静态文件的处理比较直接,文件系统时间就是 Last-Modified,ETag 一般由 Nginx 等自动生成,基本不需要额外配置。

动态页面要靠程序控制。思路是:给每个页面记录一个真实的更新时间,写进 Last-Modified;ETag 用内容摘要或者版本号生成,而不是随机数。如果页面本身有缓存层,可以让缓存层统一负责这些响应头,避免前后端各说各话。

304 节省的是传输,不是抓取。蜘蛛仍然会发出这次请求,只是不再接收正文。它不会因为站点支持 304 就提高抓取频率,也不会因此降低。

怎么自己验证

  1. 用 curl -I 请求一次页面,记下 Last-Modified 和 ETag。
  2. 带上这两个值再请求一次,看状态码是不是 304,响应体是不是空的。
  3. 隔几分钟重复一次,确认这两个值没有无端变化。
  4. 如果有多个后端节点,分别请求,确认 ETag 一致。
  5. 修改页面内容后立即请求,确认验证信息随之更新,状态码回到 200。

这几步做完,基本能判断站点在重复抓取时的表现。它不能直接决定收录,但能让抓取过程更干净:蜘蛛把时间花在真正有变化的页面上,站点也不必为没变的内容反复承担完整响应。