news 2026/9/30 6:44:08

Firecrawl Scraping 负载测试实战:从 61.6% 失败率到稳定承载 9000 请求的压测调优记录

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Firecrawl Scraping 负载测试实战:从 61.6% 失败率到稳定承载 9000 请求的压测调优记录
  • 网页爬虫
  • 后端
  • AI 应用

【免费下载链接】firecrawl

The web data API to search, scrape, and interact at scale. 🔥

项目地址:https://gitcode.com/GitHub_Trending/fi/firecrawl
点击查看免费下载

本篇技术指南以 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(迈阿密区域)应用机器:

MachineSize/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):

MetricValue
errors.ETIMEDOUT5473
errors.Failed capture or match73
http.codes.2003454
http.codes.40164
http.codes.4029
http.downloaded_bytes0
http.request_rate21/sec
http.requests9000
http.response_time.min929
http.response_time.max9919
http.response_time.mean3682.1
http.response_time.median3395.5
http.response_time.p958024.5
http.response_time.p999607.1
http.responses3527
vusers.completed3454
vusers.created9000
vusers.created_by_name.Scrape a URL9000
vusers.failed5546
vusers.session_length.min1127.6
vusers.session_length.max9982.2
vusers.session_length.mean3730.6
vusers.session_length.median3464.1
vusers.session_length.p957865.6
vusers.session_length.p999607.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。

结论与下一步:从"打满即崩"到可伸缩架构

本轮结论

  1. 性能:系统在 9000 请求下力不从心,产生 5473 个超时,平均响应时间 3682.1ms;
  2. 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 服务)压测与调优的几条核心经验:

  1. 瓶颈定位要分资源维度:第 2 轮中 CPU 打满而内存充裕,说明调优重点在"计算能力/并发承载",而非内存扩容;抓取类服务的 HTML 解析、渲染与转 Markdown 均为 CPU 密集操作(Firecrawl 的文档解析、抓取转换逻辑集中在 apps/api/src 下),单核机器扛不住高峰并发。
  2. 失败率要区分"真故障"与"客户端超时假象":ETIMEDOUT 与 502 的成因完全不同——前者是客户端等不到响应,后者是网关拒绝。第 5 轮证明调高客户端超时能大幅"降低"失败率,但这不代表系统更快,只代表队列更深。
  3. 弹性伸缩需要软硬限配合:soft_limit决定何时启动新机器(越低越早扩容),hard_limit决定网关何时拒绝新请求(越低越早保护)。第 3、4 轮的对比说明,两者需要在"扩容及时性"与"保护性拒绝"之间反复校准。
  4. 异步任务链路要压到真正的执行层:/crawl 的提交接口响应快(第 6 轮平均 838.1ms),真正的压力在 worker 与 fire-engine 执行层(第 7 轮 22 分钟打满)。只压 API 入口会漏掉执行层瓶颈,worker 数量(如NUM_WORKERS_PER_QUEUE)与自动扩容策略必须纳入观测。
  5. 用脚本固化压测流程:把 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. 🔥

项目地址:https://gitcode.com/GitHub_Trending/fi/firecrawl
点击查看免费下载

相关推荐

上一篇:orx instance完整教程:创建、列出与销毁计算实例
下一篇:FoundationDB 读写路径深度解析:事务读路径、写路径与并发排序机制

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/30 6:41:13

冴羽 JavaScript 专题:数组扁平化从递归手写到 underscore 源码解读

技术博客文档教程 【免费下载链接】Blog 冴羽写博客的地方,预计写四个系列:JavaScript深入系列、JavaScript专题系列、ES6系列、React系列。 项目地址: https://gitcode.com/GitHub_Trending/blo/Blog 点击查看 免费下载 本篇是冴羽「JavaSc…

作者头像 李华
网站建设 2026/9/30 6:39:58

FPGA跨时钟域设计:亚稳态原理、两级同步器与异步FIFO实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华