抓取日誌里,同一個 URL 一周被訪問十几次並不罕见。除了站点本身更新频繁,另一個常被忽略的原因是:服務器没有告诉蜘蛛“這個頁面没變”。這时 304 狀態碼和配套的缓存头就有實际價值。
蜘蛛為什么會反复来
蜘蛛對已知 URL 有一套重新訪問計划,頁面權重、更新频率、站点整体抓取情况都會影响它回来的間隔。如果每次訪問服務器都返回完整頁面,那么一份没變化的 HTML 會被反复传輸和解析。對于模板頁、說明頁、分頁尾頁這類長期不動的 URL,這部分開销是白花的。
协商缓存就是解决這個問题的标准手段:让蜘蛛带着上次的标识来問一句“變了吗”,没變就只回一個狀態碼。
條件請求:蜘蛛怎么問“變了吗”
蜘蛛以前抓過某個 URL,再次訪問时通常會在請求头里带上 If-Modified-Since(值来自上次响應里的 Last-Modified)或 If-None-Match(值来自上次的 ETag)。服務器判断内容没變,就返回 304 Not Modified,不带响應体。
304 省下了什么,没省下什么
- 省下的是响應体传輸、带宽占用和服務端渲染成本,對资源有限的站点帮助明顯;
- 省不下的是這次請求本身。蜘蛛仍然發了一次 HTTP 請求,抓取配額的占用不會完全消失;
- 也不會带来新的 URL 發現。304 没有正文,蜘蛛不會從中解析出任何連結,新頁面還得靠内鏈和 Sitemap。
容易踩的几個坑
- ETag 不稳定:多台後端各自生成 ETag,蜘蛛带着 A 机器的值問到 B 机器,對不上就只能回 200,协商缓存形同虚设。文件 inode、時間戳參與計算时尤其容易出問题。
- CDN 或反向代理吞掉條件請求头:节点按自己的策略统一回 200,源站的 304 逻辑根本没被执行。
- Last-Modified 精度不足:只精确到秒、或者由静態化任務统一寫成生成時間,會導致判断失真。
- 内容更新了却仍然回 304:缓存規則寫死、或頁面由定时任務生成而判断逻辑没跟上。蜘蛛會繼續使用舊版本,新内容和頁面上新加的連結都會推迟被發現。
- 把 304 当成屏蔽手段:它只影响重复抓取,拦不住蜘蛛第一次訪問,也解决不了低质頁面被反复抓的問题。
排查顺序
- 用 curl -I 之類的工具手工带上 If-None-Match 或 If-Modified-Since,看返回的是 200 還是 304;
- 確認响應里是否同时存在 ETag 與 Last-Modified,只有其中一個也能协商,但双份更稳;
- 換一台後端再請求一次,比較两次的 ETag 是否一致;
- 回到抓取日誌,統計同一個 URL 的狀態碼分布,200 占比長期偏高,說明协商基本没生效。
如果頁面确實更新了,服務器却仍然回 304,蜘蛛看到的就是舊頁面。這属于内容同步問题,和抓取量無關,優先級要排在前面修。
和 URL 發現的關系
新 URL 的發現主要靠站内連結、Sitemap 和主動推送,304 在這件事上帮不上忙。它的定位是降低重复訪問的成本,让资源更多留给真正需要抓的頁面。所以優化顺序應该是:先把内鏈结构和 Sitemap 理顺,再回头看服務器响應里的细节。
另外注意,304 之後蜘蛛仍會更新它记錄的 Last-Modified 或 ETag,但頁面上的連結集合沿用上一次收到的正文。如果正文已经變了,新版連結要等下一次拿到 200 才會進入抓取队列。
實践建议
把 304 当成一次“降噪”,不要指望它改變抓取节奏的根本。對長期不變的頁面,让 ETag 保持一致、Last-Modified 反映真實修改時間;對频繁更新的頁面,宁可老老實實回 200,也不要為了省流量而返回错誤的 304。判断标准很简單:狀態碼要如實反映内容有没有變,而不是服務器想省多少带宽。