🎯 一款可定制、具备反检测功能的云浏览器,由自主研发的 Chromium驱动,专为网页爬虫AI 代理设计。👉立即试用
返回博客

如何构建生产级 RAG 系统并将 LLM Token成本降低 70%

Ethan Brown
Ethan Brown

Advanced Bot Mitigation Engineer

02-Jun-2026

关键要点:

  • 干净的Markdown是LLM真正想要的格式。 原始HTML主要由导航、脚本、广告位置和内联样式组成——这是一种浪费上下文窗口并降低检索质量的噪声。Scrapeless的scrape_markdown返回页面的可读主体作为干净的Markdown,因此传递给您的嵌入模型的文本就是页面所涉及的文本。
  • 管道分为四个步骤:发现 → 提取 → 分块 → 嵌入。 找到重要的URL,使用云浏览器将每个URL渲染为干净的Markdown,将Markdown分割成适合您模型的重叠块,然后嵌入并持久化到向量数据库中,以用于增强生成的检索。
  • JavaScript重的页面和反机器人墙由平台处理。 许多高价值源通过客户端渲染来补充其内容,或位于机器人验证挑战之后。Scrapeless Scraping Browser在一个真实的反检测云浏览器中渲染页面,并提供住宅出口,因此您得到的Markdown是完全渲染的页面,而不是空壳。
  • 两个表面,一个原语。 当AI代理驱动管道时,从Scrapeless MCP服务器调用scrape_markdown,或者当脚本控制循环时,通过Python SDK创建云浏览器会话。两者均使用相同的反检测云浏览器。
  • 无状态MCP工具在其有效负载前缀中加上Response:\n\n 当您通过MCP服务器读取scrape_markdown输出时,在分块之前去除该前缀——这是一行代码的修复,可以防止多余的头出现在您的语料库中。
  • 反检测云浏览器,195多个国家的住宅代理。 Scrapeless Scraping Browser在每个会话中处理JavaScript渲染、住宅代理出口和指纹随机化(UA、时区、WebGL、canvas),以便构建语料库的脚本可以专注于文本质量,而不是规避技术。
  • 免费开始。 新的Scrapeless帐户包括免费的Scraping Browser运行时——请在Scrapeless注册。

介绍:给您的模型输入文本,而不是页面界面

语言模型的好坏取决于它所读取的文本。无论您是在组装微调语料库、建立基于检索增强生成(RAG)的知识库还是基于实时市场数据为代理提供支持,输入阶段决定了后续一切的上限。垃圾进不仅仅是垃圾出——它是浪费的令牌、污染的嵌入,以及在呈现饼干横幅而非答案的情况下的检索。

问题在于,现代网络是为浏览器和人类构建的,而非为嵌入模型。一个典型的文章页面包含几千字的实际内容,被数万字的导航菜单、分享按钮、相关帖子网格、评论小部件、饼干通知、跟踪脚本和内联CSS包裹着。将这些原始HTML输入到嵌入器中,信号在标记中淹没。更糟的是,越来越多的页面在初始加载后通过JavaScript渲染其主要内容,因此简单的HTTP获取返回的是一个空容器。还有一些网站存在反机器人挑战,完全阻止自动收集。

这篇文章将介绍一个基于Scrapeless Scraping Browser的Python工作流,将混乱的公共网页转换为干净、分块、准备好嵌入的文本。管道有四个步骤——发现URL、提取干净的Markdown、为RAG分块、嵌入到向量数据库——而scrape_markdown在提取阶段负责重任,通过返回任何页面的可读主体作为干净的Markdown。有关同一原语的代理框架版本,请参阅LangChain集成文章


您可以构建的内容

干净文本提取是多种LLM和RAG系统的基础:

  • 基于您自己文档的RAG。 爬取文档网站或知识库以提取干净的Markdown,进行分块并嵌入,以便支持代理根据当前文档而非过时的训练版本回答。
  • 微调和继续预训练语料库。 从公共文章和参考资料中组装大型去重文本数据集,收集时即已剔除多余内容。
  • 为代理提供实时网页支持。 渲染代理在查询时所需的页面,并提供干净的Markdown,以便答案引用当前的页面。
  • 竞争和市场情报。 将公共产品页面、博客文章和发布说明转化为可搜索的向量索引,以供分析师或LLM查询。
  • 新闻和研究监测。 定期提取出版商和期刊页面,标准化为Markdown,并嵌入以在不断变化的来源中进行语义搜索。
  • 内部语义搜索。 在您的团队依赖的公共参考资料上建立私有检索层,按计划保持更新。

为什么选择Scrapeless Scraping Browser

Scrapeless Scraping Browser 是一个可定制的反检测云浏览器,专为网络爬虫和 AI 代理设计。特别是针对 LLM 和 RAG 文本管道,它带来了:

  • 干净的 Markdown 提取。 scrape_markdown 渲染一个 URL 并以 Markdown 形式返回可读内容——保留标题、段落、列表、表格和链接;剥离导航、脚本、广告位和内联样式。这是嵌入模型最佳读取的格式。
  • 云端 JavaScript 渲染。 完整的 Chromium 在提取之前为页面加水分,因此单页应用、延迟加载部分和在初始请求后注入的内容都被捕获,而不是被漏掉。
  • 195+ 个国家的住宅代理。 地理绑定页面返回本地读者看到的内容,并且在每个会话中自动轮换——这就是实际文章与地区封锁页面之间的区别。
  • 每个会话的反检测指纹识别——用户代理、时区、语言、屏幕分辨率、WebGL 和画布每个会话都会随机化,因此高价值来源能够保持可访问,而无需每次请求调整指纹。
  • 一个原始工具,两种表现形式。 同一个云浏览器既可以作为代理驱动管道的 MCP 工具访问,也可以作为脚本驱动管道的 Python SDK 会话访问,因此相同的提取步骤可组合成任意架构。

获取 免费计划 的 API 密钥,请访问 Scrapeless


先决条件

  • Python 3.10 或更新版本。
  • 一个 Scrapeless 账户和 API 密钥——请在 app.scrapeless.com 注册,并从 设置 → API 密钥管理 复制密钥。
  • 一个嵌入模型的 API 密钥(如果计划进行嵌入,下面的示例使用 OpenAI;任何嵌入提供商通过交换一行代码即可工作)。
  • pipvenv 的基本熟悉。

完整的 SDK 和工具参考见 docs.scrapeless.com


安装

有两种方法可以访问同一个云浏览器。请选择符合谁驱动管道的选项——代理还是脚本。

选项 A — Python SDK(脚本驱动)

对于一个负责发现 → 提取 → 分块 → 嵌入循环的脚本,安装 Scrapeless Python SDK 及你打算使用的嵌入和向量存储库:

bash Copy
python -m venv .venv
source .venv/bin/activate          # Windows: .venv\Scripts\activate
pip install scrapeless openai chromadb tiktoken

将你的 API 密钥导出到当前 shell。SDK 自动从环境读取 SCRAPELESS_API_KEY

bash Copy
export SCRAPELESS_API_KEY="your_api_token_here"
export OPENAI_API_KEY="your_openai_token_here"

选项 B — MCP 服务器(代理驱动)

对于一个调用工具的 AI 代理,运行 Scrapeless MCP 服务器。它向任何支持 MCP 的客户端暴露 scrape_markdownscrape_htmlgoogle_search 及一系列浏览器工具:

bash Copy
npx -y scrapeless-mcp-server

将你的 MCP 客户端指向命令,并在服务器配置中将 API 密钥作为 SCRAPELESS_KEY 环境变量传递。代理可以直接调用 scrape_markdown


管道一目了然

Copy
发现 URLs            提取干净文本         分块以用于 RAG            嵌入 + 存储
┌──────────────┐         ┌──────────────────┐       ┌──────────────┐        ┌──────────────┐
│ google_search│         │ scrape_markdown   │       │ split into    │        │ embed each    │
│ 或网站地图   │  ────►   │ (云浏览器          │ ────► │ ~500–1000-tok │ ────►  │ chunk, upsert │
│ 或种子列表   │         │  渲染 + 清理)      │       │ overlapping   │        │ 到向量数据库  │
└──────────────┘         └──────────────────┘       │ chunks        │        └──────────────┘
                                                     └──────────────┘

四个阶段,清晰分离。发现决定 哪些 页面进入语料库;提取决定 文本有多干净;分块决定 检索的便利性;嵌入使其 可搜索。Scrapeless 管理前两个阶段——即网络反击的阶段——而标准库负责后两个阶段。


第一步 — 发现 URLs

语料库从一系列 URLs 开始。三种常见来源几乎涵盖所有情况:

  • 已经拥有的种子列表或网站地图——最简单的情况;直接跳到第二步。
  • 网站爬取——从某个部分根部开始,跟踪域内链接到有限深度。
  • 搜索发现——当相关页面事先未知时,进行搜索。

Scrapeless MCP 服务器提供了一个 google_search 工具,返回结构化行的自然结果,这是发现主题源 URL 的干净方式。每一行携带 positiontitlelinksnippetsource

python Copy
# discover.py — 从搜索查询收集候选 URLs
# (MCP 工具参数采用 camelCase;这展示了返回的形状)
results = [
json Copy
[
    {"position": 1, "title": "检索增强生成的解释", "link": "https://example.com/guides/rag-explained", "source": "example.com"},
    {"position": 2, "title": "RAG 的分块策略", "link": "https://example.com/blog/chunking-strategies", "source": "example.com"}
]

urls = [row["link"] for row in results]

保持发现阶段的真实性:去重网址,删除无关域,限制数量,然后再考虑每页渲染预算。一个聚焦的 200 个网址的语料库比一个嘈杂的 2000 个网址的要更好检索。


第 2 步 — 使用 scrape_markdown 提取干净的 Markdown

这是决定语料库质量的阶段。scrape_markdown 在反检测云浏览器中渲染网址——JavaScript 运行,页面变得可用,住宅出口保持内容可达——并将可读正文返回为干净的 Markdown。标题保持为标题,列表保持为列表,表格保持为表格,所有非内容的部分会被剔除。

代理驱动 (MCP)

当代理调用工具时,它会将 Markdown 作为工具结果返回。有一个细节对于语料库卫生很重要:无状态 MCP 工具在文本负载前加上 Response:\n\n 前缀。 在文本进入语料库之前,去掉该头部,否则它会出现在你的第一个块的顶部:

python Copy
# clean_mcp_payload.py — 在分块之前规范化 MCP 工具结果
PREFIX = "Response:\n\n"

def clean_markdown(tool_result: str) -> str:
    """从 MCP scrape_markdown 结果中去除无状态工具 'Response:' 前缀。"""
    if tool_result.startswith(PREFIX):
        tool_result = tool_result[len(PREFIX):]
    return tool_result.strip()

脚本驱动 (Python SDK)

当脚本控制循环时,使用 SDK 创建一个云浏览器会话并渲染每个网址。SDK 从环境中读取 SCRAPELESS_API_KEYproxy_country 固定住宅出口(在 SDK 上为 snake_case):

python Copy
# extract.py — 将每个发现的 URL 渲染为干净的 Markdown
from scrapeless import Scrapeless
from scrapeless.types import ICreateBrowser

client = Scrapeless()  # 读取 SCRAPELESS_API_KEY
session = client.browser.create(
    ICreateBrowser(proxy_country="US", session_ttl=240)
)

def fetch_markdown(url: str) -> str:
    """在云浏览器中渲染 URL 并返回干净的 Markdown 正文文本。"""
    # 会话在 session.browser_ws_endpoint 暴露一个 CDP 端点;
    # 驱动它导航到 `url`,让页面充水,然后读取
    # 为语料库清理后的 Markdown 正文。
    # `render_to_markdown` 是你自己的帮助程序:驱动 CDP 端点进行导航,
    # 等待充水,然后将清理后的 HTML 转换为 Markdown。对于没有帮助程序可写的
    # 一体机结果,使用上面展示的 MCP `scrape_markdown` 工具,它直接返回 Markdown。
    markdown = render_to_markdown(session, url)
    return markdown.strip()

documents = []
for url in urls:
    text = fetch_markdown(url)
    if len(text) > 200:                # 跳过接近空白或阻塞的页面
        documents.append({"url": url, "text": text})

最后的短长度保护值得保留:返回的 Markdown 只有几十个字符的页面通常是同意墙或空容器,而不是文章,应该避免污染语料库。

免费计划中获取你的 API 密钥:Scrapeless

Markdown 还是 HTML?

scrape_markdownscrape_html 提供相同的渲染。区别在于返回内容和你对其的处理:

scrape_markdown scrape_html
输出 干净可读的 Markdown 完整渲染的 HTML
标准处理 导航、脚本、广告被剔除 存在 — 自己剔除
最佳用途 LLM 训练和 RAG 输入 自定义 CSS 选择器提取
下游的令牌成本 低 — 仅内容 高 — 包含标记
结构保留 标题、列表、表格、链接 完整 DOM

对于 LLM 或 RAG 语料库,Markdown 是默认格式。它将内容而非其他东西交给嵌入模型,能比 CSS 选择器更好地适应 DOM 转换,并且在每个下游阶段占用更少的令牌。仅在需要针对特定布局运行自己的选择器时才使用 scrape_html


第 3 步 — 为 RAG 进行分块

嵌入模型具有有限的输入大小,检索在每个存储单元是连贯段落时工作最佳。分块将干净的 Markdown 拆分为重叠的窗口。实用默认值是 每块 500-1000 个令牌,重叠 10-15% — 足够大以容纳一个完整的思想,足够小以保持检索的准确性,且有重叠确保一个跨越边界的句子在至少一个块中保持完整。

python Copy
# chunk.py — 将干净的 Markdown 拆分为重叠的、令牌大小的块
import tiktoken

enc = tiktoken.get_encoding("cl100k_base")
python Copy
def chunk_text(text: str, max_tokens: int = 800, overlap: int = 100):
    """生成清理过的 Markdown 文档中的重叠令牌窗口。"""
    tokens = enc.encode(text)
    step = max_tokens - overlap
    for start in range(0, len(tokens), step):
        window = tokens[start:start + max_tokens]
        if not window:
            break
        yield enc.decode(window)

chunks = []
for doc in documents:
    for i, piece in enumerate(chunk_text(doc["text"])):
        chunks.append({
            "id": f"{doc['url']}#chunk-{i}",
            "url": doc["url"],
            "chunk_index": i,
            "text": piece,
        })

由于输入已经是干净的 Markdown,因此分块器不必处理饼干横幅或 <script> 块将段落分成两部分。在令牌窗口之前按 Markdown 标题进行分割,即使源文档具有明确的标题结构——在一个部分内分块,而不是跨越两个——这也更好地保持了相关内容在一起。

一个单一的块记录如下所示:

json Copy
{
  "id": "https://example.com/guides/rag-explained#chunk-0",
  "url": "https://example.com/guides/rag-explained",
  "chunk_index": 0,
  "token_count": 800,
  "text": "## 检索增强生成\n\nRAG 通过在查询时检索最相关的段落并将其作为上下文传递给模型,将语言模型嵌入外部语料库。检索质量直接依赖于源文本提取和分块的干净程度 ..."
}

第 4 步 — 嵌入并持久化到向量数据库

最终阶段将每个块转换为向量并存储以供检索。下面的示例使用本地 Chroma 存储和 OpenAI 嵌入;对于 pgvector、Pinecone 或任何其他向量数据库,形状是相同的——交换客户端并保持记录不变:

python Copy
# embed.py — 嵌入每个块并插入至向量存储
import chromadb
from openai import OpenAI

oai = OpenAI()
db = chromadb.PersistentClient(path=".chroma")
collection = db.get_or_create_collection("web_corpus")

def embed(texts: list[str]) -> list[list[float]]:
    resp = oai.embeddings.create(model="text-embedding-3-small", input=texts)
    return [d.embedding for d in resp.data]

batch = chunks[:64]                                   # 批量嵌入
collection.upsert(
    ids=[c["id"] for c in batch],
    documents=[c["text"] for c in batch],
    embeddings=embed([c["text"] for c in batch]),
    metadatas=[{"url": c["url"], "chunk_index": c["chunk_index"]} for c in batch],
)

urlchunk_index 元数据随每个向量传递,因此当检索到一个块时,您可以引用源页面并重新组装相邻块以获得更完整的上下文。这些元数据也是让您通过 id 进行插入或更新的原因——刷新页面会在原地替换其块,而不是复制它们。


您所获得的

存储在向量数据库中的语料库是一个干净、嵌入的、源链接块的列表。检索到的记录如下所示:

json Copy
{
  "id": "https://example.com/blog/chunking-strategies#chunk-2",
  "document": "### 重叠\n\n相邻块之间 10-15% 的重叠确保句子在至少一个窗口中完整,而这使得针对两个想法之间缝隙的查询的召回率提高 ...",
  "metadata": {
    "url": "https://example.com/blog/chunking-strategies",
    "chunk_index": 2
  },
  "distance": 0.18
}
// 模式准确反映了第 4 步的插入或更新输出。字段值是示例。

关于何时在实时网络上运行此操作的一些诚实观察:

  • Markdown 质量反映页面结构。 具有干净语义 HTML 的页面转换为优秀的 Markdown;而由通用 <div> 汤构建的页面则可接受,但可能将侧边栏说明合并到主体中。在信任大型语料库之前,先检查一部分转换页面。
  • 补水时间因网站而异。 大多数页面在 Markdown 被读取时已经完全渲染,但有些通过延迟请求补充其主要内容;对于这些页面,在读取之前,请稍等片刻以让页面稳定下来。
  • 在两个层面上去重。 在发现时删除重复的 URL,并在嵌入之前删除近重复的块(哈希或相似性阈值)——联合文章和模板页脚会膨胀语料库并偏差检索。
  • 固定输出区域以适应地理变化内容。 本地化内容的站点根据地区返回不同文本;将 proxy_country 设置为您希望在语料库中包含的区域的版本,以保持数据集一致。
  • 保持长度限制。 仅返回几十个字符的页面通常是同意墙或空容器,而不是内容——在分块之前将其过滤掉。

结论:建立可扩展的干净文本管道

发现 → 提取 → 分块 → 嵌入的过程大约缩减为六十行 Python 代码。承载阶段是提取,而 scrape_markdown 扮演了这一角色:反检测云浏览器渲染页面,住宅出口保持可达,返回的是可读的正文,形式为干净的 Markdown——嵌入模型最易读取的格式。分块和嵌入则是针对已经清理过的文本的标准库操作。

有关将同一 Scrapeless Scraping Browser 原语与具有类型化输出和向量存储的代理框架相连的内容,请参见 LangChain 集成帖子。有关组成搜索、渲染和提取工作系统的更多端到端模式,请参见 AI 代理使用案例帖子。贯穿所有模式的共通点是:锁定区域,返回 Markdown 而非 HTML,分块时有重叠,并在嵌入前进行去重。


准备构建您的 AI 驱动数据管道吗?

加入我们的社区以领取免费计划,连接正在 Scrapeless 上构建 LLM 和 RAG 数据管道的开发者:Discord · Telegram

Scrapeless 注册以获取免费的 Scraping Browser 运行时,并将上述模式调整为您的管道所需的来源、区域和分块大小。计划和限制详见 scrapeless.com/en/pricing


常见问题

问:为 LLM 或 RAG 语料库抓取网站文本合法吗?

在大多数法域,抓取公开可见的数据是广泛允许的,但各国和网站的服务条款有所不同。请查看目标网站的服务条款,尊重 robots.txt(如适用),在没有合法依据的情况下不要收集个人数据,并咨询律师以应付商业规模的语料库。构建训练或 RAG 数据集并不改变合法访问仅公开数据的基本义务。

问:为什么对 LLM 输入使用 Markdown 而非原始 HTML?

原始 HTML 主要是标记——导航、脚本、广告位、内联样式——这稀释了内容信号,增加了标记成本,并污染了嵌入。来自 scrape_markdown 的干净 Markdown 保留了页面实际涉及的标题、段落、列表、表格和链接,去掉其余部分,这样嵌入模型只读取内容而无其他。Markdown 也比 CSS 选择器提取更能经受 DOM 变化。

问:我应该使用多大块大小来进行 RAG?

一个实用的默认设置是每块 500–1000 个标记,重叠 10–15%。较小的块提高检索精度,但可能割裂一个想法;较大的块保留更多上下文,但稀释了相关性。根据您的嵌入模型输入大小和查询来调整——短小的事实查找倾向于较小的块,综合性问题则倾向于较大的块。在进行标记窗口操作前在 Markdown 标题上分割,以保持相关内容在一起。

问:我需要代理吗?

是的,对于大多数值得收集的公共来源。住宅出口使地理定位和反机器人保护的页面可达,并使重 JavaScript 页面渲染真实内容,而不是阻断页面。Scrapeless Scraping Browser 在 195 个国家通过住宅代理路由;设置 proxy_country 以锁定您所需内容版本的区域。

问:我该如何去重和清理语料库?

在两个层面进行去重:在发现阶段删除重复 URL,并在嵌入前使用内容哈希或相似性阈值删除近重复的块。由于 scrape_markdown 已经去除了样板,剩余的清理工作很轻——断长度守卫以丢弃几乎空的页面,和可选的注意标题的分割通常就足够了。

问:为什么 MCP 的 scrape_markdown 结果以 Response: 开头?

Scrapeless MCP 服务器上的无状态工具在其文本有效负载前加上 Response:\n\n。这是一种传输层头,而不是页面内容的一部分。在分块之前去掉它——步骤 2 中的一行 clean_markdown 辅助函数会处理它——这样前缀就不会出现在您的第一个块的顶部。

问:我可以在没有 AI 代理的情况下运行这个吗?

可以。步骤 2 中的 Python SDK 路径完全拥有发现 → 提取 → 分块 → 嵌入的循环,且无需涉及代理。当代理决定要收集哪些页面时,MCP 服务器是推荐的路径;当脚本决定时,SDK 是推荐的路径。

在Scrapeless,我们仅访问公开可用的数据,并严格遵循适用的法律、法规和网站隐私政策。本博客中的内容仅供演示之用,不涉及任何非法或侵权活动。我们对使用本博客或第三方链接中的信息不做任何保证,并免除所有责任。在进行任何抓取活动之前,请咨询您的法律顾问,并审查目标网站的服务条款或获取必要的许可。

最受欢迎的文章

目录