蜘蛛第一次抓走一个 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 却给另一个值,蜘蛛会反复来确认却总拿到全量内容,白白消耗抓取。把这两个时间对齐,抓取节奏会更平稳。
条件请求不是提速的魔法,它只是把「没变」这件事说得更清楚一点。省下来的每一次正文传输,都是留给其他页面的抓取余量。