【问题标题】:Using Puppeteer, how can I open a page, get the data, then go back to the previous page to get the next page on the list?使用 Puppeteer,我如何打开一个页面,获取数据,然后返回上一页以获取列表中的下一页?
【发布时间】:2021-04-23 17:56:26
【问题描述】:

情况:

这是我想做的:

1) 我加载页面 0。页面 0 包含指向不同页面的可点击链接。我想加载所有这些页面的内容。所以:

2) 点击第一个链接。加载页面 1. 获取数据。返回上一页(第0页)

3) 点击加载页面 2 的第二个链接。等等.. 无限循环,直到所有链接都被点击。

使用我当前的代码,加载第 0 页,然后单击第一个链接并加载第 1 页,然后出现崩溃并显示以下错误:

(node:2629) UnhandledPromiseRejectionWarning: Error: Protocol error (Runtime.callFunctionOn): Execution context was destroyed.

问题:

我做错了什么,如何让我的脚本按照我的预期运行?


代码:

const puppeteer = require('puppeteer');
const fs = require('fs');

let getData = async () => {
    const browser = await puppeteer.launch({headless: false});
    const page = await browser.newPage();

    await page.goto('url', { waitUntil: 'networkidle2' });
    await page.setViewport({width: ..., height:...});

    const result = await page.evaluate(async () => {
        let data = []; 
        let elements = document.querySelector('.items').querySelectorAll('.item'); 

        for (const element of elements) {

            element.click();
            await new Promise((resolve) => setTimeout(resolve, 2000));

            // GETTING THE DATA THEN PUSHING IT INTO THE DATA ARRAY

            await page.goBack();
        }

        return data; // Return our data array

    });

    browser.close();
    return result; // Return the data
};

【问题讨论】:

  • 如果为consoleerror 添加处理程序,您会看到任何错误吗?
  • 您是否考虑过只制作一个刮板而不是在窗口中实际加载页面?
  • @codeWonderland 是的。我需要这样做。

标签: javascript node.js puppeteer


【解决方案1】:

好的,这是我对此的看法。首先,您错误地使用了evaluate 方法。主要是因为您实际上并不需要它,还因为您要求它做一些它不能做的事情。只是为了解释一下:evaluate 方法仅在您的 网页 上下文中运行。它几乎只允许您直接在远程浏览器的当前页面上执行 Javascript 指令。它没有你在该函数外部声明的变量的概念——所以在这种情况下,当你这样做时:

await page.goBack();

evaluate 方法不知道page 是什么,也不知道如何使用它。现在有一些方法可以将page 注入到evaluate 方法中,但这也不能解决您的问题。 Puppeteer API 调用根本无法在 evaluate 方法中工作(我自己尝试过,它总是返回异常)。

现在让我们回到您确实遇到的问题 - 您在 evaluate 函数中所做的是检索一个具有类 .items 的 UI 元素,然后搜索该 UI 元素中具有类 @ 的每个 UI 元素987654331@。然后,您将遍历所有找到的 UI 元素,单击每个元素,获取某种数据,然后返回单击下一个元素。

您无需使用 evaluate 方法就可以实现所有这些,而是​​使用 Puppeteer API 调用,如下所示:

const itemsList = await page.$('.items'); // Using '.$' is the puppeteer equivalent of 'querySelector'
const elements = await itemsList.$$('.item'); // Using '.$$' is the puppeteer equivalent of 'querySelectorAll'

const data = [];
elements.forEach(async (element) => {
  await element.click();
  // Get the data you want here and push it into the data array
  await page.goBack();
});

希望对你有所帮助!

【讨论】:

    【解决方案2】:

    与其来回导航以单击第一页中的下一个链接,不如将第一页中的链接存储到一个数组中,然后使用page.goto() 一次打开一个链接会更有意义.

    换句话说,您可以使用以下示例完成此任务:

    await page.goto('https://example.com/page-1');
    
    const urls = await page.evaluate(() => Array.from(document.querySelectorAll('.link'), element => element.href));
    
    for (let i = 0, total_urls = urls.length; i < total_urls; i++) {
      await page.goto(urls[i]);
    
      // Get the data ...
    }
    

    【讨论】:

      【解决方案3】:

      @AJC24 对我不起作用。问题是单击并返回原始页面时页面上下文被破坏。

      我最终不得不做的是类似于格兰特建议的事情。我将所有按钮标识符收集在一个数组中,然后返回到原始页面,我会再次单击。

      【讨论】:

        【解决方案4】:

        通过使用来自@Grant 的迭代

        执行上下文被破坏,很可能是因为导航。

        然后我让它在迭代中打开一个新标签,它解决了问题!

        for (let i = 0, total_urls = urls.length; i < total_urls; i++) {
          const page = await browser.newPage();
          await page.goto(url), { waitUntil: 'networkidle0', timeout: 0 };
        
          await page.goto(urls[i]);
        
          // Get the data ...
        }
        

        【讨论】:

          【解决方案5】:

          你应该也可以在 eval 函数中使用 window.history.go(-1) 来完成。 MDN for history.go

          const puppeteer = require('puppeteer');
          const fs = require('fs');
          
          let getData = async () => {
              const browser = await puppeteer.launch({headless: false});
              const page = await browser.newPage();
          
              await page.goto('url', { waitUntil: 'networkidle2' });
              await page.setViewport({width: ..., height:...});
          
              const result = await page.evaluate(async () => {
                  let data = []; 
                  let elements = document.querySelector('.items').querySelectorAll('.item'); 
          
                  for (const element of elements) {
          
                      element.click();
                      await new Promise((resolve) => setTimeout(resolve, 2000));
          
                      // GETTING THE DATA THEN PUSHING IT INTO THE DATA ARRAY
          
                      await window.history.go(-1);
                  }
          
                  return data; // Return our data array
          
              });
          
              browser.close();
              return result; // Return the data
          };
          

          【讨论】:

            猜你喜欢
            • 2015-03-14
            • 2020-08-23
            • 2019-05-25
            • 1970-01-01
            • 2020-02-27
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多