使用Puppeteer,如何打开页面,获取数据,然后返回上一页以获取列表中的下一页?

时间:2018-08-06 11:23:43

标签: javascript node.js puppeteer

情况:

这就是我想要做的:

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
};

5 个答案:

答案 0 :(得分:4)

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

await page.goBack();

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

因此,现在让我们回到您遇到的问题上-在evaluate函数中正在执行的操作是检索一个类为.items的UI元素,然后在该UI中搜索每个UI元素类为.item的元素。然后,您将遍历所有找到的UI元素,单击每个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();
});

希望这对您有所帮助!

答案 1 :(得分:2)

与其来回导航以单击第一页上的下一个链接,不如将第一页中的链接存储到数组中,然后使用{{3一次将其打开。 }}。

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

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 ...
}

答案 2 :(得分:1)

您也应该可以在 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
};

答案 3 :(得分:0)

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

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

答案 4 :(得分:0)

使用@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 ...
}