在 Chrome 中加载页面时,如何捕获所有网络请求和完整响应数据?

IT技术 javascript google-chrome puppeteer
2021-02-20 05:03:00

使用 Puppeteer,我想在 Chrome 中加载一个 URL 并捕获以下信息:

  • 请求网址
  • 请求头
  • 请求发布数据
  • 响应标头文本(包括重复的标头,如set-cookie
  • 传输的响应大小(即压缩大小)
  • 完整的响应体

捕获完整的响应主体是导致我出现问题的原因。

我尝试过的事情:

  • 获取响应内容response.buffer- 如果在任何时候有重定向,这将不起作用,因为缓冲区在导航时被擦除
  • 拦截请求并使用getResponseBodyForInterception- 这意味着我无法再访问 encodingLength,并且在某些情况下我也无法获取正确的请求和响应标头
  • 使用本地代理有效,但这会显着减慢页面加载时间(并且还更改了某些行为,例如证书错误)

理想情况下,该解决方案应该只对性能产生较小的影响,并且与正常加载页面没有功能差异。我也想避免分叉 Chrome。

6个回答

您可以page.setRequestInterception()为每个请求启用一个请求拦截,然后在内部page.on('request'),您可以使用该request-promise-nativemodule作为中间人收集响应数据,然后request.continue()在 Puppeteer 中继续请求

这是一个完整的工作示例:

'use strict';

const puppeteer = require('puppeteer');
const request_client = require('request-promise-native');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  const result = [];

  await page.setRequestInterception(true);

  page.on('request', request => {
    request_client({
      uri: request.url(),
      resolveWithFullResponse: true,
    }).then(response => {
      const request_url = request.url();
      const request_headers = request.headers();
      const request_post_data = request.postData();
      const response_headers = response.headers;
      const response_size = response_headers['content-length'];
      const response_body = response.body;

      result.push({
        request_url,
        request_headers,
        request_post_data,
        response_headers,
        response_size,
        response_body,
      });

      console.log(result);
      request.continue();
    }).catch(error => {
      console.error(error);
      request.abort();
    });
  });

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

  await browser.close();
})();
我认为request.continue会提出一个新的请求而不是使用相同的数据,但request.respond应该有效。
2021-04-26 05:03:00
我试图操纵请求 URL,但它不允许这样做,而且我在 chrome 的跟踪中看不到不同的 URL。关于如何做到这一点的任何想法?
2021-04-27 05:03:00
谢谢!这种方法会破坏一些站点,因为在请求拦截时,一些标头尚未包含在内(例如 Accept 和 Cookie)。github.com/GoogleChrome/puppeteer/issues/3436我希望传出请求具有与没有请求拦截相同的标头。
2021-04-28 05:03:00
request-promise-native 目前似乎已被弃用。
2021-05-06 05:03:00
期待你写一个答案,否则我会写同样的答案。:D
2021-05-09 05:03:00

仅限 Puppeteer 的解决方案

这可以单独使用 puppeteer 来完成。您描述的问题response.buffer是在导航时被清除,可以通过一个接一个地处理每个请求来规避。

怎么运行的

下面的代码page.setRequestInterception用于拦截所有请求。如果当前有正在处理/正在等待的请求,则将新请求放入队列中。然后,response.buffer()可以在没有其他请求可能异步擦除缓冲区的问题的情况下使用,因为没有并行请求。一旦处理了当前处理的请求/响应,就会处理下一个请求。

代码

const puppeteer = require('puppeteer');

(async () => {
    const browser = await puppeteer.launch();
    const [page] = await browser.pages();

    const results = []; // collects all results

    let paused = false;
    let pausedRequests = [];

    const nextRequest = () => { // continue the next request or "unpause"
        if (pausedRequests.length === 0) {
            paused = false;
        } else {
            // continue first request in "queue"
            (pausedRequests.shift())(); // calls the request.continue function
        }
    };

    await page.setRequestInterception(true);
    page.on('request', request => {
        if (paused) {
            pausedRequests.push(() => request.continue());
        } else {
            paused = true; // pause, as we are processing a request now
            request.continue();
        }
    });

    page.on('requestfinished', async (request) => {
        const response = await request.response();

        const responseHeaders = response.headers();
        let responseBody;
        if (request.redirectChain().length === 0) {
            // body can only be access for non-redirect responses
            responseBody = await response.buffer();
        }

        const information = {
            url: request.url(),
            requestHeaders: request.headers(),
            requestPostData: request.postData(),
            responseHeaders: responseHeaders,
            responseSize: responseHeaders['content-length'],
            responseBody,
        };
        results.push(information);

        nextRequest(); // continue with next request
    });
    page.on('requestfailed', (request) => {
        // handle failed request
        nextRequest();
    });

    await page.goto('...', { waitUntil: 'networkidle0' });
    console.log(results);

    await browser.close();
})();
@onassar 是的,如果您不需要缓冲区,您可以简化它。
2021-04-22 05:03:00
啊好吧。所以如果我只关心响应头,我可以简化方法是吗?就我而言,我打电话setRequestInterceptiontrue,然后调用continue下面的事件的请求对象:requestrequestfailedrequestfinished例外是我将标题存储在requestfinished事件调用中。有道理?
2021-04-25 05:03:00
为什么需要暂停请求?为什么不能简单地让请求继续,并使用该requestfinished事件来检查 URL 和响应标头并存储它们?就我而言,我想要的只是与特定请求 URL 关联的标头。
2021-04-27 05:03:00
@onassar 您的用例与 OP 不同。问题是如何捕获“完整响应数据”而不仅仅是标题。
2021-05-01 05:03:00
有什么[page]用?我没有看到它在您的代码中使用。
2021-05-05 05:03:00

我建议您搜索一个允许将请求日志与实际内容一起写入的快速代理服务器。

目标设置是让代理服务器只写一个日志文件,然后分析日志,搜索你需要的信息。

不要在代理工作时拦截请求(这会导致速度变慢)

您可能遇到的性能问题(使用代理作为记录器设置)主要与 TLS 支持有关,请注意在代理设置中允许快速 TLS 握手、HTTP2 协议

例如Squid 基准测试表明它能够处理数百个 RPS,这对于测试目的来说应该足够了

@MattZeunert,谢谢,如果您需要任何帮助,请告诉我
2021-04-20 05:03:00
谢谢!由于我遇到的性能问题,我不太热衷于使用代理,但我会再次研究它。
2021-04-23 05:03:00

我建议使用一种工具,即“提琴手”。它将捕获您在加载 URL url 时提到的所有信息。

转到Chrome按F12,然后转到“网络”选项卡,您可以在那里看到该网站发送的所有http请求,您可以看到您提到的详细信息。

有一个复选框可以保留日志,因此您可以重新加载页面并且不会丢失请求日志
2021-04-30 05:03:00
那是使用response.bufferwhich 在导航上被擦除。
2021-05-07 05:03:00
它不起作用,它只在导航后显示“无法加载响应数据”消息。
2021-05-15 05:03:00