首页
/ Crawl4AI 快速上手:从首次爬取到 Markdown 生成、结构化抽取、自适应爬取与多 URL 并发

Crawl4AI 快速上手:从首次爬取到 Markdown 生成、结构化抽取、自适应爬取与多 URL 并发

2026-09-06 15:49:15作者:薛曦旖Francesca

本文以 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.pyasync_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())

这里发生了什么?

  1. AsyncWebCrawler 启动一个无头浏览器(默认 Chromium)。从 BrowserConfig 的文档字符串 可以看到:browser_type 默认 "chromium"headless 默认 Truebrowser_mode 默认 "dedicated"(每次创建独立浏览器实例);
  2. arun() 拉取 https://example.com 页面,方法签名见 arun()url 参数支持 http://https://file://(本地文件)与 raw:(原始 HTML)四类来源;
  3. 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 的爬虫高度可定制,核心是两类配置对象:

  1. BrowserConfig:控制浏览器行为——无头与否、user agent、JavaScript 开关、视口、代理、CDP 端点等;
  2. 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())

重要:CrawlerRunConfigcache_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=1080viewport_height=600device_scale_factor=1.0chrome_channel="chromium"(见 async_configs.py#L727-L738)。

更高级的配置(代理、PDF 输出、多标签会话等)可在仓库文档中继续深入,例如 安装指南Hooks & Auth 进阶

4. Markdown 输出:raw_markdown 与 fit_markdown

默认情况下 Crawl4AI 会为每个页面自动生成 Markdown,但确切输出取决于你是否指定了 markdown generatorcontent filter

  • result.markdownraw_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 的处理时间。仓库中除剪枝外还提供 BM25ContentFilterLLMContentFilter 等过滤器,均在 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,同族还有 JsonXPathExtractionStrategyJsonLxmlExtractionStrategyRegexExtractionStrategy
  • 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.3no_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")
        )
    )

这里发生了什么?

  1. 用 Pydantic 模型(示例中的 OpenAIModelFee)描述目标字段,并通过 model_json_schema() 转成 JSON Schema 传给策略;
  2. LLMExtractionStrategy(定义于 extraction_strategy.py#L533)结合 schema、instructionextraction_type="schema",把抓取的原始文本转换为结构化 JSON;
  3. 通过 LLMConfigproviderapi_token 决定使用本地模型(Ollama)还是远程 API(OpenAI 等)。

参数细节值得注意:示例中 word_count_threshold=1 是为了让短小价目页内容也能进入抽取流程(默认阈值约 200 词,见第 3.2 节表格);page_timeout=80000 把页面超时放宽到 80 秒,因为 LLM 抽取涉及更长的整体耗时;extra_args 中的 temperature=0 降低随机性,适合要求稳定的结构化输出。仓库中同类示例可参考 llm_extraction_openai_pricing.pyextraction_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.pydigest() 主入口位于 L1330confidence 属性位于 L1531print_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())

示例展示了两种结果消费方式:

  1. 流式模式stream=True):用 async for 在结果就绪时立即处理;
  2. 批量模式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_codewait_forjs_code 列表中的脚本在抓取前执行(本例中逐个 scrollIntoView() + click() 各标签并等待 500ms,确保懒加载内容进入 DOM);wait_for 用于声明“等待某 CSS 选择器或 JS 条件成立后再抽取”(默认 None,超时受 page_timeout 约束,见第 3.2 节)。在“点击下一页并等待新内容加载”的分页场景中,二者配合使用;
  • js_only=True:表示不再重新导航,而是继续复用当前会话页面执行 JS;
  • 会话结束后调用 kill_session() 清理页面与浏览器会话,释放资源。

wait_forwait_for_timeout 等等待参数均集中在 CrawlerRunConfig 的导航与定时参数区,可按需组合;动态交互的更多教程可参考仓库中的 tutorial_dynamic_clicks.md

10. 小结与下一步

完成本篇快速上手后,你已经:

  1. 执行了基础爬取并打印了 Markdown;
  2. 内容过滤器与 markdown 生成器组合使用(raw_markdown vs fit_markdown);
  3. 通过 CSSLLM 两种策略抽取了结构化 JSON;
  4. 使用 js_code/wait_for/session_id 机制处理了动态页面。

若准备深入,建议按以下路径继续(均为仓库内文档):

Crawl4AI 是一个功能强大且灵活的抓取工具。祝你构建出高效的抓取器、数据管道或 AI 驱动的数据抽取流程。

登录后查看全文
热门项目推荐
相关项目推荐