RAG 为什么用 R2 而非爬虫:数据噪音与召回质量
AI 工程64 阅读约 4 分钟
RAG(检索增强生成)的效果,七成取决于喂进去的数据干不干净。我搭博客 AI 助手时,在「数据源」这一步面临两个选择,最后的决定影响了整个召回质量。
两种数据源
Cloudflare AI Search(以及大多数 RAG 方案)支持两类数据源:
1. 爬虫(Sitemap / Crawler):给它一个网站,它自动抓页面。上手最快,但有致命问题:
- 抓的是 HTML,且常常只有首屏内容,对分段渲染的长文章无能为力;
- 更要命的是,爬虫分不清正文和噪音——导航栏、侧边栏、评论区、页脚、甚至 AI 摘要框,全被一锅端进去。
2. R2 存储桶(主动维护 Markdown):自己把干净的 Markdown 文件放进对象存储当数据源。要自己搭同步管道,但内容 100% 可控。
我选了 R2 方案。
为什么噪音是致命的
RAG 的核心是把文本切块、向量化,存进向量库,检索时按语义相似度召回。问题在于:噪音也会被向量化。
想象每篇文章页都带着相同的导航文字「首页 / 归档 / 关于 / 友链」、相同的页脚、一堆评论。这些重复的、与正文无关的内容被切块、嵌入后,会在向量空间里形成一片「噪音云」。后果是:
- 用户问一个问题,召回的可能是某篇文章的评论或导航,而不是正文;
- 大量重复样板内容拉低了正文 chunk 的区分度,相似度排序失真;
- 长文被首屏截断,关键内容根本没进库。
噪音污染向量空间——这是爬虫方案最隐蔽也最难补救的问题。
R2 方案的做法
我让博客后端在内容变更时,主动把文章转成干净的 Markdown推送过来,写进 R2。每个文件带一段 Frontmatter,给模型结构化的元数据上下文:
---
id: 42
title: Redis 归档缓存的事件驱动失效
url: https://jiawen.live/article/42
created_at: 2026-03-12
---
正文(纯 Markdown,无任何 UI 噪音)...
这样 AI Search 索引到的:
- 是纯正文,没有导航/评论/侧边栏;
- 是完整长文,不被首屏截断;
- 带结构化元数据,召回时能直接拿到文章 id、标题、URL,方便引用和「取全文」的后续工具调用。
检索时配上 query 改写和重排序,进一步提升相关性:
.search({
messages: [{ role: 'user', content: query }],
ai_search_options: {
retrieval: { max_num_results: 4 },
query_rewrite: { enabled: true },
reranking: { enabled: true, model: '@cf/baai/bge-reranker-base' },
},
})
代价与权衡
R2 方案不是免费的——你得自己搭「内容变更 → 同步到 R2」的管道(我用带 HMAC 签名的 Webhook 做)。但这点工程量换来的是对数据质量的完全掌控,对 RAG 来说太值了:
| 爬虫 | R2 Markdown | |
|---|---|---|
| 上手 | 极快 | 要搭同步管道 |
| 内容纯净度 | 混杂噪音 | 100% 可控 |
| 长文支持 | 常被截断 | 完整 |
| 元数据 | 无 | Frontmatter 结构化 |
| 召回质量 | 受噪音拖累 | 高 |
小结
RAG 的质量上限由数据质量决定。爬虫省事,但抓来的 HTML 混着导航、评论、侧边栏,这些噪音被向量化后会污染整个检索空间。主动维护干净的 Markdown(放 R2)虽然要搭一条同步管道,却换来纯正文、完整长文、结构化元数据——召回质量完全不是一个量级。在 RAG 里,喂什么比模型多大更重要。





