【问题标题】:slow looping over pages and extracting data using puppeteer使用 puppeteer 缓慢循环页面并提取数据
【发布时间】:2019-09-10 12:46:33
【问题描述】:

我有一张这样的桌子。名称列中的所有名称都是导航到下一页的链接。

|---------------------|------------------|
|         NAME        |         ID       |
|---------------------|------------------|
|        Name 1       |          1       |
|---------------------|------------------|
|        Name 2       |          2       |
|---------------------|------------------|
|        Name 3       |          3       |
|---------------------|------------------|

我正在尝试获取链接,从中提取数据,然后返回表格。但是,表中有超过 4000 条记录,并且所有内容的处理速度都非常慢(每条记录大约 1000 毫秒)

这是我的代码:

//Grabs all table rows.
const items = await page.$$(domObjects.itemPageLink);

  for (let i = 0; i < items.length; i++) {
    await page.goto(url);
    await page.waitForSelector(domObjects.itemPageLink);
    let items = await page.$$(domObjects.itemPageLink);

    const item = items[i];

    let id = await item.$eval("td:last-of-type", node => node.innerText.split(",").map(item => item.trim()));
    let link = await item.$eval("td:first-of-type a", node => node.click());

    await page.waitForSelector(domObjects.itemPageWrapper);
    let itemDetailsPage = await page.$(domObjects.itemPageWrapper);

    let title = await itemDetailsPage.$eval(".page-header__title", title => title.innerText);
    console.log(title);
    console.log(id);
  }

有没有办法加快这个速度,以便我可以更快地一次获得所有结果?我想将它用于我的 API。

【问题讨论】:

    标签: javascript node.js puppeteer


    【解决方案1】:

    这里有一些小的代码改进和一个主要的改进。

    小改进:使用更少的 puppeteer 函数

    细微的改进归结为尽可能少地使用 puppeteer 函数。您使用的大多数 puppeteer 函数都是通过 WebSocket 将数据从 Node.js 环境发送到浏览器环境。虽然这只需要几毫秒,但从长远来看,这些毫秒显然会加起来。有关这方面的更多信息,您可以查看 this question 询问使用 page.evaluate 与使用更多 puppeteer 函数的区别。

    这意味着,为了优化您的代码,您可以例如在页面内部使用querySelector,而不是多次运行item.$eval。另一个优化是直接使用page.waitForSelector的结果。该函数将在节点出现时返回它。因此,以后无需再通过page.$查询。

    这些只是很小的改进,可能会稍微提高抓取速度。

    主要改进:使用木偶池

    现在,您正在使用一个浏览器和一个页面来抓取多个 URL。您可以通过使用 puppeteer 资源池并​​行抓取多个 URL 来提高脚本的速度。 puppeteer-cluster 允许您这样做(免责声明:我是作者)。该库接受一个任务并将其并行应用于多个 URL。

    您可以使用的并行实例数取决于您的 CPU、内存和吞吐量。可以使用的越多,爬取速度就会越好。

    代码示例

    以下是一个最小示例,调整您的代码以提取相同的数据。代码首先建立一个有一个浏览器和四个页面的集群。之后,定义一个任务函数,该函数将为每个排队的对象执行。

    在此之后,集群的一个页面实例用于从初始页面中提取 ID 和 URL。赋予cluster.queue 的函数从页面中提取ID 和URL,并调用cluster.queue,对象为{ id: ..., url: ... }。对于每个排队的对象,都会执行 cluster.task 函数,然后提取标题并将其打印在传递的 ID 旁边。

    // Setup your cluster with 4 pages
    const cluster = await Cluster.launch({
        concurrency: Cluster.CONCURRENCY_PAGE,
        maxConcurrency: 4,
    });
    
    // Define the task for the pages (go the the URL, and extract the title)
    await cluster.task(async ({ page, data: { id, url } }) => {
        await page.goto(url);
        const itemDetailsPage = await page.waitForSelector(domObjects.itemPageWrapper);
        const title = await itemDetailsPage.$eval('.page-header__title', title => title.innerText);
        console.log(id, url);
    });
    
    // use one page of the cluster, to extract the links (ignoring the task function above)
    cluster.queue(({ page }) => {
        await page.goto(url); // URLs is given from outside
        // Extract the links and ids from the initial page by using a page of the cluster
        const itemData = await page.$$eval(domObjects.itemPageLink, items => items.map(item => ({
            id: item.querySelector('td:last-of-type').split(',').map(item => item.trim()),
            url: item.querySelector('td:first-of-type a').href,
        })));
        // Queue the data: { id: ..., url: ... } to start the process
        itemData.forEach(data => cluster.queue(data));
    });
    

    【讨论】:

    • 我怎样才能给你买啤酒?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-29
    相关资源
    最近更新 更多