爬取与异步任务:WebCrawler + 自建队列 NuQ
30 秒导读: 单页抓取(01)解决"把一个 URL 变成 LLM-ready 数据";本章解决"把一个站点变成成千上万条数据"。做法是把爬取拆成一张自我生长的作业图——每抓完一页,就从 HTML 里发现新链接、过滤+去重、再把新页当成新作业入队,直到无新页或触顶。撑起这张图的,是 Firecrawl 自己写的作业队列 NuQ(Postgres + RabbitMQ + Redis)。
本章的所有引用都锚定在 commit f4464e19 上。
1. 这是什么(零基础也能懂)
1.1 一句话定义
爬取(crawl)= 从一个起始 URL 出发,顺着页面里的链接一层层往外抓,把整个站点(或其中一个子路径)都变成结构化数据。
1.2 它解决什么问题
假设你要给一个 RAG 系统灌入 docs.example.com 的全部文档页。你不想手写几千个 URL,你只想说一句"把这个文档站爬下来"。爬取就是干这个的:
- 你给一个入口
https://docs.example.com; - Firecrawl 自动找到站点地图(sitemap)、顺着页内
<a>链接发现更多页; - 每个页面都走一遍单页抓取内核,产出 markdown;
- 最后把所有页面聚合成一个结果集。
1.3 为什么这件事"难"
一次爬取不是"跑一个函数",而是一个可能持续几分钟、涉及上万次抓取的长任务。它天然带来四个规模问题:
| 问题 | 白话 | Firecrawl 的应对(本章主角) |
|---|---|---|
| 发现 | 我怎么知道站点有哪些页? | sitemap + 页内链接提取(WebCrawler) |
| 过滤 | 哪些链接该爬、哪些该丢? | filterLinks / filterURL(深度、正则、robots、外链…) |
| 去重 | 同一个页别抓两遍 | Redis visited 集合 + URL 规范化/排列 |
| 调度 | 上万作业怎么排队、限流、不丢 | 自建队列 NuQ |
1.4 用起来什么样
对使用者,爬取是异步的:提交后立刻拿到一个 crawl id,再去轮询状态。
# 1) 提交爬取,立刻返回一个 id(不阻塞)
curl -X POST https://api.firecrawl.dev/v2/crawl \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"url":"https://docs.example.com","limit":200,"scrapeOptions":{"formats":["markdown"]}}'
# => { "success": true, "id": "0190...", "url": ".../v2/crawl/0190..." }
# 2) 稍后用 id 轮询,拿到已完成的页面
curl https://api.firecrawl.dev/v2/crawl/0190... -H "Authorization: Bearer $KEY"
这跟单页 /v2/scrape 的同步返回是根本不同的执行模型——本章 §6 会讲清这条分水岭。