ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Puppeteer 响应体获取全解:HTTPResponse.content() 的返回值、编码边界与 CDP/BiDi 底层实现

Puppeteer 响应体获取全解:HTTPResponse.content() 的返回值、编码边界与 CDP/BiDi 底层实现 Puppeteer 响应体获取全解HTTPResponse.content() 的返回值、编码边界与 CDP/BiDi 底层实现【免费下载链接】puppeteerJavaScript API for Chrome and Firefox项目地址: https://gitcode.com/GitHub_Trending/puppeteer1/puppeteerHTTPResponse.content()是 Puppeteer本仓库基于 Chromium 与 Firefox 的官方 JavaScript API中用于获取 HTTP 响应原始字节的核心方法。无论你是在做接口抓取、PDF/图片等二进制资源下载、还是用page.waitForResponse()做条件式响应断言都会直接或间接经过它——HTTPResponse上的buffer()、text()、json()三个便捷方法全部建立在content()之上。阅读本文后你将掌握它的签名与返回类型、与三个派生方法的取舍关系、浏览器对响应体的再编码陷阱以及它在 CDP 与 WebDriver BiDi 两种协议下的底层实现链路。HTTPResponse.content() 方法签名依据本仓库自动生成的 API 文档 docs/api/puppeteer.httpresponse.content.md该方法声明于抽象基类HTTPResponseclass HTTPResponse { abstract content(): PromiseUint8Array; }返回类型PromiseUint8Array。它解析为一个包含响应体response body字节的缓冲区而不是已经解码好的字符串。抽象方法content()在协议无关的 API 层只定义契约真正的实现由 CDP 与 WebDriver BiDi 两个协议后端分别提供见下文底层实现部分。在源码 packages/puppeteer-core/src/api/HTTPResponse.ts 中HTTPResponse被注释为The HTTPResponse class represents responses which are received by the {link Page} class即表示一个被页面Page接收到的网络响应。构造函数标有internal用户通常不会直接实例化它而是通过 API 回调获得对象实例。如何拿到一个 HTTPResponse 并读取响应体content()的典型调用链是先通过下述任一入口获得HTTPResponse对象page.goto()/page.reload()/frame.goto()的返回值——这些方法在导航成功后返回主文档对应的HTTPResponsepage.waitForResponse()——等待并捕获满足条件的响应例如某个接口或静态资源详见 docs/api/puppeteer.page.waitforresponse.mdpage.on(response, handler)——监听页面加载期间触发的每一个response事件。一个可直接运行的最小示例Node.js 本仓库puppeteer包import puppeteer from puppeteer; const browser await puppeteer.launch(); const page await browser.newPage(); // 情形一直接使用导航返回的主响应 const response await page.goto(https://example.com); const rawBody await response.content(); // Uint8Array console.log(rawBody.byteLength, rawBody instanceof Uint8Array); // 情形二捕获页面中某个异步加载的接口 const apiResponse await page.waitForResponse(res res.url().includes(/api/data) ); console.log(await apiResponse.text()); await browser.close();注意仓库代码风格为 ESMimport ... from puppeteer。由于content()返回的是原始字节若直接打印需要先做解码见下一节。返回类型为什么是 Uint8Arraybuffer() / text() / json() 的关系content()返回Uint8Array而非 Node.js 专属的Buffer这是为了兼容浏览器等非 Node 运行环境。在 HTTPResponse.ts 中可以看到另外三个便捷方法全部建立在content()之上async buffer(): PromiseBuffer { const content await this.content(); return Buffer.from(content); } async text(): Promisestring { const content await this.content(); return new TextDecoder(utf-8, {fatal: true}).decode(content); } async json(): Promiseany { const content await this.text(); return JSON.parse(content); }实际使用时应按需选择四者的差异如下方法返回适用场景关键限制content()PromiseUint8Array拿到未经解码的原始字节二进制最安全需要自行处理编码buffer()PromiseBufferNode 环境下需要BufferAPI如写入文件流依赖 Node 运行时的Buffertext()Promisestring明确是 UTF-8 文本的响应以TextDecoder(utf-8, {fatal: true})严格解码非合法 UTF-8 会直接抛错json()Promiseany接口返回 JSON先走text()再用JSON.parse响应体不是合法 JSON 会抛错其中text()的严格 UTF-8行为可以在单元测试 packages/puppeteer-core/src/api/HTTPResponse.test.ts 中看到直接证据测试构造了内容为new TextEncoder().encode(hello)的响应并断言text()返回hello又构造了内容为new Uint8Array([0xff])的非法 UTF-8 字节并断言text()抛出异常fatal: true的含义即遇到非法字节立即报错。因此处理图片、字体、压缩包等二进制资源时请优先使用content()或buffer()避免经过text()的 UTF-8 强校验与潜在乱码转换处理 HTML/JSON 文本时再使用text()/json()。编码边界浏览器可能再编码响应体API 文档 puppeteer.httpresponse.content.md 的 Remarks 部分明确提醒了一个容易踩坑的事实The buffer might be re-encoded by the browser based on HTTP-headers or other heuristics. If the browser failed to detect the correct encoding, the buffer might be encoded incorrectly.即你拿到的字节可能已被浏览器依据 HTTP 头如Content-Type的 charset或其它启发式规则再编码过。当浏览器未能正确探测到响应字符集时返回的缓冲区可能被错误编码从而让内容产生乱码或字节错位。这一问题的历史讨论记录在上游 issue 6478 中仓库文档仅引用其编号未收录其内容。这个边界意味着content()的字节不保证与网络上传输的原始字节逐字节一致它反映的是浏览器解码/重编码管线之后的形态若你对字节保真度有强诉求如校验完整性、签名比对应知晓此限制并尽量依赖服务端明确返回的Content-Type字符集同时content()本身不做任何解码承诺——这正是它既能安全承载二进制、又可能在文本场景产生编码困惑的原因文本请务必用text()。底层实现一CDP 后端的 Network.getResponseBody 调用链以 ChromiumCDP 协议运行时为例content()的实现在 packages/puppeteer-core/src/cdp/HTTPResponse.ts 中其流程可以归纳为四个关键点延迟与去重每个CdpHTTPResponse实例内部维护一个#contentPromise。首次调用content()时才真正发起请求并把 Promise 缓存起来后续重复调用直接返回同一个 Promise避免重复抓取响应体。等待 body 加载完成通过#bodyLoadedDeferred一个Deferred对象先等待浏览器端body加载事件完成由_resolveBody()触发再向浏览器索取内容。协议命令真正抓取字节调用的是 CDP 的Network.getResponseBodyconst response await this.#request.client.send(Network.getResponseBody, { requestId: this.#request.id, }); return stringToTypedArray(response.body, response.base64Encoded);源码注释指出这里刻意使用发起该请求的请求对象所对应的 CDPSessionthis.#request.client去抓取 body因为该 session 可能已被更新例如响应属于被接管的跨进程 iframe——adopted OOPIF 场景。错误重映射若 CDP 返回ProtocolError且原始信息为No resource with given identifier found则会被转换成更友好的错误——Could not load response body for this request. This might happen if the request is a preflight request.典型场景CORS 预检请求没有可读取的响应体。stringToTypedArray定义在 packages/puppeteer-core/src/util/encoding.ts当协议标记base64Encoded时优先走Uint8Array.fromBase64其次回退到 NodeBuffer.from(str, base64)最后回退到atob否则使用TextEncoder().encode(str)把协议返回的字符串编码为字节。底层实现二WebDriver BiDi 后端的 network.getData 调用链当通过 WebDriver BiDi 连接 Firefox或支持 BiDi 的 Chromium时content()由 packages/puppeteer-core/src/bidi/HTTPResponse.ts 实现它只是转发到请求对象async content(): PromiseUint8Array { return await this.#request.getResponseContent(); }真正与浏览器通信的逻辑位于 packages/puppeteer-core/src/bidi/core/Request.tsconst data await this.#session.send(network.getData, { dataType: Bidi.Network.DataType.Response, request: this.id, }); return stringToTypedArray( data.result.bytes.value, data.result.bytes.type base64, );同样基于stringToTypedArray完成 base64/字符串到字节的转换并沿用与 CDP 后端一致的No resource with given identifier found→ preflight request 错误提示逻辑。可以推断Puppeteer 之所以能同时服务 Chromium 与 Firefox项目描述即 JavaScript API for Chrome and Firefox正是因为content()这类核心方法被抽象在协议无关层再分别由 CDP 与 BiDi 两个后端填充协议细节。测试中的典型用法把 content() 当抓手在仓库的端到端测试中可以观察到content()派生方法最常见的落地形态——通过response.text()或response.json()断言服务端返回的内容。例如 test/src/network.test.ts 使用(await response.text()).trimEnd()对响应文本做归一化断言test/src/requestinterception.test.ts 在请求拦截场景下对多个响应批量调用response.text()而 test/src/navigation.test.ts 中expect(await response.text()).toBe(serverResponseTexts[i])直接校验导航后响应体的完整内容。这些用例印证了一个工程事实page.waitForResponse()content()/text()/json()的组合是 Puppeteer 生态中等待并读取响应的标准模式。使用建议与注意事项小结综合 API 文档、源码与测试在使用HTTPResponse.content()时建议记住以下四点按内容形态选方法二进制用content()/buffer()UTF-8 文本用text()JSON 用json()避免让非 UTF-8 字节经过text()的严格解码而抛错。警惕再编码语义content()返回的是浏览器解码/重编码管线之后的字节见 puppeteer.httpresponse.content.md Remarks对字节保真度有强要求的场景需自行评估这一限制。调用成本与内存同一响应对content()的多次调用在 CDP 后端会被#contentPromise缓存、只抓取一次但响应体始终整体载入内存超大响应体场景需评估内存占用且并非所有请求都能取到 body如 CORS 预检请求会得到专门的错误提示。协议差异是透明的无论运行在 CDP 还是 WebDriver BiDi 之上调用方看到的都是同一个PromiseUint8Array契约上层业务代码无需关心底层是Network.getResponseBody还是network.getData。如需进一步阅读可顺藤摸瓜查看响应对象其余能力如headers()、status()、fromCache()等均声明于 packages/puppeteer-core/src/api/HTTPResponse.ts以及请求方向的对应文档 docs/api/puppeteer.httprequest.response.md 与总体 API 索引 docs/api/index.md。【免费下载链接】puppeteerJavaScript API for Chrome and Firefox项目地址: https://gitcode.com/GitHub_Trending/puppeteer1/puppeteer创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表