为什么同一个 URL 会被反复抓取
蜘蛛不是抓一次就记住页面内容。为了确认页面有没有更新,它会在一段时间后再次请求同一批 URL。如果一个站点有几万个页面,蜘蛛每天来几轮,服务器就要把这批页面重复发送好几遍,而真正发生变化的可能只是一小部分,剩下的带宽和响应时间都花在了没变的内容上。
减少这种浪费的办法不止一种,条件请求是最容易被忽略、又最容易实现的一种。
条件请求是怎么工作的
蜘蛛第一次抓到页面时,服务器会在响应头里带上 Last-Modified 或 ETag。下次再来,蜘蛛会把这两个值分别放进请求头 If-Modified-Since 和 If-None-Match,相当于问一句:这个版本和上次一样吗?
Last-Modified 与 If-Modified-Since
服务器把请求头里的时间与文件当前的修改时间做比较。如果文件没变,返回 304 Not Modified,并且不带正文;如果变了,返回正常的 200 和完整内容。
ETag 与 If-None-Match
ETag 是内容版本的标识,可以是文件指纹,也可以由内容计算得出。它比时间更精确,适合修改时间不可靠、或者同一秒内多次写入的场景。两者可以同时存在,服务器一般优先参考 ETag。
304 带来的实际好处
- 响应体为空,传输的数据量明显下降,带宽占用减少。
- 服务器不必重新渲染整个页面,响应时间通常比完整请求短。
- 蜘蛛用更少的时间确认一批 URL 没有变化,省下的抓取额度可以分给新页面和更新页面。
- 源站压力下降,抓取高峰时更不容易出现超时。
需要说明的是,304 只是让重复抓取变便宜,它不会直接让新页面更快被发现。URL 发现仍然要靠内链、Sitemap 这些入口。
几种让 304 失效的常见情形
- Last-Modified 每次请求都变。有些程序直接拿当前时间当修改时间,蜘蛛带回来的时间永远对不上,于是每次都拿到 200。
- ETag 里含进程号或时间戳。同一份内容在不同机器、不同时间生成的 ETag 不一样,条件请求永远判定为已修改。
- CDN 或反向代理覆盖了头部。源站设置正确,但边缘节点重新生成了 ETag,或者强制返回完整响应。
- 动态页面没有做内容比对。由数据库查询拼装出来的页面,如果不额外计算版本标识,就只能一直返回 200。
- 对首次请求返回 304。没有历史版本的客户端拿到 304,页面内容就完全拿不到了。
在日志里怎么核对效果
服务器日志里的状态码一栏可以直接看出 304 的比例。做法很简单:把最近的蜘蛛请求按状态码分组,看看 200 与 304 各占多少。如果同一批 URL 连续多天全是 200,而页面内容并没有变化,就说明条件请求没有生效。
顺便留意日志里 304 的响应字节数是不是接近 0。如果状态码是 304 但字节数很大,说明中间层仍在传输完整正文,节省的效果并没有落到实处。
配置时的几点建议
- 让 ETag 基于内容本身生成,不要插入时间或随机值。
- Last-Modified 使用文件或数据的真实修改时间,不要用请求时间。
- 静态资源交给 Web 服务器处理条件请求,动态页面在应用层做一次内容摘要比对。
- 检查 CDN 配置,确认边缘节点不会替换源站的 ETag,也不会把 304 转成 200。
- 改动后用日志复核一段时间,确认 304 比例上升,同时没有出现内容错乱。
条件请求不是抓取优化的全部,但它把重复确认这件事的成本压得很低。在抓取额度有限的情况下,省下来的那部分,可以留给真正需要抓的页面。