搜尋抓取

蜘蛛重复抓取同一個頁面时,服務器该回 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。

這几步做完,基本能判断站点在重复抓取时的表現。它不能直接决定收錄,但能让抓取過程更干净:蜘蛛把時間花在真正有變化的頁面上,站点也不必為没變的内容反复承担完整响應。