管理 puppeteer 的内存和性能

Managing puppeteer for memory and performance

我正在使用 puppeteer 抓取一些页面,但我很好奇如何在节点应用程序的生产中管理它。我将在一天内抓取多达 500,000 页,但这些抓取作业将以随机间隔发生,因此这不是我可以完成的单个队列。

我想知道的是,打开浏览器,转到页面,然后在每次作业之间关闭浏览器是否更好?我认为哪个会慢很多,但也许可以更好地处理内存?

或者我是在应用程序启动时打开一个全局浏览器,然后转到该页面,并在我完成该页面后以某种方式转储该页面(例如,关闭 [=19= 中的所有选项卡) ], 但不关闭 chrome) 然后在我需要的时候重新打开一个新页面?这种方式看起来会更快,但可能会占用大量内存。

我从来没有用过这个库,尤其是在生产环境中,所以我不确定是否有我应该注意的地方。

如果您每天要抓取 500,000 页(大约每 0.1728 秒 抓取一页),那么我建议打开一个新页面在 现有 浏览器会话中,而不是为每个页面打开一个新的浏览器会话。

您可以使用以下方法打开和关闭 Page

const page = await browser.newPage();
await page.close();

如果您决定为您的项目使用一个 浏览器,我会确保实施错误处理程序以确保如果程序崩溃,您在创建时的停机时间最短新的 PageBrowserBrowserContext.

您可能想要创建一个包含多个具有独立浏览器的 Chromium 实例的池。这样做的好处是,当一个浏览器崩溃时,所有其他作业都可以保留 运行。一个浏览器(具有多个页面)的优点是内存少,CPU 优点是 cookie 在页面之间共享。

人偶操作实例池

图书馆 puppteer-cluster(免责声明:我是作者)为您创建了一个浏览器或页面池。它负责为您创建、错误处理、浏览器重启等。所以你可以简单地排队jobs/URLs,图书馆会处理其他一切。

代码示例

const { Cluster } = require('puppeteer-cluster');

(async () => {
    const cluster = await Cluster.launch({
        concurrency: Cluster.CONCURRENCY_BROWSER, // use one browser per worker
        maxConcurrency: 4, // cluster with four workers
    });

    // Define a task to be executed for your data (put your "crawling code" in here)
    await cluster.task(async ({ page, data: url }) => {
        await page.goto(url);
        // ...
    });

    // Queue URLs when the cluster is created
    cluster.queue('http://www.google.com/');
    cluster.queue('http://www.wikipedia.org/');

    // Or queue URLs anytime later
    setTimeout(() => {
        cluster.queue('http://...');
    }, 1000);
})();

如果您有不同的任务要做,您也可以直接对函数进行排队。通常你会在通过 cluster.close() 完成后关闭集群,但你可以自由地让它保持打开状态。您会发现另一个集群示例,该集群在请求进入 repository.

时获取数据
  • 重复使用浏览器和页面实例,而不是每次都启动浏览器
  • 同时将您的 chrome 爬虫暴露给 从队列而不是休息端点接受请求 。这将确保 chrome 可以度过美好的时光,并且如果出现崩溃,请求会在队列中。

其他与性能相关的文章是,

  1. 如果您只需要 DOM - https://www.scrapehero.com/how-to-increase-web-scraping-speed-using-puppeteer/
  2. ,请不要渲染图像、字体和样式表
  3. 提高性能 - https://docs.browserless.io/blog/2019/05/03/improving-puppeteer-performance.html
  4. 如果您有足够的时间 - CEF 值得再看一遍 - https://bitbucket.org/chromiumembedded/cef/src/master/ - CEF 允许您将 Chromium 嵌入到您自己的代码中,而不是像 puppeteer 那样使用库。 (Puppeteer 的工作方式是在侧面启动 chrome 并与之通信)。
  5. 在花时间研究 puppeteer (https://playwright.dev/) 之前,还可以查看 Microsoft 的 Playwright。
  6. 这是一个实现网页抓取的教程 - 使用 k8、openfaas 和 puppeteer - https://www.openfaas.com/blog/puppeteer-scraping/
  7. 这是一篇关于如何使用代理服务器使用 headless chrome 和 puppeteer 进行抓取的重要文章 - https://blog.apify.com/how-to-make-headless-chrome-and-puppeteer-use-a-proxy-server-with-authentication-249a21a79212/

这是另一个使用 puppeteer 和通用池库的示例。

const puppeteer = require('puppeteer');
const genericPool = require("generic-pool");

async function createChromePool() {
    
    const factory = {
        create: function() {
            //open an instance of the Chrome headless browser - Heroku buildpack requires these args
            return puppeteer.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox', '--ignore-certificate-errors'] });
        },
        destroy: function(client) {
            //close the browser
            client.close();
        }
    };  
    const opts = { max: 1, acquireTimeoutMillis: 120000, priorityRange: 3};
    global.chromepool = genericPool.createPool(factory, opts);
    
    global.chromepool.on('factoryCreateError', function(err){
        debug(err);
    });
    global.chromepool.on('factoryDestroyError', function(err){
        debug(err);
    });

}

async function destroyChromePool() {
    
    // Only call this once in your application -- at the point you want to shutdown and stop using this pool.
    global.chromepool.drain().then(function() {
        global.chromepool.clear();
    });

}