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

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 请求获取 ETag、Last-Modified 或 Content-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 验证信息,只能在下载后比较内容。 |
| 超大响应体或昂贵 GET | HEAD 预检 + 条件 GET | 在 HEAD 行为可靠时,可能减少不必要的正文下载。 |
| 高并发 API 采集 | ETag 条件 GET | 单次请求完成验证,适合连接池和并发调度。 |
落地实现流程与注意事项
推荐流程
- 首次请求资源,保存响应正文、ETag、Last-Modified、响应状态码、Content-Encoding 和采集时间。
- 后续请求优先发送
If-None-Match,同时在可用时发送If-Modified-Since。 - 收到
304时复用本地已保存正文,不重复解析和入库。 - 收到
200时更新正文及验证字段;如果页面存在随机区域,再执行业务字段指纹比较。 - 遇到
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 行为稳定的场景。