Firecrawl 入门:实用指南

data-science入门12 分钟阅读2026/7/22

上个月,我正在开发一款竞品分析工具,需要从十几个不同的 SaaS 定价页面抓取产品数据。一开始我用了经典的技术栈:requestsBeautifulSoup。起初效果挺好——直到我碰上了一个用 Next.js 搭建的网站。返回的 HTML 是空的,数据全被锁在 JavaScript 渲染里,结果我整整一个周末都在跟无头浏览器的配置作斗争,还一直超时。

就在这时候,一位同事向我推荐了 Firecrawl。它的卖点很简单:给它一个 URL,就能拿回干净的数据。不用管浏览器,不用解析 DOM,也不用写那些一改类名就挂掉的脆弱 CSS 选择器。我当时挺怀疑的——这种宣称“开箱即用”的工具往往都不太灵——但我还是试了试。下面就是我在真实项目里实际配置和使用它的经验总结。

准备工作

首先:去 firecrawl.dev 注册并获取 API key。免费额度给的额度足够你好好测试一番了,而且不需要绑信用卡。

安装 Python SDK:

pip install firecrawl-py

然后把你的 API key 设为环境变量:

export FIRECRAWL_API_KEY="fc-your-key-here"

我一开始犯了个错,把 key 直接硬编码到了脚本里。千万别这么干——把它放到 .env 文件里,像个靠谱的开发者那样用 python-dotenv 来读取。

基础用法:抓取单个页面

咱们从简单的开始。我想抓取一篇博客文章的内容,喂给后续的摘要生成流程:

from firecrawl import FirecrawlApp

app = FirecrawlApp(api_key="your-api-key")

result = app.scrape_url("https://example.com/blog/some-article")
print(result["markdown"])

就这么简单。一次调用,你拿到的就是干净的 Markdown,而不是那种混杂着导航栏、页脚和 Cookie 弹窗的乱糟糟的 HTML。我第一次跑这段代码的时候,真的被输出的干净程度惊到了——完全不需要写正则去清理。

/scrape 接口每次调用消耗 1 个额度(credit)。你也可以请求不同的格式:

result = app.scrape_url("https://example.com", params={
    "formats": ["markdown", "html", "links", "screenshot"]
})

我发现 links 格式特别好用,当我想在正式爬取前先发现子页面时,它帮了大忙。而 screenshot 格式则让我免去了单独配置 Playwright 来做视觉验证的麻烦。

重头戏:结合 Pydantic 做结构化提取

这才是 Firecrawl 真正戳中我痛点的地方。你不需要写 CSS 选择器去提取特定字段,只要定义一个 Pydantic schema,描述清楚你想要什么数据就行。Firecrawl 会在底层用 AI 帮你搞定提取。

针对我的定价页项目,我定义了这样一个 schema:

from pydantic import BaseModel, Field

class PricingPlan(BaseModel):
    plan_name: str = Field(description="The name of the pricing tier")
    monthly_price: str = Field(description="The monthly price, including currency symbol")
    features: list[str] = Field(description="Key features listed for this plan")
    is_free: bool = Field(description="Whether this plan is free")

class PricingPage(BaseModel):
    company_name: str = Field(description="Name of the company")
    plans: list[PricingPlan] = Field(description="All pricing tiers listed on the page")

然后把它传给抓取调用:

result = app.scrape_url("https://example.com/pricing", params={
    "formats": ["extract"],
    "extract": {
        "schema": PricingPage.model_json_schema()
    }
})

print(result["extract"])

输出的结果是一个符合你 schema 的、规规矩矩的结构化 JSON 对象。不用写正则,不用写 XPath,也不用祈祷网站别改版。这总共花费 5 个额度(1 个用于抓取 + 4 个用于提取),跟我以前花在维护脆弱解析器上的无数小时相比,这钱花得太值了。

途中我踩过一个坑:如果你只用提示词而不用 schema,字段名在不同次的运行中可能会变。用了 Pydantic schema,字段名就锁死了,当你需要把输出传给下游需要特定键名的代码时,这点至关重要。所以在生产环境中,一定要用 schema 方式。

规模扩展:爬取整个网站

抓取单页还行,但我需要整站的数据。Firecrawl 的 crawl 方法一次调用就能搞定:

result = app.crawl_url("https://example.com", params={
    "crawlerOptions": {
        "includes": ["/blog/*", "/docs/*"],
        "exclude": ["/blog/page/*"],
        "maxDepth": 3
    },
    "limit": 50
})

它会爬取网站,跟踪链接,并遵循你的包含/排除模式。不用手动管理 URL 队列,不用调试递归爬取逻辑。limit 参数限制了总页面数——这很重要,免得你在巨型网站上把额度烧光。

爬取默认是异步运行的。你可以检查状态或等待完成:

# 立即返回一个 job ID
crawl_status = app.crawl_url("https://example.com", params={"limit": 100})

# 轮询结果
while crawl_status["status"] != "completed":
    time.sleep(2)
    crawl_status = app.check_crawl_status(crawl_status["id"])

我吃过血亏:如果不加 includes 模式去爬大站,你的额度会瞬间见底。一定要把爬取范围缩小到你真正需要的板块。

应对重度依赖 JavaScript 的网站

这正是最初把我逼上这条路的难题。用 React、Next.js 或类似框架构建的网站,用 requests 拿到的往往是空 HTML,因为内容是在客户端渲染的。

用 Firecrawl,你根本不用操心这个。它会在云端自动处理 JavaScript 渲染。我拿一个用 requests 啥也抓不到的 Next.js 仪表盘试了试——Firecrawl 第一次尝试就拿到了完整的渲染内容。

对于需要交互(点击按钮、在搜索框输入)的页面,你可以使用 actions 参数:

result = app.scrape_url("https://example.com/login", params={
    "actions": [
        {"type": "type", "selector": "#email", "value": "test@example.com"},
        {"type": "type", "selector": "#password", "value": "mypassword"},
        {"type": "click", "selector": "button[type='submit']"},
        {"type": "wait", "milliseconds": 3000},
        {"type": "screenshot"}
    ]
})

它会在云端启动一个真实的浏览器,按顺序执行你的操作,然后返回最终状态。我在一个客户项目里用它抓取登录墙后面的数据——确实管用,但速度更慢,消耗的额度也更多,所以只有在你真的需要交互时才去用它。

保存结果

拿到数据后,你得找个地方存起来。这里有一个简单的模式,可以把结果保存为不同格式:

import json
import csv
import sqlite3

# 存为 JSON,供 Web 应用使用
with open("pricing_data.json", "w") as f:
    json.dump(result["extract"], f, indent=2)

# 存为 CSV,方便用电子表格分析
plans = result["extract"]["plans"]
with open("pricing.csv", "w", newline="") as f:
    writer = csv.DictWriter(f, fieldnames=["plan_name", "monthly_price", "is_free"])
    writer.writeheader()
    writer.writerows(plans)

# 存入 SQLite,方便查询
conn = sqlite3.connect("pricing.db")
cursor = conn.cursor()
cursor.execute("""
    CREATE TABLE IF NOT EXISTS plans 
    (company TEXT, plan_name TEXT, price TEXT, is_free BOOLEAN)
""")
for plan in plans:
    cursor.execute("INSERT INTO plans VALUES (?, ?, ?, ?)",
                   (result["extract"]["company_name"], 
                    plan["plan_name"], 
                    plan["monthly_price"], 
                    plan["is_free"]))
conn.commit()

实用技巧与客观局限

用了 Firecrawl 几周后,这些是我希望一开始就能知道的事:

爬取前先用 map map 接口能快速返回网站上的所有 URL,但不会去抓取内容。在决定全面爬取前,先用它摸清网站结构。

爬取时务必设置 limit 不设的话,你可能一不小心就爬了几千个页面。我曾在某个文档网站上白白烧掉 200 个额度,等反应过来已经晚了。

includes 模式用起来。 缩小爬取范围,速度更快、更省钱,数据也更干净。

开发阶段在本地缓存结果。 我写了个简单的装饰器,把抓取结果存到磁盘,后续运行时直接读取。这在反复调试提取 schema 时帮我省下了海量额度。

免费额度真的够用来测试。 你有足够的额度来验证方案是否可行,然后再考虑付费计划。

支持批量抓取。 如果你有几百个 URL,用 batch_scrape()(同步)或 start_batch_scrape()(异步),别在循环里挨个调用。

再来说说局限。如果你大规模抓取,Firecrawl 并不便宜——额度消耗得很快,尤其是开了提取功能的话。如果你要大量抓取简单的静态网站,requests + BeautifulSoup 依然更具性价比。它的核心价值其实在于 JavaScript 渲染、结构化提取,以及免去了维护爬虫基础设施的麻烦。另外,提取的准确率并非百分百。据我估算,在结构良好的页面上,它大概有 90% 的时候能提取对数据。如果是关键数据,你需要在下游加上校验逻辑。

最后,免费额度有速率限制。如果你要跑每分钟抓取几百个页面的生产级流水线,那就得买付费版了。

在我的项目里,Firecrawl 用大约 30 行真正能在不同网站架构上跑通的代码,替换掉了原来 300 行脆弱的抓取代码。这笔买卖我愿意再做一次——但那是因为我确实需要 JavaScript 渲染和结构化提取。对于简单任务,老工具依然好使。按需选择就好。

相关 Agent

J

Jupyter AI

Jupyter笔记本的AI助手

了解更多 →