- 网页爬虫
- 后端
- AI 应用
【免费下载链接】firecrawl
The web data API to search, scrape, and interact at scale. 🔥
本篇技术指南以 Firecrawl 开源仓库中 测试套件压测结果记录 为主体,完整还原第 2 轮负载测试(9000 请求、4 阶段加压、61.6% 失败率)的实验环境、Artillery 压测配置与逐项指标分析,并结合同一仓库中第 3~8 轮测试记录,给出 CPU 瓶颈定位、Fly.io 并发限流配置、Artillery 超时调整与弹性伸缩(Autoscaling)等可落地的优化路径。读者读完将掌握如何用 Artillery 对 Firecrawl 的/scrape与/crawl接口进行分阶段加压测试、如何解读压测报告中的超时与响应时间指标,以及如何通过 Fly.io 并发配置与 worker 参数迭代完成一轮完整的性能调优闭环。
测试背景:为什么要做第 2 轮压测
Firecrawl 提供 search、scrape、crawl、map 等网页数据抓取 API。为验证服务在大流量下的稳定性,仓库维护者在 apps/test-suite 目录下搭建了基于 Artillery 的负载测试体系,并连续执行了多轮测试(仓库中保留了 tests-1-5 与 tests-6-7 两组共 8 份报告)。
第 1 轮测试(详见 load-test-1.md)仅以arrivalRate: 10恒定速率运行 60 秒,600 个请求全部返回 HTTP 200,平均响应时间 1380.1 ms,两台机器 CPU 峰值约 50%,但压测后内存未回落到基线,提示可能存在内存泄漏。第 2 轮测试的目标正是针对更高负载验证系统极限:将请求量放大 15 倍至 9000,并通过 4 个阶段模拟"爬坡—峰值—冷却"的真实流量曲线,观察系统在 CPU 打满、请求排队等极端场景下的表现。
测试环境:两台 2048MB 的 Fly.io 应用机器
第 2 轮压测的实验环境为两台 Fly.io 上运行的mia(迈阿密区域)应用机器:
| Machine | Size/CPU |
|---|---|
| e286de4f711e86 mia (app) | performance-cpu-1x@2048MB |
| 73d8dd909c1189 mia (app) | performance-cpu-1x@2048MB |
单核 CPU 搭配 2048MB 内存的机型,用于检验默认部署(未开启自动扩容)在突增流量下的承载能力。从后续轮次(load-test-3.md)可以看到,这套环境随后补充了 3 台paused状态的机器用于测试自动扩缩容,说明第 2 轮结果直接触发了基础设施层面的调优决策。
压测配置:9000 请求 · 7 分 11 秒 · 4 阶段加压
第 2 轮使用的 Artillery 场景配置记录在测试报告中,与仓库根部的 load-test.yml 一脉相承。压测负载由 4 个 phase 组成:
# load-test.yml - duration: 60 arrivalRate: 10 # Initial load - duration: 120 arrivalRate: 20 # Increased load - duration: 180 arrivalRate: 30 # Peak load - duration: 60 arrivalRate: 10 # Cool down- Initial load(60s,10 req/s):预热阶段,建立初始连接与并发基线;
- Increased load(120s,20 req/s):翻倍加压,观察系统在持续增长流量下的表现;
- Peak load(180s,30 req/s):峰值阶段,持续 3 分钟逼近系统吞吐上限;
- Cool down(60s,10 req/s):回落到初始速率,观察资源是否随负载释放。
按各阶段速率累加,共产生 9000 个请求。每个虚拟用户(vuser)对应一次/scrape调用——从报告中的vusers.created_by_name.Scrape a URL: 9000可以确认。
仓库中的完整 Artillery 配置参考
虽然第 2 轮报告只摘录了 phases 部分,仓库根部的 load-test.yml 给出了完整可运行的配置,包含第 2 轮报告未展开的http.timeout与场景(scenario)定义:
config: target: "https://staging-firecrawl-scraper-js.fly.dev/v0" http: timeout: 30 phases: - duration: 60 arrivalRate: 1 # Initial load - duration: 120 arrivalRate: 2 # Increased load - duration: 180 arrivalRate: 3 # Peak load - duration: 60 arrivalRate: 1 # Cool down defaults: headers: Authorization: "Bearer YOUR_API_KEY" scenarios: - name: Crawl a URL flow: - post: url: "/crawl" json: url: "https://rsseau.fr" crawlerOptions: limit: 100 pageOptions: onlyMainContent: true capture: - json: "$.jobId" as: job_id - think: 10 - get: url: "/crawl/status/{{ job_id }}" capture: - json: "$.status" as: crawl_status until: - condition: "equals" value: "completed" variable: "crawl_status" retry: count: 20 wait: 10这个配置展示了两个关键工程细节:一是target指向staging环境(staging-firecrawl-scraper-js.fly.dev/v0),压测必须与生产隔离;二是场景中通过capture从/crawl响应里提取$.jobId,再轮询/crawl/status/{jobId}直到状态变为completed,模拟真实的异步抓取任务提交—轮询链路。think: 10与retry/wait组合用于控制轮询节奏,避免空转打爆接口。
提示:当前 load-test.yml 的 phases 和场景分别对应
/crawl的注释版,/scrape与/search场景以注释形式保留(Scrape a URL、Search for a query)。第 2 轮报告中的 9000 个Scrape a URLvuser 说明测试时使用的是 scrape 场景,可按需在文件中切换启用。
如何运行这套压测
按照 test-suite README 的说明,运行负载测试需要先全局安装 Artillery,再执行artillery run:
npm install -g artillery artillery run load-test.yml若希望把完整 JSON 报告输出到 load-test-results 目录(与仓库中test-run-report.json的做法一致),可以使用仓库 package.json 中定义的脚本:
npm run test:load # 等价于:artillery run --output ./load-test-results/test-run-report.json load-test.yml运行前请将配置中的YOUR_API_KEY替换为有效的 API Key,并将target指向自己的测试环境。
Artillery 报告逐项解读:9000 请求、5473 次超时
第 2 轮压测的核心结果如下(报告日期 13:50:08 -0300):
| Metric | Value |
|---|---|
| errors.ETIMEDOUT | 5473 |
| errors.Failed capture or match | 73 |
| http.codes.200 | 3454 |
| http.codes.401 | 64 |
| http.codes.402 | 9 |
| http.downloaded_bytes | 0 |
| http.request_rate | 21/sec |
| http.requests | 9000 |
| http.response_time.min | 929 |
| http.response_time.max | 9919 |
| http.response_time.mean | 3682.1 |
| http.response_time.median | 3395.5 |
| http.response_time.p95 | 8024.5 |
| http.response_time.p99 | 9607.1 |
| http.responses | 3527 |
| vusers.completed | 3454 |
| vusers.created | 9000 |
| vusers.created_by_name.Scrape a URL | 9000 |
| vusers.failed | 5546 |
| vusers.session_length.min | 1127.6 |
| vusers.session_length.max | 9982.2 |
| vusers.session_length.mean | 3730.6 |
| vusers.session_length.median | 3464.1 |
| vusers.session_length.p95 | 7865.6 |
| vusers.session_length.p99 | 9607.1 |
关键指标解读:
- errors.ETIMEDOUT = 5473:压倒性的失败来源。所谓 ETIMEDOUT 是 Artillery 在客户端侧捕获的 TCP 连接超时——即客户端在
http.timeout: 30秒内未能收到响应。5473 个超时占总请求的 60.8%,是 61.6% 失败率的绝对主因。 - vusers.failed = 5546(61.6%):vuser 总数 9000,成功 3454,失败 5546。其中 5473 个为超时,73 个为
Failed capture or match(响应返回但 JSON 提取断言未匹配,说明服务端可能在过载时返回了非预期内容)。 - http.responses = 3527,与 200/401/402 之和一致:3454 + 64 + 9 = 3527。除 200 外,出现了 64 个 401(鉴权失败)与 9 个 402(Payment Required,通常是配额/计费限制),表明过载环境下部分请求在进入处理流程前就被网关或鉴权层拒绝。
- http.downloaded_bytes = 0:Artillery 未统计到下载字节,间接说明大量请求未能完整走完抓取流程拿到正文内容。
- 响应时间分布严重恶化:
mean 3682.1ms、median 3395.5ms,而 p95 高达 8024.5ms、p99 9607.1ms,最大值 9919ms 逼近客户端超时上限 30s 的 1/3。尾部延迟(p95/p99)与中位数之间拉出 2.4~2.8 倍差距,是典型的排队拥塞特征——请求在队列中等待,服务端 CPU 已无法及时处理。 - session_length 与 response_time 高度一致:p99 同为 9607.1ms,说明会话耗时几乎全部消耗在 HTTP 请求往返上,而非客户端脚本逻辑。
机器资源曲线:CPU 双机打满是瓶颈铁证
测试报告中引用的资源监控图 metrics-test-2.png 直观呈现了四个面板:
- 内存利用率:系统总内存稳定在 1.8GiB 左右,两个节点的内存使用在 0~512MiB 区间平稳波动,无异常飙升——内存不是本轮瓶颈;
- CPU 利用率:测试启动后两个节点快速爬升,13:46~13:50 期间黄色节点(73d8dd909c1189)逼近 100%,蓝色节点(e286de4f711e86)峰值约 95%,属于满负载运行;
- 应用并发数:随负载同步增长,峰值约在 13:48 出现,单节点并发接近 170~190;
- 5 分钟负载均值:与 CPU、并发曲线吻合,13:50 左右达到峰值(约为系统容量的 90%),测试结束后回落。
两张图(报告截图 + 文字描述)共同指向同一结论:CPU 是第 2 轮的硬瓶颈。两台单核机器在持续加压下双双打满,请求在队列中排队,最终在客户端 30s 超时窗口内无法完成,产生数千个 ETIMEDOUT。
结论与下一步:从"打满即崩"到可伸缩架构
本轮结论
- 性能:系统在 9000 请求下力不从心,产生 5473 个超时,平均响应时间 3682.1ms;
- CPU 利用率:两台机器均达到 100%,造成严重性能退化与高失败率。
仓库后续轮次的调优路径(可复用的完整闭环)
第 2 轮报告给出的 Next Step 只有一句——"在 Fly.io 上实现自动扩缩容方案,并用相同配置复测"。而仓库中保留的第 3~8 轮报告,恰好完整记录了这一建议被逐步落实的全过程,构成一套可借鉴的性能调优闭环:
Step 1 —— 开启 Fly.io 自动扩缩容(第 3 轮,见 load-test-3.md)在fly.staging.toml中为 http 服务配置并发限流与弹性伸缩:
# fly.staging.toml [http_service.concurrency] type = "requests" hard_limit = 100 soft_limit = 75测试环境变为 5 台机器(2 台常开 + 3 台初始 paused)。压测中 3 台机器自动扩容启动,超时从 5473 降至 653(7.3%),平均响应时间降到 3037.2ms,但仍有 2 个 502。结论:自动扩容有效,但 soft limit 触发偏慢。
Step 2 —— 调整软/硬限流参数(第 4 轮,见 load-test-4.md)将 soft_limit 降到 50、hard_limit 保持 100,期望机器更早启动:
[http_service.concurrency] type = "requests" hard_limit = 100 soft_limit = 50结果:502 消失(说明机器启动变快、不再因排队被网关拒绝),但超时反弹到 1329(14.8%),平均响应时间 3547.9ms。结论:需要转向调整 Artillery 侧的超时配置。
Step 3 —— 提高 Artillery 客户端超时(第 5 轮,见 load-test-5.md)
http: timeout: 30在 30s 客户端超时下,9000 请求中 8996 个成功,仅 4 个 502(0.04%),失败率断崖式下降。但代价是平均响应时间拉长到 5661.8ms、峰值 18924ms——系统在"排队硬扛"而非"快速处理"。这轮的意义在于:调整压测客户端超时需要与真实用户体验/网关超时统一口径,过短的客户端超时会高估系统故障。
Step 4 —— 转向 /crawl 异步链路压测(第 6~8 轮,见 load-test-6.md、load-test-7.md、load-test-8.md)抓取策略切换为 fire-engine 后,压测对象变成app + worker + fire-engine三层拓扑。第 7 轮中 fire-engine 机器在处理队列 22 分钟后 CPU/内存双双打满导致测试中断,暴露出 worker 数与资源管理缺陷;第 8 轮将NUM_WORKERS_PER_QUEUE从 8 提升到 12,fire-engine 机器 CPU 仍达 99%、内存 92%,但队列能全部处理完,同时暴露了 autoscaling 未按预期启动备用 worker 机器的问题。
Step 5 —— 沉淀为自动化脚本整个压测流程最终固化在 package.json 的test:load脚本与 load-test.yml 中,报告统一输出到 load-test-results 目录,形成"改配置 → 跑压测 → 存报告 → 分析 → 再改配置"的可持续迭代机制。
实战经验总结
综合第 2~8 轮共 7 份报告,可以提炼出 Firecrawl(以及任何基于 Fly.io 的抓取 API 服务)压测与调优的几条核心经验:
- 瓶颈定位要分资源维度:第 2 轮中 CPU 打满而内存充裕,说明调优重点在"计算能力/并发承载",而非内存扩容;抓取类服务的 HTML 解析、渲染与转 Markdown 均为 CPU 密集操作(Firecrawl 的文档解析、抓取转换逻辑集中在 apps/api/src 下),单核机器扛不住高峰并发。
- 失败率要区分"真故障"与"客户端超时假象":ETIMEDOUT 与 502 的成因完全不同——前者是客户端等不到响应,后者是网关拒绝。第 5 轮证明调高客户端超时能大幅"降低"失败率,但这不代表系统更快,只代表队列更深。
- 弹性伸缩需要软硬限配合:
soft_limit决定何时启动新机器(越低越早扩容),hard_limit决定网关何时拒绝新请求(越低越早保护)。第 3、4 轮的对比说明,两者需要在"扩容及时性"与"保护性拒绝"之间反复校准。 - 异步任务链路要压到真正的执行层:/crawl 的提交接口响应快(第 6 轮平均 838.1ms),真正的压力在 worker 与 fire-engine 执行层(第 7 轮 22 分钟打满)。只压 API 入口会漏掉执行层瓶颈,worker 数量(如
NUM_WORKERS_PER_QUEUE)与自动扩容策略必须纳入观测。 - 用脚本固化压测流程:把 Artillery 配置、报告输出路径写进 package.json,让性能回归成为可重复的工程动作,而不是一次性手工操作。
相关资源导航
- 本文主体:第 2 轮压测报告(及其同目录下的 load-test-1.md、load-test-3.md、load-test-4.md、load-test-5.md)
- 压测工具链:Artillery 配置、package.json 脚本、测试套件说明
- 后续轮次:/crawl 与 fire-engine 压测、load-test-7.md、load-test-8.md
- 被压测的服务端实现:Firecrawl API 源码位于 apps/api/src,其中 /scrape 与 /crawl 路由 及 scraper 相关模块 是理解压测行为背后逻辑的入口
说明:本文数据均来自仓库 apps/test-suite/load-test-results 下的原始压测报告,未做任何外部假设;压测环境为 Firecrawl 的 staging 部署(
staging-firecrawl-scraper-js.fly.dev),结果反映该特定机型(performance-cpu-1x@2048MB)与特定配置组合下的表现,生产环境复测时应以自身部署参数为准。
- 网页爬虫
- 后端
- AI 应用
【免费下载链接】firecrawl
The web data API to search, scrape, and interact at scale. 🔥
相关推荐
Firecrawl 抓取接口负载压测实战:9000 并发请求下的 30 秒超时调优与稳定性验证
Firecrawl 抓取接口负载压测实战:9000 并发请求下的 30 秒超时调优与稳定性验证 本篇指南以 Firecrawl 开源仓库中记录的第五轮抓取(Sc
网页爬虫后端AI 应用Firecrawl 负载测试实战:用 Artillery 压测 Scrape 接口与 Fly.io 性能调优
Firecrawl 负载测试实战:用 Artillery 压测 Scrape 接口与 Fly.io 性能调优 本指南以 Firecrawl 开源仓库中 load
网页爬虫后端AI 应用Vegeta HTTP负载测试终极指南:从零开始掌握9000+请求的威力
Vegeta HTTP负载测试终极指南:从零开始掌握9000+请求的威力 Vegeta是一款功能强大的HTTP负载测试命令行工具和Go库,专门设计用于以恒定请求
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考