crawl4ai:大模型驱动的网页结构化数据提取新范式 📅 发布时间:2026/9/8 22:55:26 👁 浏览次数: 爬虫写了几年requests 用得比筷子还顺手但这两年明显感觉有点跟不上趟了。以前抓网页最烦的是解析正则写半天xpath 调半天好不容易跑通了网站改个版又废了。现在大模型能把自然语言变成结构化指令爬虫这个领域也跟着变天。crawl4ai 就是在这个背景下冒出来的一个库主打让大模型直接参与网页到结构化数据的转换过程。我上手试了一段时间说实话这东西的思路和传统爬虫完全不是一个路子用好了确实省事但坑也不少。这篇文章不打算写成官方文档的翻译版。我就按照自己实际摸索下来的顺序聊聊 crawl4ai 到底解决了什么问题、核心组件怎么用、结构化输出怎么调参以及我在实测中踩过的那些坑。1. crawl4ai 解决的问题其实不是爬虫本身先说结论crawl4ai 不是让你告别爬虫它是把爬虫链路里最费人的两个环节——页面解析和数据结构化——交给大模型来做。1.1 传统爬虫的结构化数据流程有多痛传统流程大概是这样的用 requests 或 httpx 发请求拿到 HTML。分析网页结构用 XPath、CSS 选择器或者正则表达式提取标题、正文、链接。把提取结果塞进自建的字典或模型类清洗、去重、落库。网站改版重新分析结构然后回第 2 步。这套流程的问题不在于不会写而在于太脆。尤其当你面对的是十几个不同来源的网站每个网站都得单独写一套解析规则每套规则还动不动就失效。更别提那些内容挂在 JS 异步渲染里的页面requests 拿到的 HTML 根本什么都没有又得上 Selenium 或 Playwright。整个链条下来60% 的时间花在写选择器和调试解析上真正跟数据打交道的精力反而少。1.2 crawl4ai 改变了链路中的哪一环crawl4ai 的核心思路是把解析这件事从手工选择器切换成大模型能力。它做了几件关键的事内置 Playwright 解析 JS 渲染后的页面不需要你额外写浏览器控制代码。提供两种内容过滤策略启发式修剪和基于关键词的 BM25 过滤先把无关导航页脚广告去掉再喂给大模型。支持把 LLM 的 JSON 输出直接映射到你自己定义的 Pydantic 模型或 JSON Schema 上。这意味着什么意味着你只需要告诉模型我要这个页面的标题、作者、发布日期输出成 JSON它就能返回结构化的字段。不用写选择器不用等页面结构稳定不用在 chrome devtools 里反复右键 copy selector。当然这只是理想状态。实际用下来crawl4ai 并不能完全消灭写代码但它确实把人力从维护解析规则里解放了出来转向设计 schema 和调 prompt这个转向我认为是值得的。2. crawl4ai 的核心组件和运行流程拆解从代码层面看crawl4ai 的逻辑链条相当清晰主要围绕浏览器配置、爬取运行配置、内容过滤器和 LLM 配置四类对象展开。2.1 BrowserConfig管好看不见的浏览器crawl4ai 把 Playwright 的浏览器封装成了一个 BrowserConfig 对象。你可以用它控制浏览器是 headless 还是有头、是否启用代理、是否忽略 HTTPS 错误、设置 user agent 等等。这里有一个比较实用的参数是headless调试阶段可以关了看真实渲染效果跑量的时候再开回去。from crawl4ai import BrowserConfig browser_config BrowserConfig( headlessTrue, ignore_https_errorsTrue, user_agent_moderandom, java_script_enabledTrue, )注意java_script_enabled默认就是 True不用手动关除非你明确只爬静态页面想加速。很多第一次接触的朋友不知道这一点以为需要额外开启 JS 渲染实际上 crawl4ai 的爬取引擎本身就是围绕 JS 渲染设计的你不需要特意去启用它本来就是开着的。2.2 CrawlerRunConfig爬取行为的总闸CrawlerRunConfig 是整个爬取动作的核心配置指定了如何加载目标 URL如何过滤内容是否执行 JavaScript以及如何把结果交给 LLM。它的设计思路是一次配置、多次复用很适合在生产环境里针对不同网站维护多套配置。from crawl4ai import CrawlerRunConfig run_config CrawlerRunConfig( css_selectorarticle.main-content, word_count_threshold10, extraction_strategyLLM, )字段比较多但重点理解extraction_strategy。它的取值决定了走普通爬取提取路线还是LLM 提取路线。如果你想省时省力拿纯文本可以走普通爬取加内容过滤如果你要的是字段化数据就交给 LLM 提取策略。2.3 LLMConfig决定消息最终给你什么格式这是 crawl4ai 最出彩的一层。你可以在 LLMConfig 里指定模型比如 OpenAI 兼容接口、deepseek、本地 Ollama 等、温度、最大令牌数等参数。然后配合 instruction 告诉大模型输出规则。比如你想从一篇新闻页提取标题、作者、时间、正文就写from crawl4ai import LLMConfig llm_config LLMConfig( provideropenai/gpt-4o-mini, api_token你的密钥, temperature0.2, max_tokens2048, )温度调低一点是为了让模型输出更稳定这对结构化提取非常重要。做数据提取不是写文案温度 0.2 以下才是合理区间。2.4 把四类配置拼装起来执行的完整流程理解了上面几个对象之后实际调用的全貌就很清楚了。这里给一个入门级的同步示例import asyncio from crawl4ai import AsyncWebCrawler, BrowserConfig, CrawlerRunConfig, LLMConfig async def main(): browser_config BrowserConfig(headlessTrue) run_config CrawlerRunConfig( extraction_strategyLLM, llm_configLLMConfig( provideropenai/gpt-4o-mini, api_token你的密钥, ), instruction 请从页面中提取以下信息以 JSON 格式返回 { title: 文章标题, author: 作者, published_at: 发布日期, content: 正文内容 } 只需要返回 JSON不要额外解释。 , ) async with AsyncWebCrawler(configbrowser_config) as crawler: result await crawler.arun(urlhttps://example.com/news/123, configrun_config) print(result.extracted_content) asyncio.run(main())result.extracted_content里就是模型返回的结构化内容通常是 JSON 字符串可以直接再用json.loads解析成 Python 对象。这整套流程跑通之后你会发现爬取页面—渲染 JS—过滤噪声—LLM 抽取—结构化输出首次被串成了一条完整链路而且每一步都有明确的配置对象在支撑。3. 把网页变成结构化数据的完整实操看官方文档和自己动手完全是两回事。这里我拿一个真实的演示站点跑一遍把具体步骤、参数怎么调、输出长什么样都摆出来。官方文档里用的例子是 Arxivhttps://arxiv.org/abs/2411.15243这是一个 arXiv 论文详情页页面结构规整非常适合拿来验证流程。3.1 写一个最简的可运行脚本我建议你直接复制下面这段先跑通再慢慢加参数import asyncio, json from crawl4ai import AsyncWebCrawler, BrowserConfig, CrawlerRunConfig, LLMConfig async def extract_arxiv(): browser_cfg BrowserConfig(headlessTrue) llm_cfg LLMConfig( provideropenai/gpt-4o-mini, api_token你的密钥, temperature0.1, ) run_cfg CrawlerRunConfig( extraction_strategyLLM, llm_configllm_cfg, instruction 从页面中提取论文信息返回 JSON 格式字段如下 { title: 论文标题, authors: [作者1, 作者2], abstract: 摘要, subjects: [学科分类1, 学科分类2], submitted_date: 提交日期 } 只返回 JSON。 , ) async with AsyncWebCrawler(configbrowser_cfg) as crawler: result await crawler.arun(urlhttps://arxiv.org/abs/2411.15243, configrun_cfg) print(抓取状态:, result.success) print(原始HTML长度:, len(result.html)) print(LLM提取结果:, result.extracted_content) asyncio.run(extract_arxiv())我实测的结果是success为 Trueextracted_content返回了合法的 JSON 字符串里面包含了论文标题、作者列表、摘要等字段。这是 crawl4ai 最典型的使用姿势——告诉模型要什么、给什么格式它自己从页面内容里抽。3.2 用中文 prompt 和自定义 schema 提取效果一样好之前有人说中文 prompt 大模型理解得不如英文实测下来在 gpt-4o-mini 上效果差别不大。我更习惯直接把 schema 写进 instruction 里同时指定输出模式为STRICT这样模型会严格按照 JSON Schema 输出不搞自由主义。run_cfg CrawlerRunConfig( extraction_strategyLLM, llm_configllm_cfg, instruction 从页面提取以下信息严格按照给定 schema 输出 JSON { 标题: 论文标题字符串, 作者列表: [字符串数组], 摘要: 论文摘要字符串, 学科分类: [字符串数组], 投稿时间: 日期字符串 } 输出字段名必须保持一致不要添加多余内容。 , )如果你用的模型对中文键名支持不够好也可以保留英文字段名只把 prompt 描述写成中文。这是我在多次测试里常用的折中方案灵活度和准确率都更稳。3.3 结果解析与模型类型定义的衔接拿到extracted_content之后很多人会问这跟 Pydantic 模型怎么衔接官方没有强迫你用 Pydantic你可以直接用json.loads转成字典。但如果你喜欢类型安全建议自己定义一个 Pydantic 模型然后做一次解析校验from pydantic import BaseModel from typing import List import json class ArxivPaper(BaseModel): title: str authors: List[str] abstract: str subjects: List[str] submitted_date: str # 假设 result.extracted_content 已经拿到 data json.loads(result.extracted_content) paper ArxivPaper(**data) print(paper.title, len(paper.authors))这样就把大模型的自由文本输出锁死在了你自己的数据结构里后面无论写数据库还是做接口都是非常确定的形态。注意如果模型偶尔输出了解释性文字比如好的我为你提取了以下信息会导致 json.loads 失败。建议在 instruction 里强调只返回 JSON再配合STRICT模式基本能避免这个问题。4. 三个最影响结果质量的参数细节工具链搭起来只需要十分钟但要让结果稳定、质量高有几个参数和细节需要专门调优。这一节我把现场容易忽略的点都摊开讲。4.1 word_count_threshold过滤短文本噪声word_count_threshold是内容过滤器里的一个重要阈值表示少于多少个单词的文本块会被丢弃。默认一般是 2~3但如果页面里充斥大量短文本比如按钮文字、标签、浮动提示可以适当调高到 5~10让最终喂给 LLM 的正文更干净。run_cfg CrawlerRunConfig( word_count_threshold8, extraction_strategyLLM, llm_configllm_cfg, )这个参数调得好能直接减少大模型被噪声干扰的概率提升字段提取准确率。4.2 temperature 和 max_tokens 的取舍结构化提取场景里temperature 一定要低我推荐 0.1~0.2。高温度会带来创造性输出但我们要的是从页面里找答案不是要它发挥。max_tokens 则取决于你要提取的内容长度。摘要或者短文章2048 足够如果是长文正文建议 4096 以上。设置得太小JSON 会被截断解析必炸。4.3 使用内容过滤器和 CSS 选择器缩小问题域LLM 不是万能的页面越大、噪声越多错误率越高。所以正确做法是先帮模型做减法用css_selector只选正文区域如article.main-content用PruningContentFilter或BM25ContentFilter自动删掉导航、页脚、广告再让模型基于清洗后的内容做提取。一个组合示例from crawl4ai import CrawlerRunConfig from crawl4ai.content_filter_strategy import PruningContentFilter, BM25ContentFilter run_cfg CrawlerRunConfig( css_selectormain, content_filterPruningContentFilter(threshold0.45, threshold_typedynamic), extraction_strategyLLM, llm_configllm_cfg, )这样做的本质是缩小问题域。模型拿到的如果是干净正文提取准确率天然就高。我实测同一个页面不做过滤直接让 LLM 抽和做了过滤再抽字段完整度差距非常明显。5. 实测对比传统方法与 crawl4ai 各来一遍拿同一个 arXiv 页面我分别用 requests BeautifulSoup 的传统方法和 crawl4ai LLM 的方式跑了一遍把差异摊开你看完就明白什么时候该用什么。5.1 传统方式requests BeautifulSoup传统方式的代码其实很简单但脆弱性体现在选择器上import requests from bs4 import BeautifulSoup resp requests.get(https://arxiv.org/abs/2411.15243) soup BeautifulSoup(resp.text, html.parser) title soup.select_one(h1.title).text.strip() authors [a.text for a in soup.select(div.authors a)] abstract soup.select_one(blockquote.abstract).text.strip()跑这个页面没问题但换个网站就得重新分析结构。而 arXiv 页面结构相对稳定所以传统方式在单一来源、长期稳定的场景里依然有优势——不消耗 token速度快延迟低。5.2 crawl4ai 方式费 token 但省心crawl4ai 方式写好了通用提取 prompt 和 schema 之后换一个完全不同的站点只要页面内容能覆盖目标字段基本不用改代码。这在多源聚合、页面经常改版、不规律页面的场景下优势极大。两种方式的取舍我整理成了下面的表维度传统解析requests BScrawl4ai LLM开发速度需要逐站写选择器慢写一份 schema 通用快抗改版能力差改版即崩强页面结构变化影响小运行成本几乎为零每次请求消耗 LLM token部署复杂度低需要管理浏览器和 LLM 服务适合场景单站固定结构、大规模长期爬取多源异构、字段不固定、页面常变这个对比应该能帮你做选型判断。量特别大、目标站点非常稳定的时候我建议还是走传统方式加缓存省钱。量不大但来源杂乱或者需要提取的字段不在 DOM 里能直接映射的位置那 crawl4ai 完胜。5.3 异步批量运行的实测记录crawl4ai 也支持异步批量爬取。我拿了一个论文列表页里的 5 个链接做并发抓取简单封装了一下import asyncio from crawl4ai import AsyncWebCrawler, BrowserConfig, CrawlerRunConfig, LLMConfig async def crawl_batch(): urls [ https://arxiv.org/abs/2411.15243, https://arxiv.org/abs/2408.12345, https://arxiv.org/abs/2406.67890, ] browser_cfg BrowserConfig(headlessTrue) llm_cfg LLMConfig(provideropenai/gpt-4o-mini, api_token你的密钥, temperature0.1) run_cfg CrawlerRunConfig( extraction_strategyLLM, llm_configllm_cfg, instruction提取论文标题、作者、摘要只输出 JSON。, ) async with AsyncWebCrawler(configbrowser_cfg) as crawler: results await crawler.arun_many(urlsurls, configrun_cfg) for i, r in enumerate(results): print(i, r.success, r.status_code, r.extracted_content[:80]) asyncio.run(crawl_batch())实测下来3 个页面的并发抓取在 20 秒左右完成每页耗时大概 6~7 秒。相比传统方式当然是慢的但换来的是不用维护任何选择器。如果你跑的是上千级别的量可以加一个简单的信号量限制并发数避免被目标网站或者大模型接口限流semaphore asyncio.Semaphore(5) async def limited_run(url): async with semaphore: async with AsyncWebCrawler(configbrowser_cfg) as crawler: r await crawler.arun(urlurl, configrun_cfg) return r6. 避坑清单与执行策略这部分是实打实踩出来的经验。直接给结论不绕弯子。6.1 懒加载内容会丢需要先滚动很多现代网页的内容不是一次性渲染完的需要滚动到可视区域才触发加载。crawl4ai 提供了js_code和wait_for参数来应对。可以在js_code里注入滚动脚本run_cfg CrawlerRunConfig( js_codewindow.scrollTo(0, document.body.scrollHeight);, wait_forcss:.comment-list, )如果是瀑布流页面滚动一次不够就得循环滚几次。这个操作等价于 Selenium 里的自动下拉忘了它会导致正文缺失。6.2 页面有 cookie 弹窗或登录墙最简单的是直接换入口crawl4ai 提供了cookie参数和set_cookie操作但说实话遇到强 cookie 校验的时候手动配置会很繁琐。我的经验是先看是否有移动版页面或者 API 接口很多时候绕过前端弹窗比硬拼页面渲染省事得多。你可以在CrawlerRunConfig里注入 cookie 字符串run_cfg CrawlerRunConfig( cookienamevalue; sessionidxxxx, )但如果是复杂的登录态保持建议你还是先在浏览器里把 cookie 导出再写进配置里。6.3 LLM 输出偶尔会飘必须有重试和校验大模型的输出本质是概率性的即使同一个页面跑十次也不能保证每次结果都完全一致。我的做法是对extracted_content做json.loads校验用 Pydantic 模型做字段必填校验失败就重试一次最多重试两次避免无限循环烧钱重试时换一个更低 temperature 的配置。import json, time from pydantic import ValidationError def parse_with_retry(result, model, tries2): for i in range(tries): try: data json.loads(result.extracted_content) return model(**data) except (json.JSONDecodeError, ValidationError) as e: if i tries - 1: raise time.sleep(1)这套兜底逻辑能显著提升整体任务的成功率不会因为某一次的模型输出异常导致全流程崩溃。6.4 并发和限流控制好自己别惹毛对方和大模型批量抓取时限制 Playwright 浏览器的并发数、随机化每个请求的间隔时间是基本的爬虫礼仪。crawl4ai 的arun_many很方便但别一上来就开 50 个并发。我推荐一开始跑 3~5 个稳定后再往上加。另外LLM 接口也有速率限制并发太高会直接收到 429那时候就不是页面抓不到的问题而是整个任务卡死。6.5 其他容易踩的坑CSS 选择器不生效时先确认页面是不是在 iframe 里Playwright 不一定能直接穿透所有 iframe 结构。arXiv 这类页面反爬比较宽松但很多国内站点会校验 TLS 指纹和 header 顺序单纯换个 UA 没用必要时开启 stealth 模式。如果目标网站有大量图片crawl4ai 会把图片链接也保留下来你要在 instruction 里明确忽略图片链接或者后续自己做过滤。7. 从爬虫到 Agent两条进阶路线crawl4ai 用熟练之后你会发现它的定位远不止更好用的爬虫。它更大的价值在于给 AI Agent 提供读取网页并理解网页的能力。这里给两个我认为很实用的进阶方向。7.1 给 Agent 提供网页阅读能力传统 RAG 方案里Agent 读网页靠的是 URL 链接 抓取 文本切块。但网页里 80% 都是导航、版权、广告信息直接抓下来塞给 Agent 会导致检索质量下降。现在你可以用 crawl4ai 的过滤策略先把正文洗干净再进 RAG效果完全不一样。更进一步的玩法是按需提取——Agent 在回答用户问题时如果发现自己的知识库不够可以直接调 crawl4ai 抓取相关网页并指挥 LLM 提取特定字段。这种检索即提取的模式其实已经在往 Agentic Search 的方向走了。7.2 与爬虫管理平台结合批量采集不再依赖逐站配置如果你维护过类似 Scrapy 的爬虫项目一定经历过新站点接入要写一套 pipeline的痛。用 crawl4ai可以做一个通用抽取服务前端传入 URL 和目标字段 schema后端用 crawl4ai 抓取页面用同一个 LLM 配置按 schema 提取输出标准化 JSON 落库。这样一来业务方不需要写任何爬虫代码只要描述我想要什么系统就能返回对应字段。这就是大模型逆向爬虫味道最浓的落地场景。结合最近社区里的讨论crawl4ai 的 0.7 版本之后还加入了更多云服务支持和策略扩展点性能和兼容性一直在迭代。我曾经在某个遍历多个站点抓行业资讯的项目里用同一套代码跑了三类结构完全不同的页面只改 prompt 里的字段描述就全部跑通了这在以前手动写解析规则的时代想都不敢想。如果你正卡在多源采集和数据结构化的老问题上这套思路值得认真试一试。