使用 puppeteer 无法点击不同的链接

Trouble clicking on different links using puppeteer

我在节点中使用 puppeteer 编写了小脚本,从 website 的着陆页对不同帖子的 link 进行循环点击.

我的脚本中使用的站点 link 是一个占位符。此外,它们不是动态的。所以,puppeteer 可能有点矫枉过正。但是,我的目的是学习点击的逻辑。

当我执行我的第一个脚本时,它单击一次并在离开源时抛出以下错误。

const puppeteer = require("puppeteer");

(async () => {
    const browser = await puppeteer.launch({headless:false});
    const [page] = await browser.pages();
    await page.goto("https://whosebug.com/questions/tagged/web-scraping",{waitUntil:'networkidle2'});
    await page.waitFor(".summary");
    const sections = await page.$$(".summary");

    for (const section of sections) {
        await section.$eval(".question-hyperlink", el => el.click())
    }

    await browser.close();
})();

上述脚本遇到的错误:

(node:9944) UnhandledPromiseRejectionWarning: Error: Execution context was destroyed, most likely because of a navigation.

当我执行以下命令时,脚本假装点击了一次(实际上没有点击)并遇到了与之前相同的错误。

const puppeteer = require("puppeteer");

(async () => {
    const browser = await puppeteer.launch({headless:false});
    const [page] = await browser.pages();
    await page.goto("https://whosebug.com/questions/tagged/web-scraping");

    await page.waitFor(".summary .question-hyperlink");
    const sections = await page.$$(".summary .question-hyperlink");

    for (let i=0, lngth = sections.length; i < lngth; i++) {
        await sections[i].click();
    }

    await browser.close();
})();

上面抛出的错误:

(node:10128) UnhandledPromiseRejectionWarning: Error: Execution context was destroyed, most likely because of a navigation.

如何让我的脚本循环执行点击?

问题:

Execution context was destroyed, most likely because of a navigation.

错误说你想点击一些 link,或者在一些不再存在的页面上做一些事情,很可能是因为你离开了。

逻辑:

将 puppeteer 脚本想象成一个浏览真实页面的真人。

首先,我们加载 url (https://whosebug.com/questions/tagged/web-scraping).

接下来,我们要查看该页面上提出的所有问题。为此,我们通常会做什么?我们将执行以下任一操作,

  • 新标签中打开一个link。专注于那个新选项卡,完成我们的工作并返回到原始选项卡。接下来继续 link.
  • 我们点击 link,做我们的事情,返回上一页,继续下一页。

所以它们都涉及离开和返回当前页面。

如果你不按照这个流程,你会得到如上的错误信息。

解决方案

至少有 4 种或更多的方法可以解决这个问题。我会选择最简单的和复杂的。

方式:Link提取

首先我们提取当前页面上的所有 link。

const links = await page.$$eval(".hyperlink", element => element.href);

这为我们提供了 url 的列表。我们可以为每个 link.

创建一个新标签
for(let link of links){
  const newTab = await browser.newPage();
  await newTab.goto(link);
  // do the stuff
  await newTab.close();
}

这将逐一介绍每个 link。我们可以通过使用 promise.map 和各种队列库来改进这一点,但你明白了。

方式:返回首页

我们需要以某种方式存储状态,以便我们可以知道上次访问了哪个 link。如果我们访问了第三个问题并返回到标签页,那么下次我们需要访问第四个问题,反之亦然。

检查以下代码。

const puppeteer = require("puppeteer");

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

  await page.goto(
    `https://whosebug.com/questions/tagged/web-scraping?sort=newest&pagesize=15`
  );

  const visitLink = async (index = 0) => {
    await page.waitFor("div.summary > h3 > a");

    // extract the links to click, we need this every time
    // because the context will be destryoed once we navigate
    const links = await page.$$("div.summary > h3 > a");
    // assuming there are 15 questions on one page,
    // we will stop on 16th question, since that does not exist
    if (links[index]) {
      console.log("Clicking ", index);

      await Promise.all([

        // so, start with the first link
        await page.evaluate(element => {
          element.click();
        }, links[index]),

        // either make sure we are on the correct page due to navigation
        await page.waitForNavigation(),
        // or wait for the post data as well
        await page.waitFor(".post-text")
      ]);

      const currentPage = await page.title();
      console.log(index, currentPage);

      // go back and visit next link
      await page.goBack({ waitUntil: "networkidle0" });
      return visitLink(index + 1);
    }
    console.log("No links left to click");
  };

  await visitLink();

  await browser.close();
})();

结果:

编辑:有多个问题与此类似。如果您想了解更多信息,我将引用它们。

  • My answer about a logic for infinite scrolling

与其循环单击所有链接,我发现解析所有链接然后导航到每个链接并重复使用同一个浏览器会更好。试一试:

const puppeteer = require("puppeteer");

(async () => {
    const browser = await puppeteer.launch({headless:false});
    const [page] = await browser.pages();
    const base = "https://whosebug.com"
    await page.goto("https://whosebug.com/questions/tagged/web-scraping");
    let links = [];
    await page.waitFor(".summary .question-hyperlink");
    const sections = await page.$$(".summary .question-hyperlink");

    for (const section of sections) {
        const clink = await page.evaluate(el=>el.getAttribute("href"), section);
        links.push(`${base}${clink}`);
    }

    for (const link of links) {
        await page.goto(link);
        await page.waitFor('h1 > a');
    }
    await browser.close();
})();