直接结论:对大多数网页采集任务,优先选择ETag 与 Last-Modified 组合使用的条件 GET 请求;服务端只支持时间判断时使用 If-Modified-Since;接口没有可靠缓存标识时,再使用应用层内容指纹。HEAD 预检通常不应作为默认方案,因为它会增加一次请求,只有在响应体很大、GET 成本较高且服务端 HEAD 行为稳定时才值得采用。

HTTP 条件请求在网页采集中的应用:实现方案对比与选型指南

HTTP 条件请求是什么

HTTP 条件请求是指采集客户端在发起请求时,携带资源上一次响应中的缓存验证信息,让服务端判断资源是否发生变化。资源未变化时,服务端返回 304 Not Modified,客户端无需重新下载正文;资源已变化时,服务端返回新的正文,通常状态码为 200 OK

网页采集中常见的验证字段包括:

  • ETag:资源内容或版本的标识,客户端下次通过 If-None-Match 回传。
  • Last-Modified:资源最后修改时间,客户端下次通过 If-Modified-Since 回传。
  • 304 Not Modified:服务端确认资源未变化,响应通常不包含完整正文。

条件请求的核心价值是减少重复下载、降低出口带宽、缩短未更新页面的处理时间,并降低对目标站点的访问压力。但它只能判断“服务端认为资源是否变化”,不能自动保证采集结果符合业务更新规则。

不同实现方案对比

方案判断依据请求次数准确性实现复杂度适用场景
ETag资源版本或内容标识1 次条件 GET通常较高动态网页、接口、需要精确判断的资源
Last-Modified最后修改时间1 次条件 GET中等静态文件、更新时间可靠的页面
ETag + Last-Modified版本标识与修改时间1 次条件 GET较高低到中通用生产采集系统
HEAD 预检先获取响应头,再决定是否 GET通常 1 至 2 次取决于服务端响应体很大、HEAD 支持规范的站点
应用层内容指纹下载后计算正文或字段哈希1 次完整 GET可控中到高服务端没有可靠 ETag 或时间字段的场景
不同实现方案对比

方案一:If-None-Match 与 ETag

实现方式

首次采集资源时,保存响应头中的 ETag。下次请求将其放入 If-None-Match

GET /news/123 HTTP/1.1
Host: example.com
If-None-Match: "abc123"

如果资源版本没有变化,服务端返回:

HTTP/1.1 304 Not Modified
ETag: "abc123"

如果资源已更新,服务端返回新的正文和新的 ETag。

优点与限制

  • 优点:不依赖客户端时间,适合内容版本变化频繁、修改时间精度不足的资源;通常能更准确地识别版本变化。
  • 限制:部分站点不返回 ETag,或者在负载均衡、压缩方式变化时生成不稳定的 ETag;弱 ETag 不能用于要求字节级一致性的场景。

选型建议:当目标站点稳定返回 ETag,且采集系统需要可靠增量判断时,ETag 是首选方案。

方案二:If-Modified-Since 与 Last-Modified

实现方式

客户端保存上次响应的 Last-Modified,下次请求通过 If-Modified-Since 发送:

GET /manual/page.html HTTP/1.1
Host: example.com
If-Modified-Since: Wed, 21 Feb 2024 10:00:00 GMT

如果服务端判断资源在该时间之后没有修改,则返回 304 Not Modified

适用边界

该方案适合静态 HTML、图片、文档和由文件修改时间驱动发布的页面。它的主要风险是时间粒度和时钟问题:资源在同一秒内多次更新时,时间字段可能无法区分;服务器时间配置异常、反向代理改写时间,也可能造成误判。

如果目标内容的更新时间可靠,但 ETag 缺失,使用 Last-Modified 是成本最低的落地方式。对于新闻列表、商品价格等高频变化数据,不建议单独依赖它。

方案三:ETag 与 Last-Modified 组合使用

推荐实现

客户端同时保存两个响应头,并在后续请求中同时发送:

GET /products/42 HTTP/1.1
Host: example.com
If-None-Match: "product-v18"
If-Modified-Since: Fri, 23 Feb 2024 08:30:00 GMT

在标准 HTTP 语义下,服务端通常优先处理 If-None-Match;只有在没有 ETag 条件或服务器实现特定时,才可能使用时间条件。客户端不应自行假设所有服务器的处理逻辑,必须以最终状态码和响应内容为准。

为什么适合生产采集

  • 兼容更多服务端实现,目标站点缺少其中一个字段时仍有机会完成条件验证。
  • ETag 提供版本判断,Last-Modified 提供时间线索,便于故障排查和数据审计。
  • 仍然只需要一次条件 GET,不会像 HEAD 预检那样默认增加请求次数。

推荐指数:通用网页采集、周期性同步、商品与内容监控系统优先采用此方案。

方案四:HEAD 预检后再 GET

实现逻辑

客户端先发送 HEAD 请求获取 ETagLast-ModifiedContent-Length,判断资源是否可能更新;确认更新后再发送 GET 下载正文。

该方案看似可以避免无效的正文传输,但没有更新时仍然已经产生了一次 HEAD 请求。若服务端没有正确实现 HEAD、对 HEAD 与 GET 返回不同的缓存头,或中间代理行为不一致,判断结果可能不可靠。

适用场景

  • 单个资源体积较大,例如大型文档、视频元数据或压缩包。
  • GET 会触发较高的计算或传输成本,而 HEAD 确实能稳定返回有效版本信息。
  • 系统需要在下载前获取资源大小、类型或更新时间,并且能够接受额外的请求延迟。

对于普通 HTML 页面,直接发送带条件头的 GET 通常更简单,也更容易与站点实际行为保持一致。

方案五:应用层内容指纹

实现方式

当服务端没有 ETag、Last-Modified,或者这些字段不稳定时,客户端完整获取响应正文,再对正文或业务字段计算哈希,例如 SHA-256。下一次采集时比较新旧哈希,判断内容是否变化。

也可以只对抽取后的业务字段计算指纹。例如商品采集可以只纳入价格、库存、标题和规格,而忽略广告位、时间戳或随机推荐内容。

优点与限制

  • 优点:判断规则由采集方控制,能够忽略页面噪声,并适配没有标准缓存头的站点。
  • 限制:每次仍需下载正文,无法节省主要带宽;网页结构变化、动态 token 和随机内容会影响哈希稳定性。

该方案适合与条件请求叠加:先用 ETag 或 Last-Modified 尝试避免下载,收到 200 响应后,再用业务字段指纹判断是否真正需要入库。

按采集场景选型

采集场景建议方案原因
新闻、公告周期性更新ETag + Last-Modified需要低成本判断页面版本,并保留更新时间信息。
静态文档或下载资源Last-Modified 或 ETag + Last-Modified资源更新时间通常明确,且正文可能较大。
商品价格与库存监控ETag + 业务字段指纹页面可能频繁变化,需要进一步判断关键字段是否变化。
无缓存头的老旧站点应用层内容指纹缺少可用的 HTTP 验证信息,只能在下载后比较内容。
超大响应体或昂贵 GETHEAD 预检 + 条件 GET在 HEAD 行为可靠时,可能减少不必要的正文下载。
高并发 API 采集ETag 条件 GET单次请求完成验证,适合连接池和并发调度。

落地实现流程与注意事项

推荐流程

  1. 首次请求资源,保存响应正文、ETag、Last-Modified、响应状态码、Content-Encoding 和采集时间。
  2. 后续请求优先发送 If-None-Match,同时在可用时发送 If-Modified-Since
  3. 收到 304 时复用本地已保存正文,不重复解析和入库。
  4. 收到 200 时更新正文及验证字段;如果页面存在随机区域,再执行业务字段指纹比较。
  5. 遇到 ETag 消失、格式异常或服务端行为变化时,允许降级为普通 GET,并记录异常。

工程注意事项

  • 正确处理 304:304 不是采集失败,也不是空页面。应将其视为“资源未变化”,继续使用本地缓存内容。
  • 区分强弱 ETag:W/"value" 是弱 ETag,适合语义等价判断,不应直接用于严格字节一致性校验。
  • 考虑 Vary:若响应受语言、压缩、Cookie 或 User-Agent 影响,应将相关请求上下文纳入缓存键,避免不同版本相互覆盖。
  • 避免无限信任缓存头:条件请求不能替代限速、重试退避、robots 规则检查、身份认证和错误处理。
  • 处理 200 但内容未变:部分服务器不支持条件请求,或者代理移除了验证字段,此时应用层指纹仍有价值。
  • 记录命中率:建议监控 304 比例、条件请求失败率、验证字段缺失率、平均响应体大小和实际解析量。

常见问题

ETag 和 Last-Modified 应该二选一吗?

不必。生产采集通常同时保存并发送两者,以提高兼容性,并依据服务端返回的状态码决定是否复用本地缓存。

收到 304 后还需要重新抓取页面吗?

通常不需要。304 表示资源未变化,程序应直接使用本地缓存正文;只有本地正文丢失时,才需要发起完整 GET 恢复缓存。

为什么发送 If-None-Match 后仍然返回 200?

可能是服务端不支持条件请求、ETag 已失效、代理改写了请求头,或 Cookie、User-Agent 导致资源版本不同。应记录请求和响应上下文排查。

HEAD 请求是否一定比条件 GET 更省流量?

不一定。HEAD 会额外增加一次请求。只有在响应体很大、HEAD 返回的版本信息可靠且 GET 成本较高时,预检才可能有明显收益。

条件请求能否保证不漏采更新内容?

不能绝对保证。应结合定期强制刷新、业务字段校验、异常监控和回溯机制,降低错误缓存头或时间精度造成的漏采风险。

总结

网页采集的默认推荐方案是 ETag 与 Last-Modified 组合条件 GET:一次请求完成版本验证,未变化时通过 304 复用本地正文。静态资源可优先考虑 Last-Modified;服务端无可靠缓存头时使用应用层内容指纹;HEAD 预检仅适用于大响应体或 GET 成本高且服务端 HEAD 行为稳定的场景。