Puppeteer - 错误:协议错误 (Network.getResponseBody):找不到具有给定标识符的资源

new*_*dev 14 javascript node.js puppeteer

我正在尝试使用此代码使用 puppeteer 从网站获取响应正文。

#!/usr/bin/env node

require('dotenv').config();
const puppeteer = require('puppeteer');
const readline = require('readline').createInterface({
    input: process.stdin,
    output: process.stdout
});
const path = require('path');
const fs = require('fs');

//
console.log('Starting Puppeteer...');

let responseBody = [];

(async () => {
    const browser = await puppeteer.launch({
        headless: false,
        executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
    });
    const page = await browser.newPage();
    
    await page.setRequestInterception(true);

    page.on('request', (request) => {
        request.continue();
    });

    //
    page.on('requestfinished', async (request) => {
        const response =  await request.response();
        const url = response.url();
        // store chunks url
        if( url.startsWith('https://audio-akp-quic-control-examplecdn-com.akamaized.net/audio/') ){
            console.log(await response.buffer());
            //responseBody.push(response.buffer());
        }
    });

    //
    await page.goto('https://accounts.examplecdn.com/login', {
        waitUntil: ['load', 'networkidle2']
    });

    const emailField = await page.waitForSelector('#login-username', {timeout: 3000});
    await emailField.type(process.env.EMAIL, {delay: 100});

    const passwordField = await page.waitForSelector('#login-password', {timeout: 3000});
    await passwordField.type(process.env.PASSWORD, {delay: 100});

    const submitButton = await page.waitForSelector('#login-button', {timeout: 3000});
    await submitButton.click();
    
    //
    const navigation = await page.waitForNavigation({ waitUntil: ['load', 'networkidle2'] });
    
    //if( navigation.url().endsWith('status') ){
    await page.goto('https://example.cdn.com/search', { 
        waitUntil: ['load', 'networkidle2'] 
    }).then( async (response) => {
        //console.log(response);
        const cookieButton = await page.$('#onetrust-accept-btn-handler');
        await cookieButton.click();
        const searchField = await page.$('[data-testid="search-input"]');
        await readline.question('What track do you want to search for?', (input) => {
            console.log('answer:', input);
            searchField.type(input).then( async () => {
                await page.waitForXPath('//*[@id="searchPage"]/div/div/section[1]/div[2]/div/div/div/div[4]').then( async (element) => {
                    element.focus().then( async () => {
                        // //*[@id="searchPage"]/div/div/section[1]/div[2]/div/div/div/div[3]/button
                        const playButton = await page.waitForXPath('//*[@id="searchPage"]/div/div/section[1]/div[2]/div/div/div/div[3]/button');
                        await playButton.click();
                    });
                });
            });
        });
    });
    
    
    //}

})();
Run Code Online (Sandbox Code Playgroud)

我遇到了问题,该错误将被记录并且脚本将终止。

/Users/dev/Desktop/test/node_modules/puppeteer/lib/cjs/puppeteer/common/Connection.js:208
            this._callbacks.set(id, { resolve, reject, error: new Error(), method });
                                                              ^

Error: Protocol error (Network.getResponseBody): No resource with given identifier found
    at /Users/dev/Desktop/test/node_modules/puppeteer/lib/cjs/puppeteer/common/Connection.js:208:63
    at new Promise (<anonymous>)
    at CDPSession.send (/Users/dev/Desktop/test/node_modules/puppeteer/lib/cjs/puppeteer/common/Connection.js:207:16)
    at /Users/dev/Desktop/test/node_modules/puppeteer/lib/cjs/puppeteer/common/HTTPResponse.js:99:53
    at runMicrotasks (<anonymous>)
    at processTicksAndRejections (node:internal/process/task_queues:93:5)
    at async /Users/dev/Desktop/test/index.js:40:25
Run Code Online (Sandbox Code Playgroud)

我需要在调用某个 url 时收集所有响应正文内容,然后使用 ffmpeg 我想将其转换回全长轨道。我该如何解决这个问题?是否可以获取每个请求的响应正文,然后将所有请求合并在一起?

ggo*_*len 1

“找不到具有给定标识符的资源”(以及在最近的 Puppeteer 版本中,包括 ^21.2.1,“协议错误:无法加载此请求的正文。如果请求是预检请求,则可能会发生这种情况。”)是由竞争引起的这种情况通常发生在您忘记await承诺时,导致导航与响应处理交错。

这里有很多问题和反模式,其中一些会导致竞争条件。您的几个.then回调永远不会返回任何内容。例如:

element.focus().then(...
Run Code Online (Sandbox Code Playgroud)

应该

return element.focus().then(...
Run Code Online (Sandbox Code Playgroud)

以下模式是不正确的:

await readline.question('What track do you want to search for?', (input) => {
Run Code Online (Sandbox Code Playgroud)

异步函数通常要么返回承诺,要么接受回调,但不能同时返回两者。await当你实际上在等待时,这些欺骗让你认为你将其保留在承诺链中undefined。实际的“承诺”是回调。

几乎总是,永远不要混合awaitthen。Promise 的目的是扁平化代码,以便您可以以同步风格编写它。如果您发现有多层嵌套回调 或.then(async () => ...,则应发出危险信号,并且您未能处理错误或放弃承诺链的可能性会增加。

如果您需要承诺回调,您可以:

const question = prompt =>
  new Promise(resolve =>
    readline.question(prompt, response => resolve(response))
  );
Run Code Online (Sandbox Code Playgroud)

现在您可以在代码中“同步”使用它,如下所示:

const input = await question("What track do you want to search for?");
Run Code Online (Sandbox Code Playgroud)

还有 Nodeutils.promisify机械地执行或多或少相同的操作。

如果没有用户名和密码,我无法运行您的代码,但是如果您删除所有thens(是的,最后一个!),await单个承诺链中的所有内容并承诺任何基于回调的异步函数,您应该能够避免这种情况错误。

我还建议避免使用那些又长又僵化的浏览器生成的 XPath。他们对结构做出了太多的假设,很容易失败,而且几乎总是有更强大的选择器或路径可以使用。

退后一步,我建议慢慢编码并在每一步运行代码,这样您就可以一路验证每个假设。这样做,您可以最大限度地减少问题并立即解决它们,避免出现多个问题难以同时调试的混乱、复杂的情况。

请参阅此答案,了解避免此错误的工作代码的最小、可重现的示例(不幸的是,它所附加的问题也是不可重现的)。链接的答案位于 Playwright 中,但相同的承诺问题和解决方案同样适用于 Puppeteer。

也可以看看: