CrewAI FirecrawlCrawlWebsiteTool:让 Agent 爬取整个站点并转换为干净 Markdown 的完整指南
本文基于 CrewAI 仓库中 FirecrawlCrawlWebsiteTool 的官方工具文档与源码实现,完整讲解该工具的定位、安装方式、配置参数、初始化流程与 _run 执行链路。读完之后,你可以直接将该工具接入自己的 CrewAI Agent,让它通过一个 URL 爬取整个网站并得到可用于上下文检索的 Markdown/结构化数据,同时理解其底层的 URL 安全校验机制与测试验证方式。
工具定位:面向整站爬取的 Firecrawl 封装
工具文档对该工具的定义是:
Firecrawl is a platform for crawling and convert any website into clean markdown or structured data.
即 Firecrawl 是一个将任意网站爬取并转换为干净 Markdown 或结构化数据的平台,而 FirecrawlCrawlWebsiteTool 则是 CrewAI 官方工具集(crewai-tools)中将该能力封装为 Agent 工具的实现。与只抓取单页的 Scrape 类工具不同,该工具面向的是整站爬取(crawl):你只提供一个起始 URL,Firecrawl 会按照深度和数量限制把站点内的多个页面一并抓取,并统一输出为 Markdown,供 Agent 做知识库构建、站点调研或内容摘要等任务。
核心实现位于 firecrawl_crawl_website_tool.py,类结构非常简洁:
class FirecrawlCrawlWebsiteTool(BaseTool):
name: str = "Firecrawl web crawl tool"
description: str = "Crawl webpages using Firecrawl and return the contents"
args_schema: type[BaseModel] = FirecrawlCrawlWebsiteToolSchema
api_key: str | None = None
config: dict[str, Any] | None = ... # 默认爬取参数,见下文
需要注意一个版本口径:工具 README 声明 "This implementation is compatible with FireCrawl API v1",而源码 docstring 中描述的配置项(max_discovery_depth、allow_subdomains、delay 等)标注为 "Firecrawl v2 API"。从源码结构看,当前仓库实现已按 v2 参数体系组织默认配置,实际行为以源码 default_factory 中的默认值为准(详见后文"配置参数全解"一节)。
安装与前置准备
按照 README 的说明,准备工作分两步:
- 获取 API Key:从 Firecrawl 官方平台申请 API key,并设置为环境变量
FIRECRAWL_API_KEY; - 安装依赖:同时安装 Firecrawl Python SDK 与
crewai[tools]包:
pip install firecrawl-py 'crewai[tools]'
这两条要求与源码声明完全对应。源码中通过 Pydantic 字段显式声明了运行时依赖与环境变量:
package_dependencies: list[str] = Field(default_factory=lambda: ["firecrawl-py"])
env_vars: list[EnvVar] = Field(
default_factory=lambda: [
EnvVar(
name="FIRECRAWL_API_KEY",
description="API key for Firecrawl services",
required=True,
),
]
)
也就是说 FIRECRAWL_API_KEY 在工具元数据层面被标记为 required,CrewAI 框架在 Agent 装配工具时即可据此做依赖检查。此外,README 中的 Arguments 部分还说明了 api_key 构造参数:
api_key:可选。指定 Firecrawl API key;缺省时读取FIRECRAWL_API_KEY环境变量。config:可选。包含 Firecrawl API 的爬取参数,详见后文。
还有一个源码级别的细节值得了解:如果你忘记安装 firecrawl-py,工具在初始化时不会静默失败,而是会交互式提示自动安装:
def _initialize_firecrawl(self) -> None:
try:
from firecrawl import FirecrawlApp
self._firecrawl = FirecrawlApp(api_key=self.api_key)
except ImportError:
import click
if click.confirm(
"You are missing the 'firecrawl-py' package. Would you like to install it?"
):
import subprocess
subprocess.run(["uv", "add", "firecrawl-py"], check=True)
from firecrawl import FirecrawlApp
self._firecrawl = FirecrawlApp(api_key=self.api_key)
else:
raise ImportError(
"`firecrawl-py` package not found, please run `uv add firecrawl-py`"
) from None
(见 firecrawl_crawl_website_tool.py#L82-L105)当用户确认后它会直接执行 uv add firecrawl-py 完成补装;拒绝则抛出带安装命令提示的 ImportError。
快速上手
README 给出的标准用法示例如下(保留原文):
from crewai_tools import FirecrawlCrawlWebsiteTool
from firecrawl import ScrapeOptions
tool = FirecrawlCrawlWebsiteTool(
config={
"limit": 100,
"scrape_options": ScrapeOptions(formats=["markdown", "html"]),
"poll_interval": 30,
}
)
tool.run(url="firecrawl.dev")
用法要点:
- 工具通过
crewai_tools顶层包导出,from crewai_tools import FirecrawlCrawlWebsiteTool即可导入(导出注册见 tools/init.py); tool.run(url=...)的入参由 Pydantic 模式FirecrawlCrawlWebsiteToolSchema约束,只有一个必填参数url(描述为 "Website URL"),这也是该工具交给 LLM 决策的唯一参数:
class FirecrawlCrawlWebsiteToolSchema(BaseModel):
url: str = Field(description="Website URL")
config中允许传字典,也允许传 Firecrawl SDK 的ScrapeOptions对象,因为它们最终都会被展开进FirecrawlApp.crawl(...)的调用参数(机制见后文_run解析一节)。
需要特别提醒的一处文档与实现差异:README 示例将 poll_interval 放进了 config,但当前源码中轮询间隔是在调用处硬编码的:
return self._firecrawl.crawl(url=url, poll_interval=2, **self.config)
(见 firecrawl_crawl_website_tool.py#L107-L112)从源码看,若你在 config 中再传一次 poll_interval,展开后会与关键字参数重复而引发冲突。因此按当前仓库实现,不要在 config 里手动指定 poll_interval,轮询间隔固定为 2 秒。
配置参数全解
config 参数的默认值由源码 default_factory 定义(firecrawl_crawl_website_tool.py#L50-L64),这是当前实现的事实基准:
| 参数 | 类型 | 默认值 | 含义 |
|---|---|---|---|
max_discovery_depth |
int | 2 |
页面发现的最大深度 |
ignore_sitemap |
bool | True |
是否忽略站点 sitemap(True 表示不依赖 sitemap 发现页面) |
limit |
int | 10 |
最多爬取的页面数 |
allow_external_links |
bool | False |
是否允许爬取指向站外链接的页面 |
allow_subdomains |
bool | False |
是否允许爬取子域名 |
delay |
int | None | None |
请求间隔(毫秒) |
scrape_options |
dict | 见下 | 页面内容抓取选项 |
其中 scrape_options 的默认值为:
"scrape_options": {
"formats": ["markdown"], # 返回的内容格式
"only_main_content": True, # 只返回正文,排除 header/nav/footer
"timeout": 10000, # 抓取超时(毫秒)
}
对照 README 中给出的 "default configuration"(保留了原文档口径,便于与历史版本对照):
from firecrawl import ScrapeOptions
{
"max_depth": 2,
"ignore_sitemap": True,
"limit": 100,
"allow_backward_links": False,
"allow_external_links": False,
"scrape_options": ScrapeOptions(
formats=["markdown", "screenshot", "links"],
only_main_content=True,
timeout=30000,
),
}
两者存在明显差异:README 使用 max_depth / allow_backward_links 且 limit 为 100、超时 30 秒,而源码使用 max_discovery_depth / allow_subdomains 并新增了 delay,limit 收紧为 10、超时 10 秒。可以推断 README 的默认配置段落对应较早的 API 版本,而当前源码已按新参数体系重写。实际使用时请以源码 default_factory 为基准:如果你只想改个别项(例如把爬取上限放宽到 100 页),在 config 中只传覆盖项即可,其余项回落到上述源码默认值。
参数取舍上的实战建议(对应上表语义):
- 控制成本与耗时:
limit与max_discovery_depth是最直接的开关,小站调研用默认值即可,全站入库再调大; - 聚焦站点边界:
allow_external_links、allow_subdomains默认False,保证爬取范围不越出目标域名; - 内容形态:
scrape_options.formats决定输出形态,Agent 上下文场景通常只保留["markdown"]最省 token,需要截图或链接结构时再追加screenshot、links等格式。
初始化流程:从 api_key 到 FirecrawlApp
工具实例化的完整调用链如下(见 firecrawl_crawl_website_tool.py#L77-L86):
__init__(api_key=None, **kwargs)先调用BaseTool.__init__完成通用工具字段(name/description/schema 等)的初始化;- 记录
self.api_key——若传入None,则最终由FirecrawlApp从FIRECRAWL_API_KEY环境变量读取; _initialize_firecrawl()创建FirecrawlApp(api_key=self.api_key)实例并缓存到私有属性self._firecrawl(PrivateAttr,不参与 Pydantic 序列化)。
文件末尾还有一段防御性逻辑:在 firecrawl-py 可用时主动执行 FirecrawlCrawlWebsiteTool.model_rebuild()(并打上 _model_rebuilt 标记防止重复构建),用于让 Pydantic 模型正确解析对第三方类的引用;SDK 未安装时直接跳过,不影响模块导入。这个设计解释了为什么该工具文件顶部对 firecrawl 的 import 采用 try/except ImportError 包裹——未安装 SDK 时模块仍可被正常导入和发现,只在真正实例化时才触发依赖检查。
_run 深度解析:URL 安全校验与爬取调用
_run 方法只有三步(firecrawl_crawl_website_tool.py#L107-L112):
def _run(self, url: str) -> Any:
if not self._firecrawl:
raise RuntimeError("FirecrawlApp not properly initialized")
url = validate_url(url)
return self._firecrawl.crawl(url=url, poll_interval=2, **self.config)
其中最值得关注的是 validate_url。它来自 crewai-tools 统一的路径/URL 安全模块 safe_path.py,其模块 docstring 明确说明目的是"防止工具在运行时接受用户或 LLM 可控输入时发生未授权文件访问和 SSRF(服务端请求伪造)"。对 URL 的校验规则(safe_path.py#L198-L260):
- 协议白名单:完全禁止
file://,只允许http/https,且必须可解析出 hostname; - DNS 解析 + 私网拦截:对主机名做真实 DNS 解析,任何解析结果落入私网/保留网段的 URL 直接拒绝。被拦截的 IPv4 网段包括
10.0.0.0/8、172.16.0.0/12、192.168.0.0/16、127.0.0.0/8、169.254.0.0/16(云元数据地址)和0.0.0.0/32;IPv6 侧拦截::1/128、::/128、fc00::/7(ULA)与fe80::/10(链路本地)。IPv4-mapped IPv6 地址(如::ffff:127.0.0.1)会先解包为 IPv4 再比对,避免绕过; - 解析失败的保守策略:主机名无法解析、IP 无法解析为合法地址时,一律按"不安全"处理(block it);
- 逃生开关:设置
CREWAI_TOOLS_ALLOW_UNSAFE_PATHS=true可跳过校验(官方注释明确"不推荐在生产环境使用");多租户托管场景还可设置CREWAI_TOOLS_FORCE_SAFE_PATHS=true强制保持校验、禁止租户自行关闭。
这意味着:由于爬取 URL 往往由 LLM 在运行期生成,CrewAI 在把 URL 交给 Firecrawl 之前先做了一道 SSRF 防线,防止 Agent 被诱导去请求内网服务或云元数据端点。理解这一点,你就能明白为什么该工具"不能用于爬取内网页面",以及托管部署时相关环境变量的意义。
最后,self._firecrawl.crawl(url=url, poll_interval=2, **self.config) 把 config 字典逐项展开为 Firecrawl SDK crawl 方法的 kwargs,poll_interval=2 表示每 2 秒轮询一次爬取任务状态(Firecrawl 的 crawl 是异步任务,SDK 内部轮询直到完成或进入可判定状态)。因此 config 中传普通 dict 还是 SDK 的 ScrapeOptions 对象都能工作——它们都是 crawl() 接口接受的参数形态。
集成测试验证
仓库为这个工具提供了基于 VCR(录制回放)的集成测试 firecrawl_crawl_website_tool_test.py:
@pytest.mark.vcr()
def test_firecrawl_crawl_tool_integration():
tool = FirecrawlCrawlWebsiteTool(config={
"limit": 2,
"max_discovery_depth": 1,
"scrape_options": {"formats": ["markdown"]}
})
result = tool.run(url="https://firecrawl.dev")
assert result is not None
assert hasattr(result, 'status')
assert result.status in ["completed", "scraping"]
该测试印证了前文的几个结论:config 使用 max_discovery_depth 这一 v2 风格参数名;limit/scrape_options 等覆盖项可自由组合;_run 的返回值是一个带 status 属性的结果对象,状态生命周期包含 scraping(进行中)到 completed(完成)——如果你的集成代码需要判断爬取是否真正拿到数据,应以 result.status == "completed" 为准,而不是仅检查对象非空。测试标记 @pytest.mark.vcr() 表示 HTTP 交互通过录制好的 cassette 回放,无需真实 API key 即可在 CI 中运行。
相关文档与工具规格
- 本工具在仓库中随附的说明文档即 firecrawl_crawl_website_tool/README.md(本文的主要依据);
- 官方站点文档对应页面为 firecrawlcrawlwebsitetool.mdx,包含安装与参数摘要;
- 自动生成的工具规格文件 tool.specs.json 中收录了
FirecrawlCrawlWebsiteTool的完整 schema:init_params_schema(api_key、config两个可选构造参数)、run_params_schema(必填url)、env_vars(FIRECRAWL_API_KEY,required)与package_dependencies(firecrawl-py),可作为该工具机器可读的接口契约参考。
小结
FirecrawlCrawlWebsiteTool是 CrewAI 官方工具集中对接 Firecrawl 整站爬取能力的工具:给一个 URL,返回整站页面转换后的 Markdown/结构化数据(README)。- 接入只需:设置
FIRECRAWL_API_KEY、pip install firecrawl-py 'crewai[tools]',然后FirecrawlCrawlWebsiteTool(config={...})+tool.run(url=...);漏装 SDK 时初始化流程会交互式提示uv add firecrawl-py。 - 当前源码默认配置为
max_discovery_depth=2、limit=10、scrape_options={"formats": ["markdown"], "only_main_content": True, "timeout": 10000},allow_external_links/allow_subdomains默认关闭;README 中的旧版默认配置(limit=100等)仅作历史口径对照。 - 执行链路
_run= 初始化检查 +validate_urlSSRF 防御(仅 http/https,解析 DNS 并拦截私网/保留 IP)+FirecrawlApp.crawl(url, poll_interval=2, **config);当前实现中poll_interval固定为 2 秒,不应再放入config。 - 集成测试(VCR 回放)确认返回对象带
status字段,取值包括scraping与completed。
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 StartedRust0629
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