跳到主要内容

Crawl4AI — 架构与原理

30 秒导读: Crawl4AI 是一个开源异步网页爬虫,专门为「喂给大模型」而生。你给它一个 URL,它用真实浏览器把页面抓下来,清掉导航/广告/脚本,转成带标题、表格、引用编号的干净 Markdown;需要的话还能按你给的 CSS schema 或一句自然语言抽出结构化字段。全程不需要任何第三方 API key(除非你主动开 LLM 抽取)。

1. 这是什么(零基础也能懂)

一句话定义: 把「网页」变成「LLM 能直接读的干净 Markdown + 结构化数据」的爬虫库。

解决什么问题 / 给谁用。 假设你在做 RAG(检索增强生成)或者一个会上网的 agent,你需要把几百个网页的正文喂给模型。直接拿原始 HTML 喂?里面全是 <script>、导航栏、广告、cookie 弹窗、嵌套 <div>——既烧 token 又干扰模型。Crawl4AI 就是站在「拿到网页」和「喂给模型」之间的那一层:把脏 HTML 洗成干净文本

它的典型使用者:

  • RAG 数据管道的工程师——批量把文档站点转成 Markdown 入库。
  • 联网 agent 的人——让 agent 实时读网页。
  • 数据抓取的人——从商品页、列表页抽结构化字段。

它能做什么(功能):

  • 用真实浏览器(Playwright)抓取,能跑 JS、点按钮、滚动懒加载页面。
  • 把 HTML 清洗成干净 Markdown,链接转成引用编号 ⟨1⟩ + 文末 References。
  • 生成 fit_markdown——只留「正文」、扔掉边角料的精简版。
  • 三种结构化抽取:CSS/XPath schema(纯 Python,免费)、正则、LLM。
  • 多页爬取:深度爬(BFS/DFS/最佳优先)和「答够了就停」的自适应爬。
  • 缓存、并发调度、反爬重试、代理轮换、会话复用。

用起来什么样。 最小的真实例子(来自 README):

import asyncio
from crawl4ai import AsyncWebCrawler

async def main():
async with AsyncWebCrawler() as crawler: # 启动浏览器
result = await crawler.arun(url="https://example.com")
print(result.markdown) # 干净 Markdown

asyncio.run(main())

或者命令行一行: crwl https://example.com -o markdown

一句话直觉/类比。 把 Crawl4AI 想成网页世界的「榨汁机」:丢进去一整颗带皮带核的水果(原始 HTML),出来一杯只剩果汁的东西(Markdown)。而且每一道工序——怎么抓、怎么削皮、怎么过滤、怎么装瓶——都是可换的零件(strategy),你不满意默认件就换一个。

2. 顶层全景(它大概怎么转)

Crawl4AI 的核心是一条单页处理流水线:AsyncWebCrawler.arun(url) 是入口,它把工作派给一串可插拔策略,最后吐出一个 CrawlResult

怎么读这张图: 从左到右是一次 arun() 的数据流向;每个方框是一个可替换的策略对象,下面标了它在哪个文件。

URL


┌──────────────┐ 命中缓存? ┌─────────────┐
│ AsyncWebCrawler├───────────►│ SQLite 缓存 │ async_database.py
│ .arun() │◄───────────┤ (~/.crawl4ai)│
└──────┬───────┘ 未命中 └─────────────┘
│ 抓取

┌────────────────────┐
│ CrawlerStrategy │ async_crawler_strategy.py
│ (Playwright 浏览器 │ → 跑 JS / 滚动 / 截图
│ 或 HTTP 轻量抓取) │ → 原始 HTML
└────────┬───────────┘
│ 反爬检测 + 重试 + 代理轮换

┌────────────────────┐
│ aprocess_html() │ async_webcrawler.py ← 处理主线
│ ① ScrapingStrategy│ 清洗 DOM → cleaned_html + media/links/tables
│ ② MarkdownGen │ HTML → Markdown(+引用化 + fit_markdown)
│ ③ ExtractionStrat │ 可选:结构化抽取(schema/正则/LLM)
└────────┬───────────┘


CrawlResult (html / cleaned_html / markdown / extracted_content / media / links …)

部件一句话职责:

部件干什么在哪个文件
AsyncWebCrawler总编排:缓存判定 → 抓取 → 反爬重试 → 调 aprocess_htmlasync_webcrawler.py
AsyncPlaywrightCrawlerStrategy用真浏览器抓页面(跑 JS、滚动、截图)async_crawler_strategy.py
AsyncHTTPCrawlerStrategy不开浏览器的轻量 HTTP 抓取async_crawler_strategy.py
LXMLWebScrapingStrategy清洗 DOM,产出 cleaned_html + 媒体/链接/表格content_scraping_strategy.py
DefaultMarkdownGeneratorHTML→Markdown,链接转引用,生成 fit_markdownmarkdown_generation_strategy.py
PruningContentFilter / BM25ContentFilter决定哪些块进 fit_markdowncontent_filter_strategy.py
JsonCss/Regex/LLMExtractionStrategy抽结构化数据extraction_strategy.py
DeepCrawlStrategy / AdaptiveCrawler多页:深爬 / 信息够了就停deep_crawling/adaptive_crawler.py
MemoryAdaptiveDispatcherarun_many 的并发调度 + 内存自适应async_dispatcher.py

主线走一遍(高层,不进代码):

  1. 你调 crawler.arun(url, config)
  2. 看缓存:config.cache_mode 决定要不要读缓存。命中就直接返回。
  3. 没命中就:交给 crawler strategy,得到原始 HTML(顺带截图/PDF/网络日志)。
  4. 反爬检查:is_blocked() 看是不是被 Cloudflare/Akamai 拦了;被拦就换代理重试,最后还能调 fallback_fetch_function
  5. 处理(aprocess_html):清洗 → Markdown → (可选)抽取。
  6. 写缓存,返回 CrawlResult

3. 阅读地图(建议顺序)

这个项目很大(核心模块单文件动辄两三千行)。本文按流水线顺序拆成 6 章,建议从头读:

  1. 01-pipeline.md — 先看主线:arun() 怎么把缓存、抓取、反爬、处理串起来。读完你就有了整张地图。
  2. 02-fetch-browser.md — 抓取层:Playwright 怎么跑 JS、smart_wait 怎么等页面、虚拟滚动怎么处理无限列表。
  3. 03-clean-and-markdown.md — 清洗 + Markdown 化:DOM 怎么被洗干净、链接怎么变成 ⟨1⟩ 引用。
  4. 04-content-filters.md — fit_markdown 的两条路:Pruning 启发式打分裁剪 vs BM25 查询相关裁剪。这是「LLM 友好」的核心。
  5. 05-extraction.md — 结构化抽取:免费的 CSS schema、纯正则、以及 LLM 抽取的取舍。
  6. 06-deep-and-adaptive.md — 多页:深爬策略 + 自适应爬取「信息够了就停」的统计学。

如果你只想用、不想钻原理:读完第 1 章 + 第 4 章就够建一个 RAG 管道了。

4. 巧妙之处(先记住这几条)

  • 一切皆策略。 抓取、清洗、Markdown、过滤、抽取、深爬——每一步都是抽象基类 + 可替换实现。CrawlerRunConfig 把它们组装起来。这让「换个清洗算法」不用改主流程。
  • fit_markdown 是杀手锏。 普通 Markdown 还是太多;fit_markdown 用启发式或 BM25 只留正文,直接喂模型省 token。见第 4 章。
  • 反爬是「检测 + 救援」两段式。 is_blocked() 宁可误报(便宜,有 fallback 兜底)也不漏报(漏了用户拿到垃圾),见 antibot_detector.py 顶部注释。
  • 自适应爬取把「爬够了吗」量化成数学。 coverage / consistency / saturation 三个指标算出一个 confidence,够阈值就停,见第 6 章。

5. 边界与局限(诚实)

  • 重依赖浏览器。 默认走 Playwright + Chromium,装起来不轻(要 crawl4ai-setup 下载浏览器)。要轻量可用 AsyncHTTPCrawlerStrategy,但就跑不了 JS 了。
  • 反爬是军备竞赛。 内置检测能识别 Cloudflare/Akamai/PerimeterX 等的拦截页,但「识别被拦」不等于「能绕过」;绕过靠 undetected 浏览器适配器 + 代理,且不保证成功。
  • LLM 抽取/过滤要花钱、要联网。 schema/正则路径是纯 Python 免费;一旦用 LLMExtractionStrategyLLMContentFilter 就走 litellm 调外部模型。
  • 自适应爬取的统计策略偏简单。 coverage 用的是查询词的 TF/DF,不是语义理解(语义版需要 embedding 模型,见第 6 章)。

6. 横向对比(同 shelf 的 RAG/检索兄弟)

Crawl4AI 在 rag-retrieval 区里占的是**「数据入口/ingestion」这一格:它不做向量检索、不做 rerank,而是把网页洗成可入库的干净文本**。下游才接 embedding、向量库、检索。换句话说,别的 RAG 库回答「我有干净文档了,怎么检索」;Crawl4AI 回答「文档还在脏网页里,怎么洗出来」。

7. 代码地图(导航索引)

主题文件路径符号名
总编排 / 主线crawl4ai/async_webcrawler.pyAsyncWebCrawler.arunAsyncWebCrawler.aprocess_html
浏览器抓取crawl4ai/async_crawler_strategy.pyAsyncPlaywrightCrawlerStrategy._crawl_websmart_wait
HTTP 轻量抓取crawl4ai/async_crawler_strategy.pyAsyncHTTPCrawlerStrategy.crawl
DOM 清洗crawl4ai/content_scraping_strategy.pyLXMLWebScrapingStrategy.scrapprocess_element
Markdown 生成crawl4ai/markdown_generation_strategy.pyDefaultMarkdownGenerator.generate_markdownconvert_links_to_citations
内容过滤crawl4ai/content_filter_strategy.pyPruningContentFilter._prune_treeBM25ContentFilter.filter_content
结构化抽取crawl4ai/extraction_strategy.pyJsonElementExtractionStrategy.extractLLMExtractionStrategy.runRegexExtractionStrategy
深度爬取crawl4ai/deep_crawling/bfs_strategy.pyBFSDeepCrawlStrategy.link_discovery
自适应爬取crawl4ai/adaptive_crawler.pyStatisticalStrategy.calculate_confidenceCrawlState
并发调度crawl4ai/async_dispatcher.pyMemoryAdaptiveDispatcher.run_urls
反爬检测crawl4ai/antibot_detector.pyis_blocked
缓存模式crawl4ai/cache_context.pyCacheModeCacheContext
结果模型crawl4ai/models.pyCrawlResultMarkdownGenerationResult