首页
/ Puppeteer HTTPRequest.abort() 全解析:请求拦截中止的用法、ErrorCode 与优先级机制

Puppeteer HTTPRequest.abort() 全解析:请求拦截中止的用法、ErrorCode 与优先级机制

2026-09-06 19:01:11作者:裴锟轩Denise

导读

HTTPRequest.abort() 是 Puppeteer 请求拦截(Request Interception)体系中最常用的三个出口之一,用于在浏览器发出真实请求前主动中止它——典型场景包括拦截图片、广告、埋点与第三方脚本,从而节省带宽并加速页面加载。本文以 docs/api/puppeteer.httprequest.abort.md 文档为核心,结合仓库源码完整讲解其签名、参数、ErrorCode 取值表、协作式优先级(cooperative handling)语义,以及 Chrome(CDP)与 Firefox(WebDriver BiDi)两套底层的真实实现差异。读完你可以直接在自己的 Puppeteer 脚本中写出正确的资源过滤逻辑,并理解多拦截器并发时 abort 的裁决规则。

一、方法签名:一句话中止一个请求

按 API 文档的定义,方法签名为:

class HTTPRequest {
  abort(errorCode?: ErrorCode, priority?: number): Promise<void>;
}
  • errorCode:可选,中止请求时向浏览器提供的错误码,缺省值为 'failed'
  • priority:可选,一旦传入,拦截将按照"协作式处理规则"(cooperative handling rules)裁决;不传则立即完成中止;
  • 返回值:Promise<void>

方法的入口实现在 packages/puppeteer-core/src/api/HTTPRequest.ts

async abort(
  errorCode: ErrorCode = 'failed',
  priority?: number,
): Promise<void> {
  this.verifyInterception();
  if (!this.canBeIntercepted()) {
    return;
  }
  const errorReason = errorReasons[errorCode];
  assert(errorReason, 'Unknown error code: ' + errorCode);
  if (priority === undefined) {
    return await this._abort(errorReason);
  }
  this.interception.abortReason = errorReason;
  // …按 priority 更新 resolutionState,见后文"优先级"一节
}

源码揭示了文档之外的三条重要细节:

  1. 默认错误码是 'failed',对应浏览器侧 net::ERR_FAILED
  2. 传入不存在的错误码会直接抛 Unknown error code: <code>
  3. 真正发往浏览器的中止命令由内部抽象方法 _abort() 完成,abort() 只是它的公共门面。

二、前置条件:必须开启请求拦截

To use this, request interception should be enabled with Page.setRequestInterception(). If it is not enabled, this method will throw an exception immediately.

abort() 并非任何时刻都可用。调用前必须先用 page.setRequestInterception(true) 开启拦截,否则会立即抛异常。相关说明见 Page.setRequestInterception()

这个"立即抛异常"的动作来自 verifyInterception()

protected verifyInterception(): void {
  assert(this.interception.enabled, 'Request Interception is not enabled!');
  assert(!this.interception.handled, 'Request is already handled!');
}

它同时做两件校验:

  • 拦截未开启(interception.enabled === false)→ 抛 Request Interception is not enabled!
  • 请求已被处理过(interception.handled === true,即已经 abort() / respond() / continue() 过)→ 抛 Request is already handled!

也就是说,同一个请求实例的三种终态操作只能成功落定一次。此外源码中还有一层 canBeIntercepted() 守卫(见 HTTPRequest.ts),当请求本身不可拦截时(例如数据来自 Service Worker 等场景),abort() 会静默返回而不做任何事,不会抛错。

最小可用示例

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setRequestInterception(true); // 必须先开启

page.on('request', request => {
  // 拦截所有图片并中止(不发真实请求)
  if (request.resourceType() === 'image') {
    request.abort(); // 相当于 abort('failed')
  } else {
    request.continue();
  }
});

await page.goto('https://example.com');
await browser.close();

这个写法与仓库自带示例 examples/block-images.js 的拦截逻辑完全一致:对 image 类型调用 abort(),其余请求调用 continue()

三、errorCode:可选的浏览器错误码

errorCode 的类型 ErrorCode 定义在 docs/api/puppeteer.errorcode.md,由 14 个字符串字面量组成。它并不会被原样发给浏览器,而是先经过内部映射表 errorReasons 转换为协议层的 ErrorReason 再下发。映射关系见 HTTPRequest.ts

ErrorCode(Puppeteer 层) 对应 Protocol.Network.ErrorReason(协议层) 语义说明(对应 Chromium net::ERR_* 一族)
aborted Aborted 请求被主动中止
accessdenied AccessDenied 访问被拒绝
addressunreachable AddressUnreachable 目标地址不可达
blockedbyclient BlockedByClient 被客户端策略屏蔽(常由 Page.setBlockedByClient 产生)
blockedbyresponse BlockedByResponse 被响应侧策略屏蔽(如 CORB / CORS 阻断)
connectionaborted ConnectionAborted 连接被中止
connectionclosed ConnectionClosed 连接已关闭
connectionfailed ConnectionFailed 连接失败
connectionrefused ConnectionRefused 连接被拒绝
connectionreset ConnectionReset 连接被重置
internetdisconnected InternetDisconnected 网络已断开
namenotresolved NameNotResolved DNS 无法解析主机名
timedout TimedOut 请求超时
failed Failed 通用失败(默认值

指定错误码的写法

page.on('request', request => {
  // 模拟 DNS 解析失败
  request.abort('namenotresolved');
});

// 若传入非法值,例如 request.abort('not-a-real-code'),
// 源码中的 assert(errorReason, 'Unknown error code: …') 会直接抛出异常。

实际开发中,屏蔽广告、图片、埋点类请求通常无需关心具体错误码,直接使用默认的 'failed' 即可;指定更精确的错误码有助于在浏览器 DevTools 的 Network 面板中呈现更贴近真实故障形态的中止原因。

四、priority 与协作式拦截(cooperative interception)

If provided, intercept is resolved using cooperative handling rules. Otherwise, intercept is resolved immediately.

这是文档中描述最简、但语义最关键的一处:priority 的有无,决定了 abort() 走完全不同的两条执行路径。

不传 priority:立即中止

回看实现,priority === undefined 时:

if (priority === undefined) {
  return await this._abort(errorReason);
}

此时请求立即被发往底层完成中止,属于"立即裁决"(resolve immediately)。

传入 priority:协作式裁决

传入 priority 时,abort() 不会立刻中止请求,而是先记录中止原因,再把"要 abort"这一意图连同优先级写入 interception.resolutionState

this.interception.abortReason = errorReason;
if (
  this.interception.resolutionState.priority === undefined ||
  priority >= this.interception.resolutionState.priority
) {
  this.interception.resolutionState = {
    action: InterceptResolutionAction.Abort,
    priority,
  };
  return;
}

最终由内部方法 finalizeInterceptions() 统一裁决——它会先依次执行队列中待定的拦截处理器,再根据最终 resolutionState 决定是 abortrespond 还是 continue

const {action} = this.interceptResolutionState();
switch (action) {
  case 'abort':
    return await this._abort(this.interception.abortReason);
  case 'respond':
    // …
  case 'continue':
    // …
}

这意味着多个监听器或拦截处理器可以各自声明自己的意图与优先级,由框架按规则收敛为唯一动作。裁决规则中值得注意的几个细节:

  • 状态为 DisabledAlreadyHandledNone 时,finalizeInterceptions() 不会执行任何终态动作(开关中没有对应 case),拦截自然放行;
  • abort() 中,新意图在 priority >= 已有优先级 时即覆盖旧意图;
  • 对比同文件中 continue()respond(),它们只在 priority > 已有优先级 时覆盖——优先级相同时 abort 拥有对 continue / respond 的优先权(respond 在平票时也能压过 continue,但压不过 abort)。

可见协作式拦截适合在插件化、多拦截器共存的架构中保证决策的确定性:无论谁先谁后,最终都按"最高优先级 + abort 优先"的确定性规则收敛。

InterceptResolutionAction 枚举的完整取值(abort / respond / continue / disabled / none / already-handled)定义于 HTTPRequest.tsHTTPRequest 类还提供 interceptResolutionState()isInterceptResolutionHandled()abortErrorReason() 等只读方法用于观察当前裁决状态,详见 HTTPRequest 类文档

五、底层实现:CDP 与 WebDriver BiDi 两条路径

abort() 本身是协议无关的公共 API,真正的网络指令由 _abort() 在两种浏览器协议实现中各自完成。

Chrome / Chromium(CDP):Fetch.failRequest

packages/puppeteer-core/src/cdp/HTTPRequest.ts 中:

async _abort(
  errorReason: Protocol.Network.ErrorReason | null,
): Promise<void> {
  this.interception.handled = true;
  if (this._interceptionId === undefined) {
    throw new Error(
      'HTTPRequest is missing _interceptionId needed for Fetch.failRequest',
    );
  }
  await this.#client
    .send('Fetch.failRequest', {
      requestId: this._interceptionId,
      errorReason: errorReason || 'Failed',
    })
    .catch(error => {
      return handleError(error, this.#logger);
    });
}

要点:

  • CDP 中止依赖 Fetch.failRequest 命令,因此请求必须持有有效的 _interceptionId(由 Fetch.requestPaused 事件携带);
  • errorReasonnull 时回退为 'Failed'
  • 发送前即把 interception.handled 置为 true,标记该请求已被裁决;
  • 发送失败时统一交给 handleError() 处理(含 Firefox 对无效头参数会抛 invalid argument 等已知兼容性分支,见 handleError 附近)。

Firefox(WebDriver BiDi):network.failRequest

packages/puppeteer-core/src/bidi/HTTPRequest.ts 中,实现要简单得多:

override async _abort(): Promise<void> {
  this.interception.handled = true;
  return await this.#request.failRequest().catch(error => {
    this.interception.handled = false;
    throw error;
  });
}

从源码签名可以看出,BiDi 的 _abort() 不接受也不传递 errorReason——当前 WebDriver BiDi 实现的中止不区分具体错误码,统一调用 failRequest()。因此当你通过 Firefox(BiDi 协议)运行 Puppeteer 时,传入的 errorCode 更多是 API 层面的兼容占位,实际中止语义与 CDP 并不完全等价。这也印证了文档只称其为 "optional error code to provide",未对不同协议做逐码承诺。

六、实战要点与常见误用

围绕 abort(),实际项目中通常关注以下几个问题:

  1. 先开拦截再监听setRequestInterception(true) 之后,page.on('request', …) 中的回调才会收到可拦截的请求;未开启拦截时调用 abort() 会立刻抛 Request Interception is not enabled!

  2. 资源过滤要做到分支完整。拦截模式下每个请求最终都必须有一个终态:abort()continue()respond()。只拦不放会导致请求悬挂。典型写法是 if (…条件…) request.abort(); else request.continue();(见上节示例)。

  3. 一次请求只能落定一次。直接调用 abort() 会在内部立即标记 handled,同一 HTTPRequest 对象上再次调用 abort() / continue() / respond() 会抛 Request is already handled!

  4. 拦截时机越早越省资源request 事件触发于真实网络请求发出之前,因此 abort() 能真正"掐断"图片、广告、第三方统计等流量,达到加速渲染与节省带宽的目的。

  5. 多拦截器场景使用 priority。多个监听器各自对同一请求声明意图时,请给每个意图传入 priority,让 finalizeInterceptions() 按"最高优先级、同分 abort 优先"的确定性规则收敛,避免因回调顺序产生不确定结果。

综合示例:拦截追踪器与第三方域名

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setRequestInterception(true);

const blockedHosts = new Set([
  'analytics.example.com',
  'ads.example.com',
  'cdn.tracker.net',
]);

page.on('request', request => {
  const host = new URL(request.url()).hostname;
  if (blockedHosts.has(host)) {
    // 让浏览器以"被客户端屏蔽"的形式终止该请求
    request.abort('blockedbyclient', 10);
  } else {
    request.continue({}, 10);
  }
});

await page.goto('https://news.example.com', {waitUntil: 'networkidle2'});
await browser.close();

七、关联 API 速查

API 作用 文档位置
HTTPRequest 请求拦截的完整对象模型 docs/api/puppeteer.httprequest.md
HTTPRequest.abort() 中止请求(本文主题) docs/api/puppeteer.httprequest.abort.md
ErrorCode 14 种可用错误码字面量 docs/api/puppeteer.errorcode.md
Page.setRequestInterception() 开启 / 关闭请求拦截(abort 的前置条件) docs/api/puppeteer.page.setrequestinterception.md
HTTPRequest.continue() / respond() 拦截的三态出口:放行 / 伪造响应 docs/api/puppeteer.httprequest.md 内方法页

三者(abort / continue / respond)共同构成 Puppeteer 请求拦截的完整闭环:abort() 决定"不发",respond() 决定"替换",continue() 决定"放行"。理解 abort() 的默认错误码、前置校验、协作式优先级以及 CDP / BiDi 两种底层差异,即可在性能优化与反爬对抗等场景中安全、准确地使用这一能力。

登录后查看全文
热门项目推荐
相关项目推荐