搜索蜘蛛回访一个已经抓过的 URL 时,通常不会无条件地把整个 HTML 重新下载一遍。它会在请求头里带上条件:If-Modified-Since 或 If-None-Match。服务器如果判断内容没有变化,就返回 304 Not Modified,不带正文。这个机制对站点和蜘蛛双方都有意义,但它经常被误解,也被错误配置。
回访请求里发生了什么
第一次抓取后,蜘蛛会记录响应中的 Last-Modified 和 ETag。下次回访时:
- If-Modified-Since 带上上次的 Last-Modified 时间;
- If-None-Match 带上上次的 ETag 值;
- 两者同时存在时,服务器按自身实现优先级判断。
如果内容未变,返回 304;如果有变化,返回 200 和新的正文。需要注意的是,304 并不等于这个 URL 不用再抓,它只是省掉了正文传输。
304 省下的是什么
它主要减少三件事:带宽占用、服务器渲染或查询开销、以及响应时间。对于列表页、标签页这类模板固定但内容偶尔变化的 URL,稳定的缓存协商能让回访更快完成。但抓取预算的消耗并不能简单等同于字节数,蜘蛛仍要发起请求、等待响应、判断状态码。因此 304 不会让一个深层页面凭空获得更多抓取机会。
把 304 当成降低服务器压力的手段是合理的,把它当成提高收录概率的手段则会失望。
常见的缓存协商失效原因
- 动态 ETag:每次请求都生成不同的 ETag,比如把进程 ID、时间戳拼进去,导致永远无法命中。
- Last-Modified 使用当前时间:页面没变,时间却每次都刷新,蜘蛛只能收到 200。
- CDN 或反向代理改写头:源站返回的 ETag 被边缘节点替换,回访时对不上。
- 压缩方式变化:同一 URL 在不同 Accept-Encoding 下产生不同 ETag,造成协商不一致。
- 会话或个性化内容:页面里混入随机推荐、时间戳、用户态片段,使内容实际每次都变。
核对步骤
- 用命令行工具带条件头发起请求,例如 curl 带上 If-None-Match 或 If-Modified-Since,观察是否返回 304。
- 连续两次无条件请求同一 URL,比较两次响应头中的 ETag 与 Last-Modified 是否一致。
- 查看源站直连与经过 CDN 后的响应头,确认没有被中间层改写。
- 从服务器日志中筛选爬虫 UA 与 304 状态码,统计回访中 304 的比例。
- 对比例异常的 URL,检查模板里是否输出了时间、随机数或未登录态内容。
与 URL 发现的关系
缓存协商不直接决定某个 URL 是否被发现,但它影响蜘蛛在同一时间窗口里能处理多少请求。当大量本可返回 304 的页面被迫返回完整正文时,响应时间变长,蜘蛛在同一时段能覆盖的 URL 数量就会下降,新入口的发现节奏也可能被拖慢。反过来,如果为了追求 304 而把内容更新藏在客户端渲染里,蜘蛛拿到的 HTML 长期不变,链接入口同样会缺失。两者需要平衡。
配置时的注意点
- ETag 应该由内容决定,而不是由请求决定。
- Last-Modified 应反映内容真实变更时间,不要用部署时间批量刷新所有页面。
- 对确实频繁更新的页面,允许返回 200,不必强求 304。
- 301、404 等状态码不要返回 304,状态语义要分开。
- 在 sitemap 的 lastmod 与页面 Last-Modified 之间保持一致,避免互相矛盾的信号。
缓存协商是抓取路径上一个不起眼但可观测的环节。定期看日志里的 304 比例和响应时间,比猜测蜘蛛为什么不来更有依据。