蜘蛛再次訪問一個已经抓過的 URL 时,通常會带上 If-None-Match 或 If-Modified-Since。如果服務器能正确回應 304,蜘蛛就知道這個地址没有實质變化,不必重新下载正文;如果每次都被回以 200 和完整頁面,同一份内容就會被反复拉取。單個 URL 看不出差別,但当站点存在大量列表頁、归档頁、标簽頁时,這類重复下载會持續占用抓取額度,也让日誌里的字节數虚高。
條件請求靠什么判定
服務端判断内容有没有變化,主要依赖两组头部:ETag 與 If-None-Match 配對,Last-Modified 與 If-Modified-Since 配對。任意一组匹配,就可以返回 304。問题往往出在两组头各自漂移,结果谁都匹配不上。
- Last-Modified 取了動態時間:有些框架預設把响應生成時間寫進 Last-Modified,每次請求都不同,條件判断必然失敗。
- ETag 里带了不稳定字段:inode、進程号、部署序号、随机串參與計算时,多台机器算出来的值不一致。
- 强、弱 ETag 混用:一端返回带 W/ 前缀的弱校驗值,另一端返回强校驗值,比較規則不同,容易被判為已變更。
- CDN 在邊缘重寫:源站的头正常,但缓存层重新生成了 ETag,回源與邊缘两套值来回切換。
- 多机房不同步:负载均衡把同一 URL 分到不同节点,各节点返回的校驗值互不相同。
几個容易被当成正常的現象
第一,响應头里寫了 Cache-Control: no-store,却又期望蜘蛛命中 304。這两個意图本身冲突,中間层通常不保留副本,條件請求也就無從比較。
第二,用 200 加空响應体代替 304。狀態碼看起来正常,但蜘蛛仍完成了一次完整的下载流程,节省不了带宽,解析环节還容易拿到空正文。
第三,頁面只改了一個無關紧要的模块,例如推荐位顺序變化,Last-Modified 就整体更新,蜘蛛被反复叫回来,而真正變化的内容並不多。
排查步骤
- 先用命令行带條件头請求一次:取出首次响應的 ETag 與 Last-Modified,再带上 If-None-Match 或 If-Modified-Since 重發,看能否稳定返回 304。
- 连續請求同一 URL 多次,比對两次的响應头是否完全一致;不一致就先定位是哪一层在改寫。
- 在多台源站上分別請求,確認所有节点返回同一组值。若不一致,大概率是校驗算法里掺了机器相關字段。
- 翻抓取日誌里同一 URL 的响應字节數,如果長期接近頁面完整大小且狀態碼多為 200,說明 304 基本没生效。
- 绕過 CDN 直接回源再测一次,区分問题出在源站還是邊缘节点。
調整方向
核心思路是让同一份内容在任意時間、任意节点上都给出稳定且一致的头。
- ETag 建议只基于内容摘要計算,或干脆去掉自定义值,交给 Last-Modified 负责。
- Last-Modified 使用内容真實的最後修改時間,不要用渲染時間。
- 多机部署时统一算法,避免文件名、路径大小寫差异參與計算。
- 缓存层設定為透传這两個头,不要自行重寫。
- 確認没有變化就用 304 回應,不要用 200 空体兜底。
观察與驗證
調整之後不要只看一次請求的结果。建议连續观察几天:看同一 URL 重复下载的次數是否下降,响應字节數分布是否向小体积偏移,日誌里 304 的比例是否趋于稳定。資料有起伏是正常的,重要的是趋势,而不是某一天的數字。
條件請求不是抓取優化的開關,它只是把重复下载的成本降下来。真正决定抓取效率的,仍然是入口是否清晰、内容是否有稳定更新,以及服務器能否及时响應。