一個 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 就提高抓取频率,也不會因此降低。
怎么自己驗證
- 用 curl -I 請求一次頁面,记下 Last-Modified 和 ETag。
- 带上這两個值再請求一次,看狀態碼是不是 304,响應体是不是空的。
- 隔几分钟重复一次,確認這两個值没有無端變化。
- 如果有多個後端节点,分別請求,確認 ETag 一致。
- 修改頁面内容後立即請求,確認驗證信息随之更新,狀態碼回到 200。
這几步做完,基本能判断站点在重复抓取时的表現。它不能直接决定收錄,但能让抓取過程更干净:蜘蛛把時間花在真正有變化的頁面上,站点也不必為没變的内容反复承担完整响應。