蜘蛛再次访问一个已经抓过的 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 的比例是否趋于稳定。数据有起伏是正常的,重要的是趋势,而不是某一天的数字。
条件请求不是抓取优化的开关,它只是把重复下载的成本降下来。真正决定抓取效率的,仍然是入口是否清晰、内容是否有稳定更新,以及服务器能否及时响应。