跳到主要内容

Firecrawl — 这是什么 · 全景 · 阅读地图

30 秒导读: Firecrawl 是一套开源的 web 数据 API:你给它一个 URL(网页、 也可以是 PDF/DOCX),它替你处理代理轮换、JS 渲染、反爬这些脏活,把页面变成 干净的 Markdown 或结构化 JSON——正好是喂给大模型的格式。它主要给 AI agent 当"眼睛和手":需要实时网页内容时调它。


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

一句话定义

Firecrawl = 把"网页"变成"LLM 能直接吃的数据"的 API。 输入一个网址,输出一段 干净 Markdown 或一份符合你 schema 的 JSON。

解决什么问题 / 给谁用

假设你在写一个 AI agent,要它"读一下这个产品页,告诉我价格方案"。你会立刻撞上一堆 和 AI 无关的苦活:

  • 页面是 JS 渲染的,curl 拿到的是空壳;
  • 网站有反爬 / 需要轮换代理 / 有速率限制;
  • 拿到的 HTML 里全是导航栏、广告、脚本,真正的正文淹在噪声里;
  • 你要的其实是结构化字段(价格、标题),不是一大坨 HTML。

Firecrawl 就是把这些苦活打包成一次 API 调用。README 的 "Why Firecrawl" 一节把卖点 列成:覆盖 96% 的网页(含 JS 重页面)、P95 延迟 3.4s、直接输出 LLM-ready 的 Markdown/JSON、 自动处理代理与反爬(README.md:52-62)。它给谁用:AI agent、RAG 管线、任何要"实时网页 上下文"的应用

它能做什么(核心端点)

端点干什么同步/异步
scrape单个 URL → Markdown / HTML / 截图 / JSON同步(等结果)
search搜索网页,并抓回结果页正文同步
map秒级发现一个站点的所有 URL同步
crawl顺着链接爬完整站,抓所有页面异步(返回 job id)
batch scrape一次异步抓成千上万个 URL异步
extract / agent用自然语言 + schema 让 AI 跨页取数异步

端点清单见 README.md:67-83;各端点的请求示例见 README.md:90-506。

用起来什么样

最小的一次调用(README.md:151-157 的 Python 例子):

from firecrawl import Firecrawl

app = Firecrawl(api_key="fc-YOUR_API_KEY")
result = app.scrape("firecrawl.dev") # 返回干净 Markdown

底下其实就是一个 HTTP POST(README.md:173-179):

curl -X POST 'https://api.firecrawl.dev/v2/scrape' \
-H 'Authorization: Bearer fc-YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{ "url": "firecrawl.dev" }'

拿回来的是 Markdown 正文,导航/脚本/广告已经被剥掉(README.md:189-199)。

一句话直觉

把 Firecrawl 当成 agent 的"网页读取器 + 提纯器":浏览器负责把页面渲染出来, Firecrawl 负责把渲染结果洗成模型看得懂的干净文本——你只管说"我要哪个 URL、要什么格式"。


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

怎么读这张图

从上到下是一次请求的生命周期。关键分叉在第二层: scrape 这类"要立刻拿结果"的 请求就地同步执行;crawl / batch / extract 这类"活很重"的请求丢进队列,由后台 worker 慢慢做,先返回一个 job id 让你之后来查。

HTTP 请求 (POST /v2/scrape | /v2/crawl | ...)

┌───────────────▼───────────────┐
│ 控制器层 (Express) │
│ routes/v2.ts → controllers/ │ ← 鉴权/限流/扣费中间件
│ v0 | v1 | v2 │
└───────────────┬───────────────┘
同步就地执行 │ 重活入队
┌──────────────────────┴───────────────────────┐
▼ ▼
┌────────────────────┐ ┌────────────────────────┐
│ scrape 控制器 │ │ 任务队列 NuQ │
│ 信号量占并发 → │ │ (自建, Postgres 背书) │
│ processJobInternal │ │ │ │
└─────────┬──────────┘ │ ▼ │
│ ← 两条路最终都汇到抓取内核 │ 后台 worker │
│ │ + WebCrawler 发现链接 │
└───────────────┬────────────────────┘ (逐个 URL 再走内核) │

┌──────────────────────────────┐
│ 抓取内核 scrapeURL │ ← 一次"抓一个 URL"的编排器
└──────────────┬───────────────┘

┌───────────────┬──────────────────┬────────────────────┐
▼ ▼ ▼ ▼
┌─────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 引擎系统 │ │ 转换流水线 │ │ 微服务 │ │ 存储/索引 │
│ engines │ │ transformers │ │ playwright / │ │ (缓存/GCS) │
│ 打分+回退│ │ HTML→MD、抽取 │ │ html-to-md │ │ │
└─────────┘ └──────────────┘ └──────────────┘ └──────────────┘

部件一句话职责

部件干什么在哪(相对 apps/api/src/)
HTTP 入口建 Express app,挂载 v0/v1/v2 路由与错误处理index.ts(路由挂载见 :123-126)
v2 路由表把 URL 路径映射到控制器,串上鉴权/限流/扣费中间件routes/v2.ts
控制器层每个端点一个控制器,校验入参、编排执行、组织响应controllers/v0 · v1 · v2/
抓取内核 scrapeURL"抓一个 URL"的核心编排:选引擎、跑转换、容错重试scraper/scrapeURL/index.ts
引擎系统 engines一堆抓取后端(fetch/playwright/fire-engine/pdf…),按能力打分排回退scraper/scrapeURL/engines/
转换流水线 transformers把原始 HTML 洗成 Markdown、再按需 LLM 抽取成 JSONscraper/scrapeURL/transformers/
爬虫 WebCrawler爬站时发现并过滤链接、读 sitemapscraper/WebScraper/crawler.ts
任务队列 NuQ + worker自建队列(Postgres + RabbitMQ + Redis)+ 后台 worker 执行异步作业services/worker/nuq.ts · scrape-worker.ts

NuQ 三系统各司其职(详见 04 章):Postgres 是作业表 + 状态机的事实源、RabbitMQ 做预取与完成通知、Redis 做团队/爬取级并发信号量。上图里"Postgres 背书"是这套组合的简写。

主线走一遍(一次 /v2/scrape 从进门到内核)

这条主线是理解整个系统的钥匙。注意它不进队列——scrape 要立刻返回,所以就地同步做:

  1. 进门。 app.use("/v2", v2Router)/v2/* 交给 v2 路由(index.ts:125); POST /scrape 先过 authMiddleware / checkCreditsMiddleware / blocklistMiddleware 等中间件,再落到 scrapeController(routes/v2.ts:200-207)。

  2. 控制器编排。 scrapeController 生成一个 jobId(uuidv7),校验请求体 scrapeRequestSchema.parse、查权限,然后用团队并发信号量占一个并发额度 (controllers/v2/scrape.ts:227 teamConcurrencySemaphore.withSemaphore)。

  3. 就地执行(不入队)。 拿到信号量后,它构造一个 job 对象(带 skipNuq: true, 见 scrape.ts:288),直接processJobInternal(job) 就地跑,而不是丢给队列 (scrape.ts:302)。

  4. 进抓取内核。 processJobInternal(services/worker/scrape-worker.ts:1293)→ processJob(:218)→ startWebScraperPipeline(main/runWebScraper.ts:9)→ 最终 await scrapeURL(...)(runWebScraper.ts:83)。到这里就交给了抓取内核 scrapeURL(scraper/scrapeURL/index.ts:1054),后面选引擎、抓、转换的细节由 01 章接手。

对比:crawl 走的是另一条路。 crawlControllercrawlToCrawler 建一个 WebCrawler(controllers/v2/crawl.ts:209),把种子 URL 通过 _addScrapeJobToBullMQ 投进队列(crawl.ts:239),然后马上返回 job id;真正的抓取由后台 worker 一个个消费。 细节见 04 章


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

本组文档共 6 章,按"由浅入深"排。你现在读的 index.md 是 Layer 0+1(全景), 其余五章逐层下钻:

index.md (本章:这是什么 + 全景 + 路由主线)

├─ 01-scrape-pipeline.md 抓取内核 scrapeURL:一个 URL 怎么被抓下来、怎么容错重试
│ │ (主线的"核心",先读这章)
│ ├─ 02-engines.md 引擎系统:内核如何给多个抓取后端打分、排回退列表
│ └─ 03-transformers.md 转换与抽取:抓到的 HTML 如何洗成 Markdown / 用 LLM 抽成 JSON

├─ 04-crawl-queue.md 爬取与异步任务:WebCrawler 发现链接 + 自建队列 NuQ 调度 worker
│ (另一条"异步"主线)
└─ 05-higher-features.md 上层能力:map / search / extract / agent / deep-research / llmstxt
(建立在 scrape/crawl 之上的高层端点)
想搞懂…读哪章
"一次 scrape 到底发生了什么"01-scrape-pipeline
"为什么有时用 playwright、有时用 fetch"02-engines
"HTML 怎么变成干净 Markdown / 结构化 JSON"03-transformers
"crawl / batch 的异步与队列怎么转"04-crawl-queue
"map / search / extract 这些高层端点"05-higher-features

推荐路线: index → 01 → 02 → 03(把同步抓取主线打通)→ 04(异步爬取)→ 05(上层)。


4. monorepo 结构(这堆目录都是啥)

Firecrawl 是一个 monorepo(一个仓库装多个包)。核心就一个 apps/api,其余是 SDK 和配套微服务:

目录是什么
apps/api核心:API 服务器 + 后台 worker,本组文档几乎全在讲它
apps/python-sdk · js-sdk · go-sdk · rust-sdk · java-sdk · ruby-sdk · php-sdk · dot-net-sdk · elixir-sdk各语言 SDK:对 HTTP API 的封装,帮你自动轮询异步任务
apps/playwright-service-ts微服务:用 Playwright 渲染 JS 重页面(引擎之一会调它)
apps/go-html-to-md-service微服务:高性能 HTML→Markdown 转换
apps/nuq-postgres自建队列 NuQ 用的 Postgres
apps/redisRedis(限流、缓存等)
apps/ui · apps/test-site · apps/test-suite前端 UI、测试用假站点、端到端测试套件

顶层还有 firecrawl-cli / firecrawl-skills / firecrawl-workflows 等目录,是 CLI 与 agent 集成物料;examples/ 是各种使用示例。理解核心原理只需盯着 apps/api/src

apps/api/src 里的顶层子目录(本组文档的地盘):

子目录装什么
controllers/各端点控制器(v0 / v1 / v2)
routes/路由表(v0.ts / v1.ts / v2.ts / admin.ts)
scraper/抓取内核 scrapeURL、引擎、转换、WebScraper(爬虫)
services/队列 worker(worker/nuq.tsscrape-worker.ts)、计费、日志等
main/runWebScraper.ts——控制器/worker 通往 scrapeURL 的桥
lib/ · types/ · utils/通用工具、类型、辅助

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

想直接跳进源码,从这几个符号入手(路径相对 apps/api/src/):

主题文件符号
HTTP 入口 / 路由挂载index.tsapp.use("/v2", v2Router)(:125)
v2 路由表routes/v2.tsv2Router · POST /scrape → scrapeController(:200)
scrape 控制器(同步主线)controllers/v2/scrape.tsscrapeController · teamConcurrencySemaphore.withSemaphore(:227) · processJobInternal(:302)
团队并发信号量services/worker/team-semaphore.tsteamConcurrencySemaphore · withSemaphore
就地/异步作业执行services/worker/scrape-worker.tsprocessJobInternal(:1293) · processJob(:218)
通往内核的桥main/runWebScraper.tsstartWebScraperPipeline(:9) · runWebScraper(:43)
抓取内核scraper/scrapeURL/index.tsscrapeURL(:1054) — 见 01 章
引擎系统scraper/scrapeURL/engines/index.tsbuildFallbackList(:577) · scrapeURLWithEngine(:787) — 见 02 章
转换流水线scraper/scrapeURL/transformers/index.ts转换器数组 — 见 03 章
爬虫scraper/WebScraper/crawler.tsWebCrawler(:48) — 见 04 章
crawl 控制器(异步主线)controllers/v2/crawl.tscrawlToCrawler(:209) · _addScrapeJobToBullMQ(:239)
自建队列services/worker/nuq.tsNuQJob 等 — 见 04 章