首页
/ Puppeteer HTTPRequest.failure() 深度解析:捕获请求失败并读取 errorText

Puppeteer HTTPRequest.failure() 深度解析:捕获请求失败并读取 errorText

2026-09-07 13:19:04作者:俞予舒Fleming

导读

HTTPRequest.failure() 是 Puppeteer 中判断网络请求是否失败的入口方法。页面发出的每个网络请求,在生命周期结束时会触发 requestfinishedrequestfailed 事件,而 failure() 的作用就是在一个失败请求的 requestfailed 回调中,取出可读的失败原因(如 net::ERR_FAILED)。本文将结合该方法的官方文档声明与 puppeteer-core 的 CDP、WebDriver BiDi 双协议实现,讲清它的签名语义、触发场景、与 requestfinished 的边界划分,并给出可直接落地的日志监控、资源诊断与故障排查代码。


一、方法签名与返回值语义

failure() 是抽象基类 HTTPRequest 上的抽象方法,在 docs/api/puppeteer.httprequest.md 中登记的完整声明如下(对应 API 声明文档):

class HTTPRequest {
  abstract failure(): {
    errorText: string;
  } | null;
}

返回类型: { errorText: string } | null

核心语义可以拆成三点:

  1. 请求成功时永远返回 null——只要请求最终被服务端正常响应,failure() 就是 null
  2. 请求失败时返回对象——形如 { errorText: string },其中 errorText 是人类可读的失败原因文本,例如 Chrome 上的 net::ERR_FAILED
  3. 失败也不保证一定有文本——即使请求真的失败了,errorText 也"不一定存在"。这正是指南标题文档中那句 It is not guaranteed that there will be failure text if the request fails 的含义。因此在业务代码中,应把 errorText 视为可选字段而非必然存在,这也是官方示例使用解构取值时值得注意的边界。

方法在抽象基类中的位置可参见 packages/puppeteer-core/src/api/HTTPRequest.ts 的 JSDoc 注释与抽象签名,它的注释内容与该 API 文档完全一致,属于核心请求对象的稳定接口之一。


二、生命周期背景:何时会触发失败

failure() 单独使用没有意义,它必须配合页面请求事件体系来理解。根据 docs/api/puppeteer.httprequest.mddocs/api/index.md 中的说明,每当页面发出一个网络请求,Puppeteer 的 page 对象会按以下规则派发事件:

  • request:请求被页面发出时触发;
  • requestfinished:响应体下载完成、请求正常结束时触发;
  • requestfailed:请求在某个环节失败时触发,它替代 requestfinished,二者在同一请求上互斥;
  • 上述事件回调中拿到的都是代表该请求的 HTTPRequest 实例。

因此,failure() 的正确使用场景几乎总是在 requestfailed 回调中。事件与 failure() 的对应关系可以参考 docs/api/puppeteer.pageevent.md 中关于 requestfailed(事件名 "requestfailed")的说明。

关键边界:HTTP 错误码 ≠ 请求失败

一个非常容易混淆的点是:HTTP 错误响应(如 404、503)在 HTTP 协议层面属于成功响应,请求会照常以 requestfinished 结束,而不会触发 requestfailed。也就是说,用 page.goto() 打开一个 404 页面、加载一个 503 的资源,在请求生命周期上并不算"失败"。

同理,重定向(redirect)也不构成失败:请求收到 3xx 重定向响应后会以 requestfinished 正常结束,随后浏览器向重定向目标发出一个新请求,新请求会继续走独立的生命周期。请求失败通常意味着连接层、DNS、TLS、超时、被浏览器取消或被人为中断等更底层的问题。


三、源码级原理:CDP 与 WebDriver BiDi 的两套实现

Puppeteer 支持基于 CDP(Chrome DevTools Protocol)和基于 WebDriver BiDi 两种协议后端。failure() 作为抽象方法在两类请求对象上各有落地实现,从源码结构看两者共享同一套对外语义,但数据来源不同。

3.1 CDP 实现:由 Network.loadingFailed 事件驱动

在 CDP 通道中,请求失败信息由网络管理器统一收集。关键链路位于 packages/puppeteer-core/src/cdp/NetworkManager.ts#emitLoadingFailed

#emitLoadingFailed(client, event) {
  const request = this.#networkEventManager.getRequest(event.requestId);
  // 部分 requestId 永远不会收到 requestWillBeSent 事件(见 crbug.com/750469)
  if (!request) return;

  this.#adoptCdpSessionIfNeeded(client, request);
  request._failureText = event.errorText;        // 写入浏览器下发的失败文本
  const response = request.response();
  if (response) {
    response._resolveBody();                     // 让挂起取 body 的调用尽快结束
  }
  this.#forgetRequest(request, true);
  this.emit(NetworkManagerEvent.RequestFailed, request);   // 最终派发 requestfailed
}

可以清晰看到:浏览器通过 CDP 的 Network.loadingFailed 事件上报 errorText,Puppeteer 把它暂存到请求对象内部字段,随后派发 RequestFailed 事件。这一事件最终经由 packages/puppeteer-core/src/cdp/Page.ts 转发为面向使用者的 pagerequestfailed 事件。

CdpHTTPRequest.failure() 的实现位于 packages/puppeteer-core/src/cdp/HTTPRequest.ts

override failure(): {errorText: string} | null {
  if (!this._failureText) {
    return null;                 // 没有失败文本,按"未失败"处理
  }
  return {
    errorText: this._failureText,
  };
}

注意这里的细节:即便底层确实发生了失败,如果 CDP 上报的 errorText 为空字符串或未定义,该方法也会返回 null。这正是文档中"失败也不保证有 failure text"警告的实现体现——failure() 判空不能 100% 等价于判断请求是否失败,判失败应优先以是否收到 requestfailed 事件为准。

3.2 WebDriver BiDi 实现:读取请求对象的 error 字段

在 BiDi 协议通道中,BidiHTTPRequest 直接使用协议层请求对象的 error 字段,见 packages/puppeteer-core/src/bidi/HTTPRequest.ts

override failure(): {errorText: string} | null {
  if (this.#request.error === undefined) {
    return null;
  }
  return {errorText: this.#request.error};
}

从源码结构看,BiDi 后端与 CDP 后端对使用者的可见行为保持一致:error 未定义(即未失败)时返回 null,否则返回包装后的 { errorText }


四、官方示例与实战模式

4.1 官方示例:记录所有失败请求

failure() API 文档 给出的官方示例是监听 requestfailed 并打印失败请求的 URL 与错误文本:

page.on('requestfailed', request => {
  console.log(request.url() + ' ' + request.failure().errorText);
});

4.2 更稳健的写法

考虑到"失败时不保证有 errorText"以及 failure() 理论上可能返回 null 两种边界,生产代码建议写成防御式版本,把失败信息结构化收集而不是直接拼字符串:

const failedRequests: Array<{url: string; errorText: string | null}> = [];

page.on('requestfailed', request => {
  failedRequests.push({
    url: request.url(),
    // failure() 可能为 null;即便非 null,errorText 也不保证存在
    errorText: request.failure()?.errorText ?? null,
  });
});

requestfailed 事件回调里还能组合使用 HTTPRequest 的其他方法做精细诊断,比如 frame()(发出请求的 frame)、resourceType()(资源类型,如 stylesheetimagefetch)、response()(失败请求的 response 恒为 null)。例如按资源类型归类失败:

page.on('requestfailed', request => {
  console.log({
    url: request.url(),
    resourceType: request.resourceType(),
    errorText: request.failure()?.errorText ?? '(no error text)',
  });
});

五、errorText 的实际取值与协议差异

errorText 的内容来自浏览器协议层的原始文本,因此在不同浏览器后端上会呈现不同的字符串风格。仓库测试 test/src/network.test.ts 中的 Page.Events.RequestFailed 用例直接验证了这一行为:

  • 测试先开启请求拦截 page.setRequestInterception(true),对 URL 以 css 结尾的请求调用 request.abort(),其余请求 request.continue()
  • 随后断言恰好收到 1 个 requestfailed
  • 对失败请求断言:response()nullframe() 有值;
  • 失败文本按浏览器区分断言:Chrome 期望 net::ERR_FAILED,Firefox 期望 NS_ERROR_ABORT
if (isChrome) {
  expect(failedRequest.failure()!.errorText).toBe('net::ERR_FAILED');
} else {
  expect(failedRequest.failure()!.errorText).toBe('NS_ERROR_ABORT');
}

这说明两个要点:

  1. errorText 不是 Puppeteer 生成的统一错误码,而是直接透传浏览器的网络错误文本——Chrome 使用 net:: 前缀(Chromium 网络栈错误),Firefox 使用 NS_ERROR_* 风格(Gecko 的 nsresult 错误)。
  2. 通过请求拦截主动 abort() 一个请求,同样会走 requestfailed 路径并产生可读取的失败文本,因此 failure() 也是判断"资源是否被拦截/中止"的可靠手段。

test/src/navigation.test.ts 的导航测试中同样能看到:请求事件按 requestrequestfailed 的顺序触发,失败请求会替代性地出现在失败事件序列里,进一步佐证了"失败请求不会再触发 requestfinished"的生命周期约定。

常见失败场景归类

综合文档与实现,可用 failure() 判别的失败一般包括:

  • 连接类失败:DNS 解析失败、TCP 连接被拒绝/超时、TLS 握手失败,如 net::ERR_NAME_NOT_RESOLVEDnet::ERR_CONNECTION_REFUSED
  • 协议层中断:响应中途连接断开、下载被中止,如 net::ERR_FAILEDnet::ERR_ABORTED
  • 人为中止:通过请求拦截调用 request.abort()(配合 setRequestInterception 使用)触发的失败;
  • 资源被浏览器策略阻止等底层原因。

不属于失败的有:任何得到 HTTP 状态码的响应(含 404/503)、被 continue() 放行并成功完成的请求、以及重定向链上的中间请求。


六、与 requestfinished 配套:监控完整资源生命周期

failure() 放进完整的生命周期中才能写出有实用价值的监控代码。官方文档明确过:同一请求要么 requestfinished、要么 requestfailed。可以这样统计页面资源成功率:

const stats = {total: 0, failed: 0, byType: new Map<string, number>()};

page.on('requestfinished', request => {
  stats.total++;
  const t = request.resourceType();
  stats.byType.set(t, (stats.byType.get(t) ?? 0) + 1);
});

page.on('requestfailed', request => {
  stats.total++;
  stats.failed++;
  console.error(`[资源加载失败] ${request.url()}`);
  console.error(`  错误: ${request.failure()?.errorText ?? '(无错误文本)'}`);
});

await page.goto('https://example.com/', {waitUntil: 'networkidle0'});

console.log(`资源总数=${stats.total}, 失败=${stats.failed}`);

几点工程建议:

  • 不要在 requestfinished/requestfailed 之外盲调 failure():对于仍在途或已成功的请求它是 null,用途有限;把它与对应事件绑定才是正确姿势。
  • errorText 做归一化:如果监控系统需要跨 Chrome/Firefox 比对,建议把 net::ERR_*NS_ERROR_* 映射成内部统一错误类别,而不是直接存储原始串。
  • 失败请求没有响应体failure()null 时,request.response() 返回 null(测试 test/src/network.test.ts 已断言),所以不要在失败分支尝试读取 body。
  • 与拦截配合做降级:当检测到第三方资源(埋点、广告 SDK、字体)失败且可容忍时,可以用 request interception 决定重试或放行;测试 test/src/requestinterception-experimental.test.ts 也验证了正常完成的请求其 failure()null

总结

  • failure() 返回 null{ errorText: string },仅用于查询"请求是否失败及失败原因",通常在 pagerequestfailed 事件回调内调用;
  • errorText 透传浏览器底层错误文本(Chrome 为 net::ERR_*,Firefox 为 NS_ERROR_*),且失败也不保证存在文本,代码必须做空值兜底;
  • HTTP 4xx/5xx、重定向都不算请求失败,不会触发 requestfailed
  • CDP 通道的数据链路是 Network.loadingFailedNetworkManager.#emitLoadingFailed 写入 _failureText → 派发事件,见 packages/puppeteer-core/src/cdp/NetworkManager.ts;BiDi 通道则直接读取协议请求的 error 字段,见 packages/puppeteer-core/src/bidi/HTTPRequest.ts
  • 相关行为均有测试佐证,可对照 test/src/network.test.ts 深入验证。
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391