蜘蛛第一次抓走一個 URL 之後,並不代表它不會再来。真正的成本大头往往在後面的每一次回訪:頁面没變,蜘蛛還是要發一次請求,服務器還是要给一次响應。如果服務器能在响應里明确告诉蜘蛛「自上次以来没變」,這次回訪的成本可以降到很低。這個机制就是條件請求。
條件請求:把「有没有變」交给服務器判断
蜘蛛回訪时,除了常規的請求头,可能還會带上两個額外的头:
- If-Modified-Since:值来自上次响應里的 Last-Modified。意思是「如果這個時間之後没改過,就別再传内容了」。
- If-None-Match:值来自上次响應里的 ETag。意思是「如果這個标识還是上次那個,就別再传内容了」。
服務器比對通過,就返回 304 Not Modified,不带正文;比對不通過,就正常返回 200 和完整頁面。對蜘蛛来说,前者意味着几乎不用下载正文,就能確認内容没有變化。
200 和 304 的差別具体在哪
有一点需要说清楚:304 通常仍然會被算作一次抓取請求。蜘蛛還是连了服務器、還是等了响應,只是省掉了正文传輸。所以它的價值主要在三個方面:
- 减少带宽和传輸時間,尤其是体积大的 HTML 頁面;
- 让服務器更快返回,單個连接的占用時間變短;
- 在抓取节奏受限时,快速完成一次「確認」,把時間留给真正需要下载的頁面。
換句话说,304 不會让蜘蛛多来,但能让它来得更省。
哪些寫法會让條件請求失效
實际站点里,條件請求经常被一些不经意的實現破坏掉,表現就是:蜘蛛每次回訪都拿到完整的 200。
1. Last-Modified 每次都變
有些 CMS 在每次渲染时把 Last-Modified 寫成「目前時間」,或者寫成資料库查询時間。這样一来,蜘蛛带上 If-Modified-Since 去比對,永遠比不過,服務器只能老老實實重传一遍。更合适的做法是取這篇文章或這個资源的真實最後修改時間。
2. ETag 里带了不稳定因素
如果 ETag 由内容哈希生成,一般没問题。但如果它掺進了進程 ID、請求 ID、渲染耗时、随机數,同一個 URL 每次算出来的 ETag 都不一样,If-None-Match 就永遠匹配不上。多台服務器之間 ETag 規則不一致,也會出現同样的结果。
3. 压缩與代理改變了實体
同一個頁面,经過 gzip 和未经過 gzip,實体内容不同,ETag 也可能不同。部分代理會给压缩後的响應加上 W/ 前缀(弱校驗),這是正常的,但要保證同一份内容在不同节点上算出的标识一致。CDN 节点與源站各算一套 ETag,是最容易出問题的地方。
4. Cache-Control 與條件請求互相打架
Cache-Control 里的 no-store、no-cache 這類指令,本意是控制缓存,但如果整套鏈路都不做校驗,回訪就全是完整响應。這不一定算错,只是要清楚代價:頁面越多、回訪越频繁,重复传輸的体量就越大。
怎么自己驗證
- 用 curl 拿一次响應头,记下 Last-Modified 和 ETag;
- 再發一次請求,带上 If-Modified-Since 或 If-None-Match,看返回碼是不是 304;
- 對同一個 URL 隔几分钟重复几次,看 ETag 是否稳定;
- 在訪問日誌里統計蜘蛛請求中 304 的比例,比例長期接近零,通常說明校驗没生效。
和 Sitemap 里的 lastmod 怎么配合
Sitemap 的 lastmod 是给蜘蛛的「可能有更新」信号,條件請求是蜘蛛来了之後的「實际有没有變」的確認。两者規則要一致:lastmod 标了更新時間,回訪时 Last-Modified 却给另一個值,蜘蛛會反复来確認却總拿到全量内容,白白消耗抓取。把這两個時間對齐,抓取节奏會更平稳。
條件請求不是提速的魔法,它只是把「没變」這件事说得更清楚一点。省下来的每一次正文传輸,都是留给其他頁面的抓取余量。