知识库的网页采集任务每天都运行。某天,日志显示“请求完成”,提取器却得到空字符串,于是把原来的制度说明替换成了空文档。下一次问答,AI说:“资料中没有相关规定。”
这不是模型忘了资料,而可能是采集器误读了一次304 Not Modified。
304没有响应正文,但不表示原文为空。它通常是在告诉发起条件请求的客户端:这次不用重传,请使用你已经保存的那份表示。 本篇只解释这一道采集入口的边界:RAG增量抓取遇到304,怎样既省流量,又不把知识库越更新越空?
AI原创概念插画:红漆盘上的蕨叶立体书、左侧小胶囊与蕨叶小片、右侧柜中留存的书并置,比喻“小回执指向已有原件”。不是实际网络设备或协议状态的证明。
一、先把“传回了什么”与“资料是什么”分开
RAG,即检索增强生成,会把收集到的资料解析、分块、建立索引,再将相关片段交给模型。网页采集往往是这个过程的第一步。
第一次读取一份网页时,服务端可能返回:
HTTP/1.1 200 OK
ETag: "policy-v1"
Content-Type: text/plain
夜间出租车须事先审批。这是虚构的教学报文片段,省略了其他头字段。ETag叫实体标签,可把它先理解为服务端为某个“表示”提供的验证标记。表示是资源在这次请求下选出的具体形式,例如中文版网页,而不只是抽象的同一个URL。[1]
客户端保存正文及标签。下次再读,在GET请求里附上:
If-None-Match: "policy-v1"意思不是“请给我v1”,而是:“如果当前选中的表示与这个标签匹配,就不必把完整内容再发一遍。”在相应条件成立时,服务端返回304;如果条件不匹配,正常情况可能返回200及当前正文。[1][3]
RFC 9110第15.4.5节明确规定:304不能包含内容或尾部字段。 所以,下面两件事可以同时成立:
- 这次网络响应的正文是零字节。
- 客户端应使用的文档仍是上一次保存的完整正文。
如果适配层把所有响应都统一处理成“读正文 → 提取文字 → 覆盖索引”,就丢掉了这层区别。304不是“空文档更新”,更不是“文档已删除”。
二、ETag必须和它验证的原件一起保存
最危险的恢复现场,是数据库里还留着"policy-v1",对象存储里的原文却已经丢失。
采集器继续带着这个标签请求;服务端正确返回304;采集器却没有可复用的内容。服务端不知道你本地硬盘是否损坏,也没有在304里藏一份备用正文。
因此,一个实用的采集记录至少应关联:
| 记录 | 用途 |
|---|---|
| 请求目标与表示选择信息 | 确认这次验证的是哪份资料 |
| 原样保存的ETag,或适用的Last-Modified | 构造后续条件请求,不自行改写标记 |
| 原始内容的持久句柄及本地完整性信息 | 确认原件仍可读,而不只剩标签 |
| 最近取回正文的时间 | 说明手上这份内容何时取得 |
| 最近成功再验证的时间与结果 | 说明何时确认它仍可复用 |
| 提取器与分块规则版本、处理状态 | 判断是否需要重新处理已有原件 |
这是本文给RAG采集层的应用设计建议,不是HTTP规定的一套数据库字段。
收到304后,先找到符合本次请求条件的留存表示,再更新相应元数据。RFC 9111第4.3.4节也不是让缓存随便挑一个同URL文件:它要求识别适合更新的已存响应,并根据验证器等信息进一步确定目标。[2]
如果原件丢失、损坏或无法确认匹配,不要把空串当补救。对有权访问的原请求,在有界预算内移除条件头,重新取得完整响应,是一种直接的恢复策略;也可以先修复可靠原件副本。无条件GET仍可能失败,不保证一定拿到200。恢复未完成就记录缺口,不能伪称已读到最新内容。
三、本地实验:304真的没正文,但资料没有变空
本文在Python 3.13.5上实际运行了一个标准库教学程序:启动只监听127.0.0.1的临时HTTP服务,由它提供人工政策字符串,再用urllib.request发起请求。服务在实验结束后关闭。
服务端只实现本例所需的“单个强ETag精确匹配”,没有实现完整的HTTP缓存、标签列表、弱比较、鉴权或缓存新鲜度算法。它不是浏览器、CDN或生产采集器的合规测试,也没有调用模型或向量库。
实际输出的关键部分如下:
| 顺序 | 请求与本地条件 | 实际结果 |
|---|---|---|
| 首次读取 | 无条件GET | 200,标签"zh-v1",取得zh: approval required; version=1 |
| 内容未改 | 携带"zh-v1" | 304,本次响应正文0字节;复用后仍为上述完整字符串 |
| 本地原件丢失 | 只有标签,没有可用正文 | 不把304当新原文;无条件恢复请求返回200,正文与首次相同 |
| 服务端切到v2 | 继续携带"zh-v1" | 200,标签"zh-v2",取得zh: approval required; version=2 |
| 请求语言改为en | 携带中文v2标签,改Accept-Language | 200,标签"en-v2",取得英文标识的v2字符串 |
程序对上述状态与内容关系执行了断言,全部通过。原件丢失由本地变量设为空模拟,不是存储故障实测;语言变体也是手写服务规则,不是自动翻译结果。
如果想跟随复现,可按这四步观察:先保存一次200正文和标签;用同一标签再请求;确认304响应没有正文,同时确认留存正文仍在;最后让服务端换版本,用旧标签再请求,检查新正文能否替换旧原件。测试时不要只断言“请求没抛异常”,还要断言业务资料没被清空。
另外,本例的urllib.request会把收到的304交给HTTPError处理路径。实验只对e.code == 304进行专门分支,其余错误继续抛出。客户端库如何暴露304要实际核对;异常路径不等于服务端故障,正常返回也不等于取得了新正文。
四、同一个URL,不一定是同一份可复用资料
设想一个地址可按Accept-Language返回不同语言。中文版的标签,不能脱离请求语境,被拿去断言“所有语言都没更新”。
RFC 9111第4.1节定义了Vary参与缓存匹配的规则。本例服务发送Vary: Accept-Language,并为不同语言生成不同标签。[2] 在实际系统中,内容还可能因协商格式等条件变化。
对RAG采集层,应把两类约束分开:
- HTTP表示选择与缓存规则:按目标URI、适用请求信息及
Vary等契约,判断哪份已存响应匹配。 - 应用的数据隔离与当前访问权:按可信的账号、租户或来源范围,决定谁可以读取和复用原件。
第二项不能交给Vary代办。ETag不是授权凭证,不保证两个账号可共享资料;同一个标签字符串也不是跨站点、跨资源的全局文档ID。不要为了“分区”把原始Cookie或令牌写进普通任务日志,可使用受控的非秘密作用域标识。
304之后也可能需要更新缓存相关元数据,不能只保留第一次200的所有头字段不动。精确更新规则应按RFC 9111第3.2及4.3.4节或成熟缓存实现处理,而不是用一行“全部覆盖headers”冒充完整缓存。[2]
五、“网络没变化”不等于“下游什么都不做”
这里有两个相反的误区。
误区一:304就重新索引空正文。 结果是把有效知识误删。应复用匹配原件,保持其内容身份,而不是把验证响应当作新的内容版本。
误区二:304就跳过所有后续处理。 如果原件没变,但提取器修复了双栏读序问题,或分块规则增加了表头信息,你可能仍要从保存的原件重新解析、重新建索引。改变的是处理链,不一定是远端资源。
所以应分开记录:远端表示的验证状态、原始内容状态、解析产物状态、索引可见状态。HTTP304只处理其中一个环节,不能证明向量已经写齐、旧切片已经清理,或模型已经引用正确资料。
还有一个证据边界:ETag是服务端定义的不透明标记,不保证是SHA256。 RFC 9110允许不同生成方法;带W/的弱标签更不承诺逐字节相同,If-None-Match的标准比较规则本身采用弱比较。[1] 本文教学服务使用精确字符串比较,只为简化演示,不能照搬成通用实现。
若需要可复查的逐字节证据,应另存具体原件及相应摘要,并注明摘要计算针对哪一种字节表示。304不能告诉你未传回的字节是什么,也不能证明制度内容现实正确,或在整个验证间隔中从未变化。最近验证时间同样不是来源发布日期、制度生效日。
六、一份可以放进采集器验收的清单
- 200新内容、304复用、访问失败和解析失败有不同结果类型,不统一降格为空字符串。
- 发条件请求前,确认验证器对应的原件仍可读、完整,且匹配本次表示与访问范围。
- ETag保留引号及
W/等原值,不当作通用哈希或全局文档ID。 - 304更新匹配记录的验证信息,不以响应空正文覆盖资料或触发文档删除。
- 原件丢失时有有界恢复路径;恢复失败保留缺口,不宣称成功同步。
- URI与
Vary等表示条件按契约处理;账号隔离和当前授权另外检查。 - 提取器、分块器升级可重处理已有原件,不因304永久跳过。
- 回归测试覆盖原件丢失、远端换版、表示切换及明确访问失败;失败不自动转成“来源为空”。
对使用网页资料的Agent来说,这也是工具结果设计问题。与其只返回一段可能为空的text,不如明确区分“本次获得新内容”“复用了某个已存版本”“验证成功但原件不可用”“读取失败”。这些是应用层建议的状态,不是新增HTTP状态码。
模型不该从零字节猜资料状态。 让采集工具先说清楚这次究竟发生了什么,再把确实可回查的正文交给它。
来源与阅读范围
本文由AI创作。开头事故为概念场景,不指向已发现的产品故障。报文、政策字符串及本地服务均为教学材料;实验只验证受控服务与客户端的分支及原件保留逻辑,不代表生产缓存、RAG效果或模型能力测试。
检索截止:2026年10月25日,UTC。网页提取结果存在截断,已另取RFC原始文本及MDN公开HTML补读以下相关正文。
- RFC 9110:HTTP Semantics。2022年6月,固定规范文本。实际读取第8.8.3节ETag及生成、比较与表示变体,第13.1.2—13.1.3节条件头,第15.4.5节304。支持协议语义,不作为某个客户端当前行为的说明。
- RFC 9111:HTTP Caching。2022年6月,固定规范文本。实际读取第4.1节
Vary、第4.3.3—4.3.4节验证响应与已存响应更新,并参考第3.2节头字段更新规则。与[1]属于同一套HTTP标准,不是独立性能复现。 - MDN:ETag header。本轮页面标示最后修改于2025年7月28日。实际读取语法、强弱标签及“Caching of unchanged resources”章节,用作易读说明;关键边界以RFC原文为准。

留下你的想法 0
欢迎提问、补充和不同观点。评论通过审核后会公开显示。