Crawl4AI 快速上手:从首次爬取到 Markdown 生成、结构化抽取、自适应爬取与多 URL 并发
本文以 Crawl4AI 官方快速上手文档(docs/md_v2/core/quickstart.md)为主体,带你完成一条完整的实战链路:用最小配置发起第一次爬取、理解 BrowserConfig/CrawlerRunConfig 两套核心配置与缓存模式、掌握 Markdown 输出与内容过滤、分别实践 CSS 与 LLM 两种结构化数据抽取、体验自适应爬取(AdaptiveCrawler)与 arun_many 多 URL 并发,最后处理 JavaScript 动态加载页面。所有关键实现均对照当前仓库源码给出文件路径与行号,便于逐行验证。
1. 核心概念:Crawl4AI 提供了什么
Crawl4AI 是一个面向 LLM 的开源 Web 爬虫与抓取框架,快速上手阶段你会直接用到以下四个核心构件(均从包根 crawl4ai/__init__.py 导出):
AsyncWebCrawler:异步爬虫主入口,负责浏览器生命周期管理与页面抓取,实现于 async_webcrawler.py;BrowserConfig/CrawlerRunConfig:分别控制“浏览器如何启动”与“每次抓取如何执行”,定义于 async_configs.py 与 async_configs.py;DefaultMarkdownGenerator:自动将 HTML 转为 Markdown,可选搭配内容过滤器;- 多种抽取策略:
JsonCssExtractionStrategy(CSS 选择器)、LLMExtractionStrategy(大模型解析)等,统一定义于 extraction_strategy.py。
完成本文后,你将能够独立编写覆盖“静态页 → 动态页 → 结构化数据 → 并发批处理”的完整抓取脚本。
2. 第一次爬取:最小可用脚本
下面是最小 Python 脚本:创建 AsyncWebCrawler,抓取一个网页,并打印其 Markdown 输出的前 300 个字符:
import asyncio
from crawl4ai import AsyncWebCrawler
async def main():
async with AsyncWebCrawler() as crawler:
result = await crawler.arun("https://example.com")
print(result.markdown[:300]) # Print first 300 chars
if __name__ == "__main__":
asyncio.run(main())
这里发生了什么?
AsyncWebCrawler启动一个无头浏览器(默认 Chromium)。从 BrowserConfig 的文档字符串 可以看到:browser_type默认"chromium",headless默认True,browser_mode默认"dedicated"(每次创建独立浏览器实例);arun()拉取https://example.com页面,方法签名见 arun()。url参数支持http://、https://、file://(本地文件)与raw:(原始 HTML)四类来源;- Crawl4AI 自动把 HTML 转换为 Markdown,写入
result.markdown。
仓库中的 hello_world 示例 给出了同款写法的官方变体:传入 BrowserConfig(headless=False, verbose=True) 以便肉眼观察浏览器过程,并用 PruningContentFilter 生成 Markdown。
arun() 还有一个值得注意的自动行为:若爬虫尚未 start(),它会自动启动浏览器(if not self.ready: await self.start(),见 async_webcrawler.py#L246-L248),因此脚本中不需要显式调用 start()。
3. 基础配置:BrowserConfig 与 CrawlerRunConfig 分工
Crawl4AI 的爬虫高度可定制,核心是两类配置对象:
BrowserConfig:控制浏览器行为——无头与否、user agent、JavaScript 开关、视口、代理、CDP 端点等;CrawlerRunConfig:控制每次抓取如何运行——缓存模式、抽取策略、超时、等待条件、会话复用等。
最小用法示例:
import asyncio
from crawl4ai import AsyncWebCrawler, BrowserConfig, CrawlerRunConfig, CacheMode
async def main():
browser_conf = BrowserConfig(headless=True) # or False to see the browser
run_conf = CrawlerRunConfig(
cache_mode=CacheMode.BYPASS
)
async with AsyncWebCrawler(config=browser_conf) as crawler:
result = await crawler.arun(
url="https://example.com",
config=run_conf
)
print(result.markdown)
if __name__ == "__main__":
asyncio.run(main())
重要:
CrawlerRunConfig中cache_mode的构造期默认值就是CacheMode.BYPASS,即默认绕过缓存以获取新鲜内容,见 async_configs.py#L1617。若要启用缓存请显式设置CacheMode.ENABLED。
3.1 CacheMode 的完整取值
CacheMode 是一个五值枚举,定义在 cache_context.py#L4-L20:
| 模式 | 行为 |
|---|---|
CacheMode.ENABLED |
正常读写缓存 |
CacheMode.DISABLED |
完全不使用缓存 |
CacheMode.READ_ONLY |
只读缓存,不写 |
CacheMode.WRITE_ONLY |
只写缓存,不读 |
CacheMode.BYPASS |
本次操作完全绕过缓存(默认) |
从源码结构看,arun() 内部还有一层兜底:若 config.cache_mode is None(例如你手动构造了配置对象且未设置该字段),会在运行时改为 CacheMode.ENABLED,见 async_webcrawler.py#L259-L261。正常通过 CrawlerRunConfig() 创建配置时不会触发这条分支,因为默认值已是 BYPASS。
3.2 常用默认值速查(来自源码)
结合 CrawlerRunConfig 的类文档 与构造函数签名,快速上手阶段最常用的参数默认值如下:
| 参数 | 默认值 | 说明 |
|---|---|---|
cache_mode |
CacheMode.BYPASS |
缓存行为,见上表 |
page_timeout |
60000(毫秒) |
页面导航等操作超时,PAGE_TIMEOUT 常量 |
wait_until |
"domcontentloaded" |
导航等待条件 |
word_count_threshold |
MIN_WORD_THRESHOLD(约 200) |
内容处理前的最低词数阈值 |
wait_for |
None |
CSS 选择器或 JS 条件,抽取前等待 |
session_id |
None |
设置后复用同一页面实例以保留状态(多步交互场景关键参数) |
extraction_strategy |
None |
结构化抽取策略 |
markdown_generator |
None |
Markdown 生成策略,为 None 时使用内置默认生成器 |
BrowserConfig 侧常用默认值:viewport_width=1080、viewport_height=600、device_scale_factor=1.0、chrome_channel="chromium"(见 async_configs.py#L727-L738)。
更高级的配置(代理、PDF 输出、多标签会话等)可在仓库文档中继续深入,例如 安装指南 与 Hooks & Auth 进阶。
4. Markdown 输出:raw_markdown 与 fit_markdown
默认情况下 Crawl4AI 会为每个页面自动生成 Markdown,但确切输出取决于你是否指定了 markdown generator 或 content filter:
result.markdown(raw_markdown):直接的 HTML 到 Markdown 转换结果;result.markdown.fit_markdown:对同一内容应用所配置的内容过滤器(如PruningContentFilter)之后的结果。
4.1 搭配 DefaultMarkdownGenerator 与过滤器的完整示例
from crawl4ai import AsyncWebCrawler, CrawlerRunConfig, CacheMode
from crawl4ai.content_filter_strategy import PruningContentFilter
from crawl4ai.markdown_generation_strategy import DefaultMarkdownGenerator
md_generator = DefaultMarkdownGenerator(
content_filter=PruningContentFilter(threshold=0.4, threshold_type="fixed")
)
config = CrawlerRunConfig(
cache_mode=CacheMode.BYPASS,
markdown_generator=md_generator
)
async with AsyncWebCrawler() as crawler:
result = await crawler.arun("https://news.ycombinator.com", config=config)
print("Raw Markdown length:", len(result.markdown.raw_markdown))
print("Fit Markdown length:", len(result.markdown.fit_markdown))
注意:如果你不指定内容过滤器或 markdown generator,通常只能看到原始 Markdown(raw_markdown)。PruningContentFilter 大约会增加 50ms 的处理时间。仓库中除剪枝外还提供 BM25ContentFilter、LLMContentFilter 等过滤器,均在 content_filter_strategy.py 中定义并从包根导出(见 __init__.py#L39-L44),可根据页面噪声水平选用。这些策略的深入用法参见专门的 Markdown Generation 教程 markdown-generation.md。
5. CSS 结构化抽取(无 LLM 成本)
Crawl4AI 可以使用 CSS 或 XPath 选择器把页面抽取为结构化 JSON。
新特性:Crawl4AI 提供了用 LLM 自动生成抽取 schema 的工具(
JsonCssExtractionStrategy.generate_schema,静态方法实现于 extraction_strategy.py#L1692)。这是一次性成本,之后即可用生成的 schema 做快速、免 LLM 的重复抽取:
from crawl4ai import JsonCssExtractionStrategy
from crawl4ai import LLMConfig
# Generate a schema (one-time cost)
html = "<div class='product'><h2>Gaming Laptop</h2><span class='price'>$999.99</span></div>"
# Using OpenAI (requires API token)
schema = JsonCssExtractionStrategy.generate_schema(
html,
llm_config = LLMConfig(provider="openai/gpt-4o",api_token="your-openai-token") # Required for OpenAI
)
# Or using Ollama (open source, no token needed)
schema = JsonCssExtractionStrategy.generate_schema(
html,
llm_config = LLMConfig(provider="ollama/llama3.3", api_token=None) # Not needed for Ollama
)
# Use the schema for fast, repeated extractions
strategy = JsonCssExtractionStrategy(schema)
关于 schema 生成与进阶用法的完整指南,参见 No-LLM Extraction Strategies。
5.1 手工定义 schema 的最小抽取示例
import asyncio
import json
from crawl4ai import AsyncWebCrawler, CrawlerRunConfig, CacheMode
from crawl4ai import JsonCssExtractionStrategy
async def main():
schema = {
"name": "Example Items",
"baseSelector": "div.item",
"fields": [
{"name": "title", "selector": "h2", "type": "text"},
{"name": "link", "selector": "a", "type": "attribute", "attribute": "href"}
]
}
raw_html = "<div class='item'><h2>Item 1</h2><a href='https://example.com/item1'>Link 1</a></div>"
async with AsyncWebCrawler() as crawler:
result = await crawler.arun(
url="raw://" + raw_html,
config=CrawlerRunConfig(
cache_mode=CacheMode.BYPASS,
extraction_strategy=JsonCssExtractionStrategy(schema)
)
)
# The JSON output is stored in 'extracted_content'
data = json.loads(result.extracted_content)
print(data)
if __name__ == "__main__":
asyncio.run(main())
这个示例同时演示了两个要点:
- schema 结构:
name(命名)、baseSelector(基础选择器,定位每个重复条目)、fields(字段列表,type支持text取文本、attribute配合attribute键取属性值)。JsonCssExtractionStrategy类定义于 extraction_strategy.py#L1989,同族还有JsonXPathExtractionStrategy、JsonLxmlExtractionStrategy、RegexExtractionStrategy; raw://前缀:可以把原始 HTML 当作“URL”直接喂给爬虫。从源码看,arun()在 async_webcrawler.py#L403 处通过url.startswith("raw:") or url.startswith("raw://")识别这类来源并跳过真实网络请求,CacheContext同样在 cache_context.py#L56 将其标记为不可缓存的 raw HTML 源。这是本地调试抽取逻辑时非常实用的技巧。
为什么这种方式有用?
- 适合重复性页面结构(商品列表、文章列表);
- 不使用 AI、零调用成本;
- 爬虫把 JSON 字符串放在
result.extracted_content中,可直接解析入库。
6. LLM 结构化抽取(复杂/不规则页面)
对于结构复杂或不规则的页面,可以让语言模型按你定义的 schema 智能解析文本。Crawl4AI 支持开源与闭源两类模型提供方:
- 开源模型:如
ollama/llama3.3(no_token),本地运行、无需密钥; - 闭源模型:如
openai/gpt-4,需要api_token; - 或任何底层库(llm-chat-api)支持的 provider。
完整示例(含开源/闭源两种调用路径):
import os
import json
import asyncio
from pydantic import BaseModel, Field
from crawl4ai import AsyncWebCrawler, CrawlerRunConfig, LLMConfig
from crawl4ai import LLMExtractionStrategy
class OpenAIModelFee(BaseModel):
model_name: str = Field(..., description="Name of the OpenAI model.")
input_fee: str = Field(..., description="Fee for input token for the OpenAI model.")
output_fee: str = Field(
..., description="Fee for output token for the OpenAI model."
)
async def extract_structured_data_using_llm(
provider: str, api_token: str = None, extra_headers: Dict[str, str] = None
):
print(f"\n--- Extracting Structured Data with {provider} ---")
if api_token is None and provider != "ollama":
print(f"API token is required for {provider}. Skipping this example.")
return
browser_config = BrowserConfig(headless=True)
extra_args = {"temperature": 0, "top_p": 0.9, "max_tokens": 2000}
if extra_headers:
extra_args["extra_headers"] = extra_headers
crawler_config = CrawlerRunConfig(
cache_mode=CacheMode.BYPASS,
word_count_threshold=1,
page_timeout=80000,
extraction_strategy=LLMExtractionStrategy(
llm_config = LLMConfig(provider=provider,api_token=api_token),
schema=OpenAIModelFee.model_json_schema(),
extraction_type="schema",
instruction="""From the crawled content, extract all mentioned model names along with their fees for input and output tokens.
Do not miss any models in the entire content.""",
extra_args=extra_args,
),
)
async with AsyncWebCrawler(config=browser_config) as crawler:
result = await crawler.arun(
url="https://openai.com/api/pricing/", config=crawler_config
)
print(result.extracted_content)
if __name__ == "__main__":
asyncio.run(
extract_structured_data_using_llm(
provider="openai/gpt-4o", api_token=os.getenv("OPENAI_API_KEY")
)
)
这里发生了什么?
- 用 Pydantic 模型(示例中的
OpenAIModelFee)描述目标字段,并通过model_json_schema()转成 JSON Schema 传给策略; LLMExtractionStrategy(定义于 extraction_strategy.py#L533)结合 schema、instruction与extraction_type="schema",把抓取的原始文本转换为结构化 JSON;- 通过
LLMConfig的 provider 与 api_token 决定使用本地模型(Ollama)还是远程 API(OpenAI 等)。
参数细节值得注意:示例中 word_count_threshold=1 是为了让短小价目页内容也能进入抽取流程(默认阈值约 200 词,见第 3.2 节表格);page_timeout=80000 把页面超时放宽到 80 秒,因为 LLM 抽取涉及更长的整体耗时;extra_args 中的 temperature=0 降低随机性,适合要求稳定的结构化输出。仓库中同类示例可参考 llm_extraction_openai_pricing.py 与 extraction_strategies_examples.py。
7. 自适应爬取(Adaptive Crawling)
Crawl4AI 内置智能自适应爬取:自动判断“何时已收集到足够信息”,避免盲目全站抓取。快速示例:
import asyncio
from crawl4ai import AsyncWebCrawler, AdaptiveCrawler
async def adaptive_example():
async with AsyncWebCrawler() as crawler:
adaptive = AdaptiveCrawler(crawler)
# Start adaptive crawling
result = await adaptive.digest(
start_url="https://docs.python.org/3/",
query="async context managers"
)
# View results
adaptive.print_stats()
print(f"Crawled {len(result.crawled_urls)} pages")
print(f"Achieved {adaptive.confidence:.0%} confidence")
if __name__ == "__main__":
asyncio.run(adaptive_example())
对应源码均在 adaptive_crawler.py:digest() 主入口位于 L1330,confidence 属性位于 L1531,print_stats() 位于 L1570。
自适应爬取的特殊之处:
- 自动停止:收集到足够信息即停止;
- 智能链接选择:只跟随与查询相关的链接;
- 置信度评分:
adaptive.confidence告诉你当前信息的完整程度(0–1)。
完整原理与参数配置参见 Adaptive Crawling 专题文档。
8. 多 URL 并发(arun_many 预览)
如果需要并行抓取多个 URL,使用 arun_many()(实现位于 async_webcrawler.py#L973)。默认情况下 Crawl4AI 采用 MemoryAdaptiveDispatcher,基于系统可用内存自动调节并发度,实现见 async_dispatcher.py#L148;同文件还提供 SemaphoreDispatcher(信号量限流)与 RateLimiter(限速)可选。快速示例:
import asyncio
from crawl4ai import AsyncWebCrawler, CrawlerRunConfig, CacheMode
async def quick_parallel_example():
urls = [
"https://example.com/page1",
"https://example.com/page2",
"https://example.com/page3"
]
run_conf = CrawlerRunConfig(
cache_mode=CacheMode.BYPASS,
stream=True # Enable streaming mode
)
async with AsyncWebCrawler() as crawler:
# Stream results as they complete
async for result in await crawler.arun_many(urls, config=run_conf):
if result.success:
print(f"[OK] {result.url}, length: {len(result.markdown.raw_markdown)}")
else:
print(f"[ERROR] {result.url} => {result.error_message}")
# Or get all results at once (default behavior)
run_conf = run_conf.clone(stream=False)
results = await crawler.arun_many(urls, config=run_conf)
for res in results:
if res.success:
print(f"[OK] {res.url}, length: {len(res.markdown.raw_markdown)}")
else:
print(f"[ERROR] {res.url} => {res.error_message}")
if __name__ == "__main__":
asyncio.run(quick_parallel_example())
示例展示了两种结果消费方式:
- 流式模式(
stream=True):用async for在结果就绪时立即处理; - 批量模式(
stream=False,默认行为):等待全部完成后一次性拿到列表。
CrawlerRunConfig.clone() 方法(async_configs.py#L2190)可在保留原有配置的前提下生成一份改动过的副本,避免修改原对象。更高级的并发控制(信号量方案、自适应内存节流、自定义限速)参见 Advanced Multi-URL Crawling;仓库测试 test_arun_many.py 覆盖了批量抓取的相关行为。
9. 动态内容处理:JavaScript 点击与等待
有些站点需要多次“翻页点击”或依赖 JavaScript 更新内容。下面的示例演示如何在抓取前执行自定义 JS,逐个点击标签页(tabs)使隐藏内容渲染出来,然后用 CSS 抽取策略提取结构化数据:
import asyncio
from crawl4ai import AsyncWebCrawler, BrowserConfig, CrawlerRunConfig, CacheMode
from crawl4ai import JsonCssExtractionStrategy
async def extract_structured_data_using_css_extractor():
print("\n--- Using JsonCssExtractionStrategy for Fast Structured Output ---")
schema = {
"name": "KidoCode Courses",
"baseSelector": "section.charge-methodology .w-tab-content > div",
"fields": [
{
"name": "section_title",
"selector": "h3.heading-50",
"type": "text",
},
{
"name": "section_description",
"selector": ".charge-content",
"type": "text",
},
{
"name": "course_name",
"selector": ".text-block-93",
"type": "text",
},
{
"name": "course_description",
"selector": ".course-content-text",
"type": "text",
},
{
"name": "course_icon",
"selector": ".image-92",
"type": "attribute",
"attribute": "src",
},
],
}
browser_config = BrowserConfig(headless=True, java_script_enabled=True)
js_click_tabs = """
(async () => {
const tabs = document.querySelectorAll("section.charge-methodology .tabs-menu-3 > div");
for(let tab of tabs) {
tab.scrollIntoView();
tab.click();
await new Promise(r => setTimeout(r, 500));
}
})();
"""
crawler_config = CrawlerRunConfig(
cache_mode=CacheMode.BYPASS,
extraction_strategy=JsonCssExtractionStrategy(schema),
js_code=[js_click_tabs],
)
async with AsyncWebCrawler(config=browser_config) as crawler:
result = await crawler.arun(
url="https://www.kidocode.com/degrees/technology", config=crawler_config
)
companies = json.loads(result.extracted_content)
print(f"Successfully extracted {len(companies)} companies")
print(json.dumps(companies[0], indent=2))
async def main():
await extract_structured_data_using_css_extractor()
if __name__ == "__main__":
asyncio.run(main())
关键点解析:
BrowserConfig(headless=False):观察调试时可关闭无头模式,亲眼看到它如何点击按钮;示例中为演示简洁保留headless=True;CrawlerRunConfig(...):指定抽取策略;在多步翻页场景中,应通过session_id复用同一页面实例以保留 DOM 状态——session_id字段说明见 async_configs.py#L1411-L1413;js_code与wait_for:js_code列表中的脚本在抓取前执行(本例中逐个scrollIntoView()+click()各标签并等待 500ms,确保懒加载内容进入 DOM);wait_for用于声明“等待某 CSS 选择器或 JS 条件成立后再抽取”(默认None,超时受page_timeout约束,见第 3.2 节)。在“点击下一页并等待新内容加载”的分页场景中,二者配合使用;js_only=True:表示不再重新导航,而是继续复用当前会话页面执行 JS;- 会话结束后调用
kill_session()清理页面与浏览器会话,释放资源。
wait_for、wait_for_timeout 等等待参数均集中在 CrawlerRunConfig 的导航与定时参数区,可按需组合;动态交互的更多教程可参考仓库中的 tutorial_dynamic_clicks.md。
10. 小结与下一步
完成本篇快速上手后,你已经:
- 执行了基础爬取并打印了 Markdown;
- 将内容过滤器与 markdown 生成器组合使用(
raw_markdownvsfit_markdown); - 通过 CSS 与 LLM 两种策略抽取了结构化 JSON;
- 使用
js_code/wait_for/session_id机制处理了动态页面。
若准备深入,建议按以下路径继续(均为仓库内文档):
- 安装:进阶安装、Docker 使用与可选依赖 —— 安装指南;
- Hooks 与鉴权:运行自定义 JavaScript、用 cookies/local storage 处理登录 —— Hooks & Auth;
- 多 URL 进阶:信号量并发、内存节流、限速 —— Advanced Multi-URL Crawling;
- 无 LLM 抽取策略:schema 自动生成与高级 CSS/XPath 抽取 —— No-LLM Extraction Strategies;
- 自适应爬取原理:策略、置信度评分机制 —— Adaptive Crawling。
Crawl4AI 是一个功能强大且灵活的抓取工具。祝你构建出高效的抓取器、数据管道或 AI 驱动的数据抽取流程。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00