抓取日志里,同一个 URL 一周被访问十几次并不罕见。除了站点本身更新频繁,另一个常被忽略的原因是:服务器没有告诉蜘蛛“这个页面没变”。这时 304 状态码和配套的缓存头就有实际价值。
蜘蛛为什么会反复来
蜘蛛对已知 URL 有一套重新访问计划,页面权重、更新频率、站点整体抓取情况都会影响它回来的间隔。如果每次访问服务器都返回完整页面,那么一份没变化的 HTML 会被反复传输和解析。对于模板页、说明页、分页尾页这类长期不动的 URL,这部分开销是白花的。
协商缓存就是解决这个问题的标准手段:让蜘蛛带着上次的标识来问一句“变了吗”,没变就只回一个状态码。
条件请求:蜘蛛怎么问“变了吗”
蜘蛛以前抓过某个 URL,再次访问时通常会在请求头里带上 If-Modified-Since(值来自上次响应里的 Last-Modified)或 If-None-Match(值来自上次的 ETag)。服务器判断内容没变,就返回 304 Not Modified,不带响应体。
304 省下了什么,没省下什么
- 省下的是响应体传输、带宽占用和服务端渲染成本,对资源有限的站点帮助明显;
- 省不下的是这次请求本身。蜘蛛仍然发了一次 HTTP 请求,抓取配额的占用不会完全消失;
- 也不会带来新的 URL 发现。304 没有正文,蜘蛛不会从中解析出任何链接,新页面还得靠内链和 Sitemap。
容易踩的几个坑
- ETag 不稳定:多台后端各自生成 ETag,蜘蛛带着 A 机器的值问到 B 机器,对不上就只能回 200,协商缓存形同虚设。文件 inode、时间戳参与计算时尤其容易出问题。
- CDN 或反向代理吞掉条件请求头:节点按自己的策略统一回 200,源站的 304 逻辑根本没被执行。
- Last-Modified 精度不足:只精确到秒、或者由静态化任务统一写成生成时间,会导致判断失真。
- 内容更新了却仍然回 304:缓存规则写死、或页面由定时任务生成而判断逻辑没跟上。蜘蛛会继续使用旧版本,新内容和页面上新加的链接都会推迟被发现。
- 把 304 当成屏蔽手段:它只影响重复抓取,拦不住蜘蛛第一次访问,也解决不了低质页面被反复抓的问题。
排查顺序
- 用 curl -I 之类的工具手工带上 If-None-Match 或 If-Modified-Since,看返回的是 200 还是 304;
- 确认响应里是否同时存在 ETag 与 Last-Modified,只有其中一个也能协商,但双份更稳;
- 换一台后端再请求一次,比较两次的 ETag 是否一致;
- 回到抓取日志,统计同一个 URL 的状态码分布,200 占比长期偏高,说明协商基本没生效。
如果页面确实更新了,服务器却仍然回 304,蜘蛛看到的就是旧页面。这属于内容同步问题,和抓取量无关,优先级要排在前面修。
和 URL 发现的关系
新 URL 的发现主要靠站内链接、Sitemap 和主动推送,304 在这件事上帮不上忙。它的定位是降低重复访问的成本,让资源更多留给真正需要抓的页面。所以优化顺序应该是:先把内链结构和 Sitemap 理顺,再回头看服务器响应里的细节。
另外注意,304 之后蜘蛛仍会更新它记录的 Last-Modified 或 ETag,但页面上的链接集合沿用上一次收到的正文。如果正文已经变了,新版链接要等下一次拿到 200 才会进入抓取队列。
实践建议
把 304 当成一次“降噪”,不要指望它改变抓取节奏的根本。对长期不变的页面,让 ETag 保持一致、Last-Modified 反映真实修改时间;对频繁更新的页面,宁可老老实实回 200,也不要为了省流量而返回错误的 304。判断标准很简单:状态码要如实反映内容有没有变,而不是服务器想省多少带宽。