本页内容
LlamaIndex 是生产级 RAG 的首选框架,但它自带的 HTML 读取器在重 JavaScript 的站点和受 Cloudflare 保护的页面上会崩溃。把它们换成 CrawlForge,你的 LlamaIndex pipeline 就能处理任何 URL —— 静态 HTML、SPA 或反爬虫墙。
from llama_index.core import Document
from crawlforge_llamaindex import CrawlForgeReader
reader = CrawlForgeReader(api_key="cf_live_your_key")
docs: list[Document] = reader.load_data(urls=["https://docs.stripe.com/api"])本指南展示如何以 CrawlForge 作为数据源来进行 LlamaIndex web scraping —— 从单页加载器到完整的 RAG pipeline 和 agent 工具。
目录
- 为什么 LlamaIndex 需要更好的网页读取器
- 前置条件
- 步骤 1:安装依赖
- 步骤 2:构建一个 CrawlForge 读取器
- 步骤 3:索引实时网页
- 步骤 4:查询索引
- 完整示例:带实时更新的文档 RAG
- 进阶:为 LlamaIndex agent 提供 CrawlForge 工具
- 故障排查
- 常见问题
为什么 LlamaIndex 需要更好的网页读取器
LlamaIndex 内置的 SimpleWebPageReader 和 BeautifulSoupWebReader 处理静态博客文章没问题,但在以下情况会失败:
- 由 JavaScript 渲染的内容(React、Vue、Angular 应用)
- 受 Cloudflare / DataDome / Akamai 保护的页面(大多数 SaaS 文档)
- 对通用 User-Agent 返回 403 的站点
- 主要内容位于某个
<main>同级元素中、无法轻易提取的页面
CrawlForge 解决了这全部四种情况。它的 extract_content 工具使用了针对文章、文档和产品页面调优的可读性算法。stealth_mode 应对反爬虫。scrape_with_actions 执行 JavaScript。全部 26 个工具都返回干净的文本或 markdown,可直接用于分块。要了解这在 RAG 中为何重要,请参阅我们的 RAG pipeline 指南。
前置条件
- Python 3.9+ --
python --version - LlamaIndex --
pip install llama-index-core llama-index-readers-web - CrawlForge 账户 —— 在 crawlforge.dev/signup 免费注册,含 1,000 credits
- OpenAI 或 Anthropic API key,用于 LlamaIndex 的 LLM 调用(或使用任何受支持的提供商)
步骤 1:安装依赖
pip install llama-index-core llama-index-embeddings-openai requests导出你的密钥:
export CRAWLFORGE_API_KEY="cf_live_your_key_here"
export OPENAI_API_KEY="sk-..."步骤 2:构建一个 CrawlForge 读取器
LlamaIndex 读取器继承自 BaseReader 并返回 Document 对象。下面是一个封装了 CrawlForge extract_content endpoint 的最小读取器:
# crawlforge_reader.py
import os
from typing import List, Optional
import requests
from llama_index.core.readers.base import BaseReader
from llama_index.core.schema import Document
class CrawlForgeReader(BaseReader):
"""LlamaIndex reader that uses CrawlForge for web scraping."""
BASE_URL = "https://crawlforge.dev/api/v1/tools"
def __init__(self, api_key: Optional[str] = None, use_stealth: bool = False):
self.api_key = api_key or os.environ["CRAWLFORGE_API_KEY"]
self.use_stealth = use_stealth
def load_data(self, urls: List[str]) -> List[Document]:
documents = []
tool = "stealth_mode" if self.use_stealth else "extract_content"
for url in urls:
response = requests.post(
f"{self.BASE_URL}/{tool}",
headers={
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json",
},
json={
"url": url,
"options": {"format": "markdown"},
},
timeout=30,
)
response.raise_for_status()
data = response.json()
documents.append(
Document(
text=data.get("content", ""),
metadata={
"source": url,
"title": data.get("title"),
"scraped_at": data.get("scraped_at"),
},
)
)
return documents费用:使用 extract_content 每个 URL 2 credits,使用 stealth_mode 为 5 credits。
步骤 3:索引实时网页
把读取器接入标准的 LlamaIndex pipeline:
# build_index.py
from llama_index.core import VectorStoreIndex
from crawlforge_reader import CrawlForgeReader
reader = CrawlForgeReader()
docs = reader.load_data(urls=[
"https://docs.stripe.com/api/charges/create",
"https://docs.stripe.com/api/payment_intents/create",
"https://docs.stripe.com/api/refunds/create",
])
index = VectorStoreIndex.from_documents(docs)
index.storage_context.persist(persist_dir="./storage/stripe_docs")现在你就有了一个由实时文档构建、并持久化的 Stripe API 索引。费用:6 credits(3 个 URL x 2)。
步骤 4:查询索引
# query_index.py
from llama_index.core import StorageContext, load_index_from_storage
storage = StorageContext.from_defaults(persist_dir="./storage/stripe_docs")
index = load_index_from_storage(storage)
query_engine = index.as_query_engine()
response = query_engine.query(
"What are the required fields to create a Charge in Stripe's API?"
)
print(response)
# -> "To create a Charge, you must provide amount (integer in cents) and
# currency (three-letter ISO code). Additionally you need a source
# (payment method) or customer."完整示例:带实时更新的文档 RAG
把它们组合起来 —— 一个每晚刷新的 Stripe 文档 RAG:
# docs_rag.py
import os
from datetime import datetime
from llama_index.core import VectorStoreIndex, StorageContext, load_index_from_storage
from crawlforge_reader import CrawlForgeReader
PERSIST_DIR = "./storage/stripe_docs"
TARGET_URLS = [
"https://docs.stripe.com/api/charges/create",
"https://docs.stripe.com/api/payment_intents/create",
"https://docs.stripe.com/api/refunds/create",
"https://docs.stripe.com/api/customers/create",
"https://docs.stripe.com/api/subscriptions/create",
]
def refresh_index() -> VectorStoreIndex:
"""Re-scrape sources and rebuild the index."""
reader = CrawlForgeReader()
docs = reader.load_data(urls=TARGET_URLS)
index = VectorStoreIndex.from_documents(docs)
index.storage_context.persist(persist_dir=PERSIST_DIR)
print(f"Indexed {len(docs)} docs at {datetime.utcnow().isoformat()}Z")
return index
def load_index() -> VectorStoreIndex:
"""Load the persisted index from disk."""
storage = StorageContext.from_defaults(persist_dir=PERSIST_DIR)
return load_index_from_storage(storage)
def ask(question: str) -> str:
index = load_index() if os.path.exists(PERSIST_DIR) else refresh_index()
return str(index.as_query_engine().query(question))
if __name__ == "__main__":
# Refresh once a day in a cron job: python docs_rag.py --refresh
import sys
if "--refresh" in sys.argv:
refresh_index()
else:
print(ask("How do I create a refund for a charge?"))每晚刷新费用:10 credits(5 个 URL x 2)。30 天下来是 300 credits —— 远在免费套餐范围之内。
进阶:为 LlamaIndex agent 提供 CrawlForge 工具
LlamaIndex 的 agent 系统接受任意的 FunctionTool 定义。把 CrawlForge 调用封装成工具,你的 agent 就能按需 scraping:
# crawlforge_tools.py
from llama_index.core.tools import FunctionTool
from crawlforge_reader import CrawlForgeReader
def scrape_url(url: str) -> str:
"""Scrape a URL and return its main content as markdown."""
reader = CrawlForgeReader()
docs = reader.load_data(urls=[url])
return docs[0].text if docs else ""
def search_and_scrape(query: str, n: int = 3) -> list[str]:
"""Search the web and return content from the top N results."""
import os, requests
resp = requests.post(
"https://crawlforge.dev/api/v1/tools/search_web",
headers={"Authorization": f"Bearer {os.environ['CRAWLFORGE_API_KEY']}"},
json={"query": query, "limit": n},
timeout=30,
).json()
urls = [r["url"] for r in resp.get("results", [])]
reader = CrawlForgeReader()
return [d.text for d in reader.load_data(urls=urls)]
scrape_tool = FunctionTool.from_defaults(fn=scrape_url)
search_tool = FunctionTool.from_defaults(fn=search_and_scrape)然后把 [scrape_tool, search_tool] 传给任何 LlamaIndex agent:
from llama_index.core.agent import ReActAgent
from llama_index.llms.openai import OpenAI
agent = ReActAgent.from_tools(
tools=[scrape_tool, search_tool],
llm=OpenAI(model="gpt-4o-mini"),
verbose=True,
)
response = agent.chat(
"Research the current state of Anthropic's MCP protocol adoption in 2026. "
"Cite at least 3 sources."
)
print(response)credits 费用明细
| 操作 | 工具 | Credits |
|---|---|---|
| 摄取一个静态页面 | extract_content | 2 |
| 摄取一个重 JS 的页面 | scrape_with_actions | 5 |
| 摄取一个受 Cloudflare 保护的页面 | stealth_mode | 5 |
| agent 搜索 + scraping(3 个 URL) | search_web + 3x extract_content | 11 |
| 完整深度研究 | deep_research | 10 |
故障排查
某些 URL 的 Document.text 为空 —— 该页面很可能需要 JavaScript。用 use_stealth=True 实例化,或构建一个调用 scrape_with_actions 的读取器变体。
requests.exceptions.HTTPError: 429 —— 你触发了 CrawlForge 的速率限制。添加带退避的重试,或将批量加载拆分为每批 10 个 URL。
LlamaIndex 索引很慢 —— 用 concurrent.futures.ThreadPoolExecutor 批量并发执行你的读取器调用(这是 I/O 密集型,GIL 不是瓶颈)。在 50 个以上 URL 上通常能获得 10 倍提速。
Document 元数据缺失 —— CrawlForge 的 scrape_structured endpoint 不会像 extract_content 那样填充 title。RAG 摄取请坚持使用 extract_content;仅在做带类型的字段提取时才使用 scrape_structured。
embedding 成本暴涨 —— LlamaIndex 在每次调用 VectorStoreIndex.from_documents 时都会重新生成 embedding。用 index.storage_context.persist() 持久化,并用 load_index_from_storage() 加载,以避免重复工作。
下一步
- 阅读 RAG pipeline 指南,了解端到端的检索模式
- 在我们的 LangChain 集成文章中探索其他框架
- 查看入门文档,了解完整的 REST API
- 在 Firecrawl 替代方案中对比各家 scraping 供应商
在 crawlforge.dev/signup 免费开始,赠送 1,000 credits。无需信用卡。
亲自试一试——无需注册
在 Playground 中运行 CrawlForge 的 27 个抓取与提取工具中的任意一个,然后免费开始,获取 1,000 credits。
1,000 免费 credits • 每月补充 • 无需信用卡
标签
及时获取最新洞察
将教程、产品更新与 Web 抓取技巧直接发送到你的收件箱。
拒绝垃圾邮件,随时可取消订阅。