一个 URL 被蜘蛛抓过一次之后,并不代表它不会再被访问。相反,只要页面还在站内、还有内链指向它,蜘蛛就会隔一段时间再来一次。问题在于:第二次、第十次来访时,服务器该怎么回答,才不会让蜘蛛白跑一趟,也不会让它误以为内容没有变化。
蜘蛛为什么带着条件请求再来
第一次抓取时,服务器返回 200 和完整 HTML,通常还会带上 Last-Modified 与 ETag 两个响应头。之后蜘蛛再访问同一个 URL 时,往往会把这两个值分别放进 If-Modified-Since 和 If-None-Match 请求头里,相当于问一句:我手上这份还有效吗?
如果服务器判断内容没变,可以回 304 Not Modified,不带正文。蜘蛛就知道该用手上的旧版本,省下的是带宽,也是它有限的抓取时间。判断不准,就会出现两种浪费:该回 304 却每次都回 200 全文,或者内容明明变了却回 304。
Last-Modified:精度和来源都要真实
Last-Modified 表示这个资源最后一次变化的时刻。它最常见的问题不是格式,而是来源不对:
- 每次请求都输出当前时间,等于告诉蜘蛛“这个页面随时在变”,条件请求永远不成立。
- 时间取的是数据库写入时间,而页面正文来自另一张表,正文改了时间却没动。
- 多台后端机器上的静态文件时间戳不一致,同一 URL 在不同机器上给出不同答案。
- 时间格式或时区写错,蜘蛛无法解析,只能当作普通响应处理。
更稳的做法是让 Last-Modified 跟着内容本身走:正文、模板中真正影响展示的字段变化时才更新。列表页、聚合页如果包含时间戳、随机推荐位,最好避开把这类内容算进最后修改时间。
ETag:稳定比“聪明”更重要
ETag 可以理解为内容指纹。理想状态是:同一版本的内容算出同一个值,内容一变值就变。实际部署里,问题往往出在这几处:
- ETag 里混入了随机数、当前时间或进程 ID,每次请求都不同。
- 几台服务器各算各的,同样的内容得到不同的 ETag,蜘蛛在机器之间来回被“骗”。
- 后台保存了一次但内容没实际变化,ETag 变了,蜘蛛只能重下一遍。
- 弱 ETag 与强 ETag 混用,语义不一致,判断容易出错。
不需要追求复杂的算法。用文件大小加修改时间,或者对正文做一次稳定哈希,只要在整个集群里保持一致,就比“看起来很聪明”的方案可靠。
304 用错,代价是蜘蛛记错版本
304 的含义很具体:和我上次拿到的那份一样。如果内容已经改了却仍然返回 304,蜘蛛会继续沿用旧版本,页面上的新段落、新价格、新标题可能迟迟不进入它的判断。反过来,本该回 304 的静态资源却总回 200,会让抓取预算消耗在重复下载上,图片、CSS、JS 这类文件尤其明显。
304 不是一个省流量的开关,而是对“内容是否变化”这件事的承诺。承诺错了,比不回 304 更麻烦。
另外要注意,304 响应本身不应该再带正文,也不应该借 304 去传递错误或跳转信息。蜘蛛只看状态码和头部,多余的东西不会帮上忙。
中间有缓存层时,更容易出问题
CDN、反向代理、Nginx 缓存都可能参与条件请求的判断。需要确认几件事:请求头是否原样透传到源站;304 是否由缓存正确返回且不带响应体;缓存键是否会因为 Cookie、UA 不同而把同一 URL 拆成多份,导致命中率低、回源频繁。
和其他信号保持一致
Sitemap 里的 lastmod 与响应头的 Last-Modified 如果长期互相矛盾,蜘蛛会倾向保守判断,回访节奏可能变得不规律。页面如果确实更新了,除了改内容,也值得让时间信号同步更新,而不是靠一次次 200 全文去“提醒”它。
一份可落地的检查清单
- 用带条件请求头的请求测试几个典型 URL,观察返回码和头部是否合理。
- 对同一个动态页面连续请求两次,对比 ETag 与 Last-Modified 是否稳定。
- 抽查多台后端机器,确认同一 URL 给出同一组缓存验证值。
- 真正修改一次内容,确认 ETag 或 Last-Modified 确实发生变化。
- 检查 CDN 与代理层,确认条件请求头被透传、304 被正确返回。
- 翻一段时间的访问日志,看看 304 与 200 的比例是否偏离预期。
这些配置不会直接带来收录或排名,但能减少蜘蛛在“重复确认同一件事”上花掉的时间。把状态码和缓存验证做对,是让抓取路径更顺的一小步,也是常被忽略的一步。