LLM 生态下爬虫程序的现状与未来
最近出现一批与 LLM 有关的新的爬虫框架,主要分为两类:一类是为 LLM 提供内容抓取解析的,比如 Jina Reader,可以将抓取的网页解析为 markdown 这样的对 LLM 友好的内容;还有一类是通过 LLM+Agent 工作流方式来构建的下一代爬虫程序,比如 ScrapeGraphAI 等。
今天我们来分析这两类爬虫框架的原理并做简单的评价。
Jina Reader
Jina Reader 是 jina 开源的针对 LLM 的解析工具,不仅开源,还提供了 API 供免费调用。在 https://r.jina.ai/<url> 中填入 URL,然后请求这个地址,就能获取到对 LLM 友好的 Parsed Content(Markdown)。
例如访问 https://r.jina.ai/https://blog.google/technology/ai/google-deepmind-isomorphic-alphafold-3-ai-model/,会得到包含标题、来源、发布时间及 Markdown 内容的结构化数据。
ps:当前访问国内地址可能受限,可自行部署。
该 API 可以通过 HTTP Header 传递控制参数:
x-set-cookie: 转发 Cookie 设置(带 Cookie 的请求不会被缓存)。
x-respond-with: 绕过 readability 过滤,支持 markdown、html、text、screenshot 模式。
x-proxy-url: 指定代理服务器。
x-no-cache: 绕过缓存(有效期 300 秒)。
x-with-generated-alt: 启用图片描述生成功能。
安装
Reader 基于 Node.js 开发,需要 Node 环境,另外还依赖 Firebase。
- Node v18 (Node 版本 >18 时构建会失败)
- Firebase CLI (
npm install -g firebase-tools)
然后 clone 代码,安装:
git clone git@github.com:jina-ai/reader.git
cd backend/functions
npm install
原理分析
主要代码在 cloud-functions/crawler.ts 里,初步看是基于 civkit 开发了一个 Web 服务,入口代码是 crawl 方法。
HTTP 接口
核心流程包括 URL 参数解析,根据 HTTP 请求头做分别处理。关键逻辑在 cachedScrap(urlToCrawl, crawlOpts, noCache) 抓取内容和 formatSnapshot 格式化抓取内容。
async crawl(
@RPCReflect() rpcReflect: RPCReflection,
@Ctx() ctx: {
req: Request,
res: Response,
},
auth: JinaEmbeddingsAuthDTO
) {
const urlToCrawl = new URL(normalizeUrl(noSlashURL.trim(), { stripWWW: false }));
const customMode = ctx.req.get('x-respond-with') || 'default';
const withGeneratedAlt = Boolean(ctx.req.get('x-with-generated-alt'));
const noCache = Boolean(ctx.req.get('x-no-cache'));
if (!ctx.req.accepts('text/plain') && ctx.req.accepts('text/event-stream')) {
const sseStream = new OutputServerEventStream();
rpcReflect.return(sseStream);
try {
for await (const scrapped of this.cachedScrap(urlToCrawl, crawlOpts, noCache)) {
const formatted = await this.formatSnapshot(customMode, scrapped, urlToCrawl);
sseStream.write({ event: 'data', data: formatted });
}
} catch (err: any) {
}
return sseStream;
}
}
网页抓取
如果 noCache 不为 false 且有 cache,会返回 cache,否则 this.puppeteerControl.scrap 抓取内容。Puppeteer 是一个 Node 库,对外提供 API 来通过 DevTools 协议控制 Chromium 或 Chrome,默认以 headless 模式运行。用 Puppeteer 的好处就是能解决一些网页 JavaScript 渲染的问题。
async *scrap(parsedUrl: URL, options: ScrappingOptions): AsyncGenerator<PageSnapshot | undefined> {
const page = await this.pagePool.acquire();
if (options.proxyUrl) {
await page.useProxy(options.proxyUrl);
}
if (options.cookies) {
await page.setCookie(...options.cookies);
}
const gotoPromise = page.goto(url, { waitUntil: ['load', 'domcontentloaded', 'networkidle0'], timeout: 30_000 });
snapshot = await page.evaluate('giveSnapshot()') as PageSnapshot;
screenshot = await page.screenshot();
}
上面的 giveSnapshot 是在初始化 page 的时候注入的 JS 代码,原理是通过 Readability 读取正文。Readability 是 Mozilla 开源的一个 Node.js 库。
preparations.push(page.evaluateOnNewDocument(READABILITY_JS));
preparations.push(page.evaluateOnNewDocument(`
function giveSnapshot() {
let parsed;
try {
parsed = new Readability(document.cloneNode(true)).parse();
} catch (err) {
void 0;
}
return {
title: document.title,
href: document.location.href,
html: document.documentElement?.outerHTML,
text: document.body?.innerText,
parsed: parsed,
imgs: [],
};
}
`));
结果处理
获取到 Snapshot 后就是如何 formatSnapshot。针对默认的 markdown,依赖 'turndown' 库将文本转换为 markdown,Turndown 是一个将 HTML 转换为 Markdown 的 Node.js 库。
if (mode === 'markdown') {
const toBeTurnedToMd = mode === 'markdown' ? snapshot.html : snapshot.parsed?.content;
let turnDownService = this.getTurndown();
for (const plugin of this.turnDownPlugins) {
turnDownService = turnDownService.use(plugin);
}
contentText = turnDownService.turndown(toBeTurnedToMd).trim();
}
Jina Reader 总结
Jina Reader 通过一个 HTTP 服务对外提供 crawl 接口,通过 Puppeteer 调用浏览器进行网页渲染抓取,过程中会注入 Readability JS 库用于正文抽取,最后返回的内容再根据用户要求返回不同的格式,比如默认的 markdown,会调用 Turndown 将 HTML 转换为 Markdown。
从实现原理上来看,这里还是常规的爬虫技术,且还是相对小众的 Node.js 爬虫技术栈,非常规的 Python 技术栈。
ScrapeGraphAI
ScrapeGraphAI 有别于 Jina Reader,可以看做是基于 LLM 与 Agent Workflow 构建的下一代网络爬虫。
官方介绍是:ScrapeGraphAI is a web scraping python library that uses LLM and direct graph logic to create scraping pipelines for websites, documents and XML files.
它是一个使用 LLM(大型语言模型)和工作流来为网站、文档和 XML 文件创建抓取管道的 Python 网络爬虫库。
安装与入门
pip install scrapegraphai
使用,假设使用 OpenAI 的 chatgpt3.5:
OPENAI_API_KEY = "YOUR API KEY"
官方有不少预设的 Graph,SmartScraperGraph 是其中之一,这个 Graph 包含抓取、解析、RAG 和生成几个处理节点。
from scrapegraphai.graphs import SmartScraperGraph
graph_config = {
"llm": {
"api_key": OPENAI_API_KEY,
"model": "gpt-3.5-turbo",
"temperature": 0,
},
}
smart_scraper_graph = SmartScraperGraph(
prompt="List me all the projects with their descriptions.",
source="https://perinim.github.io/projects/",
config=graph_config
)
result = smart_scraper_graph.run()
print(result)
先定义 Graph,设定 Prompt 指令,给一个 URL,然后 graph.run 执行,就能得到 JSON 化的抓取结果。
深入 ScrapeGraphAI
翻看源码,可以发现 ScrapeGraphAI 大量使用了 LangChain 的工具函数。ScrapeGraphAI 有几个核心概念:
- LLM Model: 官方实现了对 AzureOpenAI, Bedrock, Gemini, Groq, HuggingFace, Ollama, OpenAI, Anthropic 的支持。
- Node 处理节点: 官方实现了 FetchNode 抓取节点、ParseNode 解析节点、RAGNode 用于寻找和指令相关的片段,还有 GenerateAnswerNode 这样最后生成 Answer 的节点。
- Graph 图: 这是一个类似 Agent Workflow 的东西,类别网络、知识图谱,Node 通过 Edge 连接到一起就是图。
串起来就懂了,将 Node 串起来形成图 Graph,可以扩展 Node 增加新的功能,也可以自定义 Graph,按需编排功能。
看 Smart 的实现:
def _create_graph(self) -> BaseGraph:
fetch_node = FetchNode(input="url | local_dir", output=["doc"])
parse_node = ParseNode(input="doc", output=["parsed_doc"], node_config={"chunk_size": self.model_token})
rag_node = RAGNode(input="user_prompt & (parsed_doc | doc)", output=["relevant_chunks"], node_config={...})
generate_answer_node = GenerateAnswerNode(input="user_prompt & (relevant_chunks | parsed_doc | doc)", output=["answer"], node_config={...})
return BaseGraph(
nodes=[fetch_node, parse_node, rag_node, generate_answer_node],
edges=[(fetch_node, parse_node), (parse_node, rag_node), (rag_node, generate_answer_node)],
entry_point=fetch_node
)
这里的每个 Node,有 Input,Output,每个节点执行时有一个 State 状态 Dict,Node 的 Input 从 State 里取值,执行完成后 Output 作为 Key,这个 Node 的结果作为 Value 放回 State。
关键 Node 分析
FetchNode: 负责获取指定 URL 的 HTML 内容,使用 LangChain 的 AsyncChromiumLoader 异步获取内容。
ParseNode: 负责从文档中解析 HTML 内容的节点。解析后的内容被分割成块,以便进一步处理。这里直接用 LangChain 的 Html2TextTransformer 解析正文。
RAGNode: 看名字 RAG 就知道了,负责将文档 Chunk 向量化存储到向量库进行检索的节点。它利用 Embeddings 和 FAISS 等工具进行相似度检索和过滤。
GenerateAnswerNode: 使用大型语言模型(LLM)根据用户的输入和从网页中提取的内容生成答案。它从用户输入和抓取的内容构建一个 Prompt,将其输入 LLM,并解析 LLM 的响应以产生答案。区分了多 Chunk 和单 Chunk 情况,多 Chunk 的最后涉及到 Merge 合并。
ScrapeGraphAI 总结
ScrapeGraphAI 利用 LangChain,扩展出一套框架,可以根据用户需求去抓取和解析网页中的指定部分内容,官方提供了一些基础实现,可以满足一些简单任务的抓取,但是对于更复杂的任务,如果结合 Agents 更好的来实现,还需要继续完善。另外是否可以与 CV 模型、多模态模型结合延伸出更有趣的解析功能?
两种方案对比
| 特性 | Jina Reader | ScrapeGraphAI |
|---|
| 核心技术 | Puppeteer + Readability + Turndown | LangChain + LLM + Graph Workflow |
| 主要语言 | Node.js | Python |
| 输出形式 | 结构化 Markdown/HTML/Text | 结构化 JSON/自然语言回答 |
| 适用场景 | 快速提取网页全文,标准化内容 | 复杂信息抽取,特定字段提取,问答式抓取 |
| 灵活性 | 中等,主要通过 Header 控制 | 高,可通过自定义 Graph 和 Nodes 编排 |
| 成本 | 低(API 免费或自建),无 Token 消耗 | 较高(依赖 LLM API 调用,按 Token 计费) |
面临的挑战与未来
虽然 LLM 为爬虫带来了新的解决方案,但也面临一些挑战:
- 成本问题: 基于 LLM 的爬虫通常涉及大量的 Token 消耗,对于大规模抓取任务,API 调用成本可能远高于传统爬虫。
- 延迟问题: LLM 推理需要时间,相比直接解析 HTML,基于 Agent 的工作流响应速度较慢,不适合实时性要求极高的场景。
- 准确性问题: LLM 存在幻觉风险,在提取具体数据时可能出现偏差,需要配合验证机制。
- 稳定性: 依赖外部大模型服务的稳定性,一旦服务波动,整个爬虫链路可能中断。
未来发展方向可能集中在:
- 混合架构: 结合传统爬虫的高效性与 LLM 的理解能力,例如先用传统爬虫获取内容,再用 LLM 进行二次清洗和结构化。
- 本地化部署: 随着小模型能力的提升,在本地运行轻量级 LLM 进行解析,降低 API 成本和延迟。
- 多模态融合: 结合视觉模型(CV)处理截图、图表等非文本信息的解析。
小结
本文分析了 Jina Reader 和 ScrapeGraphAI 两块具有代表性的 LLM 时代的抓取工具功能、实现原理。可以看出 LLM 出来后对爬虫程序有了新的要求,LLM 也给爬虫带来了新的解决方案。传统的基于规则或正则的爬虫在面对复杂动态网页和非结构化数据时显得力不从心,而 LLM 凭借其强大的语义理解能力,能够更精准地提取目标信息。LLM+ 爬虫后续会有怎么进一步的发展方向呢,我们拭目以待!
未来的爬虫系统可能会更加智能化,不再仅仅是数据的搬运工,而是成为数据的理解者和加工者。开发者需要根据具体的业务场景,权衡成本、速度和精度,选择合适的技术方案。无论是选择 Jina Reader 这种高效的解析工具,还是 ScrapeGraphAI 这种灵活的 Agent 框架,核心目标都是更高效地获取高质量的数据,服务于下游的 AI 应用。