首页
/ Puppeteer 请求拦截与改写核心 API:HTTPRequest.continue() 深度实战指南

Puppeteer 请求拦截与改写核心 API:HTTPRequest.continue() 深度实战指南

2026-09-06 19:03:28作者:邓越浪Henry

本文以 docs/api/puppeteer.httprequest.continue.md 为骨架,系统讲解 Puppeteer(JavaScript API for Chrome and Firefox)中 HTTPRequest.continue() 的使用方式与底层原理。该方法与 Page.setRequestInterception() 配合,是拦截并改写页面发出的任意 HTTP 请求(Header、URL、Method、POST Body)的标准入口,是广告过滤、Mock 数据、请求降级与跨域改造类爬虫/自动化脚本的核心设施。读完本文,你将掌握请求拦截的完整调用链、ContinueRequestOverrides 的四个可改写字段、可选的协作式优先级语义(cooperative interception),以及 CDP 与 WebDriver BiDi 两种协议通道下的真实实现差异。

为什么需要 continue():一次请求、三种归宿

在现代浏览器自动化场景中,"监听请求"只是第一步,真正的难点是"在请求真正发出前改写它"。Puppeteer 为此设计了请求拦截(request interception)机制:当通过 Page.setRequestInterception() 开启拦截后,页面的每个网络请求都会被挂起(stall),直到开发者显式地给出处理结论。结论只有三种:

  • request.abort() —— 终止该请求(例如拦截图片、屏蔽追踪域名);
  • request.respond() —— 伪造响应,直接返回本地 Mock 数据,请求不会到达服务器;
  • request.continue() —— 放行请求,并且允许在放行前对请求本身做改写(这是本文主角)。

从源码注释看,三者是并列的"处理动作":InterceptResolutionAction 枚举同时收录了 'abort' | 'respond' | 'continue',另有 'disabled' | 'none' | 'already-handled' 三种状态值(见 packages/puppeteer-core/src/api/HTTPRequest.ts)。而拦截的"终结"发生在 finalizeInterceptions():它会先依次执行入队的处理器,再根据最终决议动作分别调用 _abort()_respond()_continue()(见 packages/puppeteer-core/src/api/HTTPRequest.ts)。continue() 正是触发 _continue() 这一底层放行通道的公共 API。

方法签名与核心语义

官方 API 文档给出的完整签名如下(docs/api/puppeteer.httprequest.continue.md):

class HTTPRequest {
  continue(
    overrides?: ContinueRequestOverrides,
    priority?: number,
  ): Promise<void>;
}

该方法属于 HTTPRequest 实例(由 page.on('request', req => ...) 事件回调提供),返回 Promise<void>。两个参数都可选。语义要点:

  1. 无参调用 request.continue() 即"原样放行",等价于不改写任何字段;
  2. 传入 overrides 则按需改写 URL、Method、PostData 或 Headers;
  3. 传入 priority 则走协作式拦截决议(见下文专节),否则立即执行放行。

packages/puppeteer-core/src/api/HTTPRequest.ts 中的真实实现证实了这一语义:continue() 首先调用 verifyInterception() 做前置校验,然后判断 canBeIntercepted(),通过后若未提供 priority,直接 await this._continue(overrides) 立即放行。

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

文档的 Remarks 部分明确强调了两点(docs/api/puppeteer.httprequest.continue.md):

To use this, request interception should be enabled with Page.setRequestInterception(). Exception is immediately thrown if the request interception is not enabled.

  • 必须先调用 page.setRequestInterception(true)
  • 若拦截未开启却调用 continue(),会立即抛出异常

异常的具体文案藏在源码的 verifyInterception() 中(packages/puppeteer-core/src/api/HTTPRequest.ts):

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

即未开启时抛 Request Interception is not enabled!,请求已被处理过(例如已被 abort/respond/continue 命中)再次调用时抛 Request is already handled!。后者是新手最常见的报错来源——一个请求只能被处理一次

哪些请求可以被 continue?

拦截并非对所有请求生效。canBeIntercepted() 的实现给出了边界(packages/puppeteer-core/src/cdp/HTTPRequest.ts):

protected canBeIntercepted(): boolean {
  return !this.url().startsWith('data:') && !this._fromMemoryCache;
}
  • data: 协议的请求(内联图片等 data URL)不可拦截;
  • 命中浏览器内存缓存(memory cache)的请求不可拦截。

canBeIntercepted() 返回 false 时,continue() 会静默直接返回而不报错。另外需注意:HTTP 层面"成功"的 404/503 等错误响应仍属于可被正常放行的请求,只有真正失败(如 net::ERR_FAILED)才会走 requestfailed 事件路径。

改写参数:ContinueRequestOverrides 逐字段拆解

overrides 的类型为 ContinueRequestOverrides,它只包含四个可选字段(定义见 packages/puppeteer-core/src/api/HTTPRequest.ts,属性表见 docs/api/puppeteer.continuerequestoverrides.md):

字段 类型 说明与默认值
headers Record<string, string>(可选) 覆盖请求头;值为 undefined 表示删除该请求头。不传则保持原请求头。
method string(可选) 改写请求方法(如 GETPOST)。不传则保持原方法。
postData string(可选) 改写 POST 请求体。仅对带 body 的方法有意义。不传则保持原请求体。
url string(可选) 改写目标 URL。注意:这只是改变请求的 URL,并非重定向(This is not a redirect),不会触发 3xx 跳转语义。不传则保持原 URL。

要点提示:

  • headers 中的 key 一律小写处理。官方文档示例中 request.headers() 返回的头部键已全部是小写,因此用 Object.assign({}, request.headers(), ...) 做增量修改是最安全的做法;
  • postData 字段为字符串。注意获取侧 API:request.postData() 已标记 @deprecated,官方推荐改用 request.fetchPostData() 获取完整 body(见 docs/api/puppeteer.httprequest.fetchpostdata.mddocs/api/puppeteer.httprequest.postdata.md)——当 body 过长或不易解码时 postData() 可能返回 undefined,此时应使用 fetchPostData()

实战一:改写与删除请求头(文档示例)

官方文档给出的典型用法是"读原头 → 改头 → continue"(docs/api/puppeteer.httprequest.continue.md):

await page.setRequestInterception(true);
page.on('request', request => {
  // Override headers
  const headers = Object.assign({}, request.headers(), {
    foo: 'bar', // set "foo" header
    origin: undefined, // remove "origin" header
  });
  request.continue({headers});
});

这段代码演示了 continue() 的两个独特能力:

  1. 新增请求头:把 foo 头置为 bar
  2. 删除请求头:把 origin 置为 undefined —— 注意这里并不是把值置成字符串 "undefined",而是语义化的"移除"。这一约定在类型 Record<string, string> 之外的取值空间中通过底层序列化逻辑实现。

实战二:改写 URL、方法与请求体

continue() 的四个字段可自由组合。例如把某次表单提交从 GET 改写为 POST 并携带新 body,或把资源请求指向本地镜像:

await page.setRequestInterception(true);
page.on('request', request => {
  if (request.url().includes('/api/v2/legacy')) {
    // 改写目标地址:不触发重定向,而是直接发出新 URL 的请求
    request.continue({url: 'https://example.com/api/v3/legacy'});
  } else if (request.url().endsWith('/login')) {
    request.continue({
      method: 'POST',
      postData: 'username=admin&from=interceptor',
      headers: {
        ...request.headers(),
        'content-type': 'application/x-www-form-urlencoded',
      },
    });
  } else {
    request.continue(); // 其余请求原样放行,这一分支必不可少
  }
});

⚠️ 若开启了拦截却没有对某请求调用 continue()/respond()/abort(),该请求会一直挂起直到超时。文档 Page.setRequestInterception() 明确写道:Once request interception is enabled, every request will stall unless it's continued, responded or aborted; or completed using the browser cache. 因此拦截处理器必须保证每个请求都有归宿。

底层实现:CDP 与 WebDriver BiDi 双通道

Puppeteer 当前同时支持 Chrome(CDP 协议)与 Firefox(WebDriver BiDi 协议),_continue() 的协议层实现因此存在两套。这一事实可直接从仓库源码确认。

CDP 通道:Fetch.continueRequest

CDP 实现位于 packages/puppeteer-core/src/cdp/HTTPRequest.ts

async _continue(overrides: ContinueRequestOverrides = {}): Promise<void> {
  const {url, method, postData, headers} = overrides;
  this.interception.handled = true;

  const postDataBinaryBase64 = postData
    ? stringToBase64(postData)
    : undefined;

  if (this._interceptionId === undefined) {
    throw new Error(
      'HTTPRequest is missing _interceptionId needed for Fetch.continueRequest',
    );
  }
  await this.#client
    .send('Fetch.continueRequest', {
      requestId: this._interceptionId,
      url,
      method,
      postData: postDataBinaryBase64,
      headers: headers ? headersArray(headers) : undefined,
    })
    .catch(error => {
      this.interception.handled = false;
      return handleError(error, this.#logger);
    });
}

值得注意的实现细节:

  1. 底层走的是 Chrome DevTools Protocol 的 Fetch.continueRequest 域调用,requestId 即该请求的拦截 ID(_interceptionId);
  2. postData 字符串先经 stringToBase64() 转成 base64 再传给协议层,因为 CDP 的 Fetch 域要求 base64 编码的 body;
  3. headersheadersArray() 序列化为协议要求的键值数组;值为 undefined 的头部正是在序列化层被剔除,从而实现"删头"语义;
  4. 错误回滚:协议调用失败时会把 interception.handled 复位为 false 并记录日志,意味着该请求仍可被后续处理器重新处理,而不是"卡死"在已处理状态;
  5. _interceptionId 缺失(如该请求本不可被拦截)则抛错说明。

WebDriver BiDi 通道:request.continueRequest

BiDi(Firefox)实现位于 packages/puppeteer-core/src/bidi/HTTPRequest.ts,同样是先标记 handled = true,再调用 this.#request.continueRequest(...),body 以结构化 {type, value} 形式(而非 CDP 的 base64 字符串)传递:

override async _continue(overrides: ContinueRequestOverrides = {}): Promise<void> {
  const headers: Bidi.Network.Header[] = getBidiHeaders(overrides.headers);
  this.interception.handled = true;
  return await this.#request.continueRequest({
    url: overrides.url,
    method: overrides.method,
    body: overrides.postData ? { /* ...结构化 body... */ } : undefined,
    headers,
  });
}

因此"上层 API 一致、下层协议自适应"是本方法的架构特点:无论浏览器走 CDP 还是 BiDi,开发者写的 request.continue({...}) 代码都无需变化。

协作式拦截:priority 参数与决议规则

continue() 的第二个参数 priority 是 Puppeteer 较新的高级特性。文档原话(docs/api/puppeteer.httprequest.continue.md):

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

立即决议模式(不传 priority)

不传 prioritycontinue() 直接执行 _continue() 放行请求。与此同时若页面事件流中另有一个处理器调用了 respond(),二者会产生竞态——后执行的调用很可能触发 Request is already handled! 异常。这正是引入协作模式的原因。

协作决议模式(传 priority)

prioritycontinue() 不会立即放行,而是先登记意图:把本次 overrides 存入 interception.requestOverrides,再与已登记的其它动作按优先级"协商"(实现见 packages/puppeteer-core/src/api/HTTPRequest.ts)。规则如下:

  1. 若此前没有更高优先级的动作,把决议动作置为 Continue 并记录本次 priority;
  2. 若本次 priority 高于已登记动作,则覆盖为 Continue
  3. 若 priority 相等:当已登记动作是 abortrespond 时,保持原动作(中止/伪响应优先于放行);否则置为 Continue
  4. abort()/respond() 同样支持 priority,最终谁胜出由数值大小决定。

该机制允许多个独立的拦截处理器(如"广告拦截扩展"与"业务 Mock 中间件")各自声明优先级,而不会互相踩踏。仓库为此提供了默认常量与配套方法:

协作式语义并非纸上谈兵,仓库自带的实验性测试 test/src/requestinterception-experimental.test.ts 中大量出现 request.continue({}, 0)request.abort('aborted', 1)request.respond({...}, 1) 混用的场景,通过数值优先级验证"高优先级 abort/respond 覆盖低优先级 continue"的决议结果,可作为理解该机制的活教材。

最佳实践与常见坑位清单

最后,把容易踩坑的点汇总如下:

  1. 先开闸再改写:忘记 await page.setRequestInterception(true) 就调用 continue() 会同步抛出 Request Interception is not enabled!,异常发生在 verifyInterception() 阶段(packages/puppeteer-core/src/api/HTTPRequest.ts);
  2. 每请求必有归宿:拦截开启后所有请求默认挂起,else 分支务必补 request.continue()
  3. 一请求一处理:同一请求多次调用任意处理 API 会抛 Request is already handled!;需要"多个处理器共同决定"时应走 priority 协作模式或 enqueueInterceptAction()
  4. URL 改写不是重定向overrides.url 只改变本次发出的请求地址,不产生 3xx 跳转链,也不会改变浏览器地址栏;
  5. 删除头部用 undefined:在 headers 对象中将某键设为 undefined 才能在序列化时移除该头,直接传空字符串可能会被当作合法值发送;
  6. 边界请求放行受限data: URL 与命中内存缓存的请求不可被拦截,continue() 对它们会静默跳过;
  7. 保持事件监听优先注册page.on('request', ...) 的处理器应在 page.goto() 等导航动作之前注册,避免页面首屏请求在监听器就绪前已经发出;需要保持单页长期监听时,通常选择在创建 page 后立即开启拦截。

进一步阅读

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

项目优选

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