蜘蛛抓取页面时,服务器并不需要每次都把完整 HTML 重新发一遍。如果请求里带了验证信息,而内容没有变化,返回一个 304 状态码就能结束这次抓取。这样既省服务器带宽,也减少蜘蛛等待时间。但很多站点在缓存验证头上配置得比较随意,导致条件请求形同虚设,每次抓取都变成全量回源。
条件请求在做什么
浏览器和蜘蛛在第一次拿到页面时,服务器通常会在响应头里给出两个验证器:Last-Modified 和 ETag。下次再请求同一个地址时,客户端会把这两个值分别放进 If-Modified-Since 和 If-None-Match。服务器比较后如果内容没变,就返回 304 Not Modified,不携带正文。
对蜘蛛来说,304 意味着“这个地址我看过了,内容还是老样子”,它可以很快转向下一个地址。如果服务器总是返回 200 和完整正文,蜘蛛就得重新下载、重新解析,抓取效率会下降。
容易被忽略的几种失效情况
验证器每次都在变
有些动态程序会把 Last-Modified 设成当前时间,或者用进程 ID、内存地址生成 ETag。这样每次请求得到的验证器都不一样,条件请求永远对不上,304 自然不会出现。
多节点或多层代理不一致
站点有多台源站或经过反向代理、压缩层时,不同节点给出的 ETag 可能不同。蜘蛛这次请求落到 A 节点,下次落到 B 节点,验证失败,又会拿到 200 响应。
压缩层改写了 ETag
开启 gzip 或 Brotli 后,响应体的字节变了,但 ETag 如果还是按原始内容计算,就可能出现验证不匹配。部分服务器会改用弱 ETag(W/ 前缀),这本身没问题,但要确认整个链路行为一致。
Cache-Control 与验证头冲突
Cache-Control 里写了 no-store 或极短的 max-age,同时又依赖 ETag 做验证,会让中间缓存直接放弃存储,条件请求也失去意义。
304 响应里带了正文
按规范,304 不应包含消息体。如果程序在 304 后面仍然输出页面内容,客户端可能解析异常,浪费一次请求。
自查步骤
- 用 curl -I 连续请求同一个 URL 两次,第一次记下 Last-Modified 和 ETag。
- 第二次请求时手动带上 If-Modified-Since 或 If-None-Match,观察是否返回 304。
- 如果返回 200,对比两次的 ETag 是否变化,以及 Last-Modified 是否等于当前时间。
- 在多台源站或多条线路下重复测试,确认验证器一致。
- 在访问日志里统计 304 的比例。比例长期接近零,通常说明条件请求没有生效。
- 换用蜘蛛 UA 再测一次,排除按 UA 差异化输出导致验证器变化。
调整方向
- ETag 优先使用内容哈希或稳定的版本号,不要用时间戳、进程号、内存地址。
- Last-Modified 取自内容最后修改时间,而不是请求发生时间。
- 源站与代理层统一 ETag 生成规则,压缩层不要随意改写强 ETag。
- 对长期不变的静态资源,设置较长的 Cache-Control,并保留 ETag 供验证。
- 对频繁更新的页面,可以缩短 max-age,但不要关闭条件请求。
- 304 响应只返回头,不输出正文。
和抓取预算的关系
抓取预算有限时,服务器响应速度、状态码、内容是否变化都会影响蜘蛛继续访问的意愿。304 不能直接提升排名,但能减少重复传输,让蜘蛛把时间花在真正变化过的地址上。对内容量大、更新频繁的站点,效果更明显。
抽查时建议固定几个代表性地址:首页、栏目页、详情页、静态资源,分别记录状态码、验证器和响应体积,隔一段时间再对比,看是否有节点漂移。
条件请求属于服务器与协议层面的基础配置,改动不大,但需要定期确认。把它纳入站点巡检清单,和日志、状态码、缓存策略一起看,能避免很多“蜘蛛来了却白跑一趟”的情况。