【问题标题】:Can I have multiple Puppeteer browsers open?我可以打开多个 Puppeteer 浏览器吗?
【发布时间】:2019-04-06 22:40:50
【问题描述】:

我正在使用node-cron(它允许您在节点程序中运行 cron 脚本)来运行一些 puppeteer 抓取。这些脚本有时会同时运行,这意味着将同时打开多个浏览器实例const browser = await puppeteer.launch()

这是不好的做法吗?如果是这样,是否有另一种编写此代码的方法不会使其失败?

感谢您的帮助。

cron.schedule('*/15 * * * *', async () => {    
    const browser = await pupeteer.launch({ headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox']});
    const page = await browser.newPage(); // Create new instance of puppet
    let today = moment();        
    logger.info(`Chrome Launched...`);

    try {
        await senatorBot(users, page, today.format("YYYY-DD-MM"));
    } catch(err) {
        logger.debug(JSON.stringify(err));
    }

    try {
        await senateCandidateBot(users, page, today.format("YYYY-DD-MM")); // This sequence matters, because agree statement will not be present...
    } catch(err) {
        logger.debug(JSON.stringify(err));
    }

    await page.close();
    await browser.close();
    logger.info(`Chrome Closed.`);

});

cron.schedule('*/15 17-19 * * 1-5', async () => {   

    logger.info(`Chrome Launched...`); 
    const browser = await pupeteer.launch({ headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox']});
    const page = await browser.newPage(); // Create new instance of puppet
    let today = moment();

    try {
        await contractBot(users, page, today.format("MM-DD-YYYY"));
    } catch(err) {
        logger.debug(JSON.stringify(err));
    }

    await page.close();
    await browser.close();
    logger.info(`Chrome Closed.`);
});

【问题讨论】:

  • 您可以连接到已经运行的 Chromium 实例。我所做的是尝试连接,如果连接失败,我会使用分离的 spawn 启动实例 Chromium。然后,您可以使用 browserWSEndpoint 进行连接。我将 wsEndpoint 存储在最初使用的简单文本文件中。效果很好..
  • 嘿,Keith 听起来很有趣,你能提供更多细节吗?
  • 是的,我会看看能不能挖掘出相关的代码。

标签: javascript node.js memory web-scraping puppeteer


【解决方案1】:

一般来说,只要你有足够强大的机器,并行打开两个浏览器是没有问题的。所以这个问题的答案完全取决于你机器的资源。您是否有足够的内存和 CPU 来支持多个打开的 Chrome 浏览器?

检查你是否有足够的资源

如果您使用的是 linux,请打开类似 htop 的工具来检查任务运行时处理了多少内存和 CPU。当您达到 CPU/内存限制时,您应该考虑按顺序运行任务(见下文)。

使用资源池

即使您有足够的资源,您也可以使用库puppeteer-cluster(免责声明:我是作者)来处理并发处理。该库还将负责错误处理(如果浏览器崩溃怎么办?),并且可以在运行期间向您显示内存、CPU 使用率和爬取统计信息。

代码示例

这是一个如何使用它的最小示例。

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

async function task1({ page }) => { // your first task, page is provided to your task
    await page.goto('...');
    // ...
}

async function task2({ page }) => { // another task
    await page.goto('...');
    // ...
}

(async () => {
    const cluster = await Cluster.launch({
        concurrency: Cluster.CONCURRENCY_BROWSER, // spawn to parallel browsers
        maxConcurrency: 2, // how many tasks should be run concurrently
    });

    cron.schedule('...', () => {
        cluster.queue(task1);
    });

    cron.schedule('...', () => {
        cluster.queue(task2);
    });
})();

按顺序抓取

如果你的机器没有资源来运行两个浏览器,你也可以一个接一个地运行任务,你只需要将maxConcurrency的值设置为1。这样排队的任务就不会并行运行,而是顺序运行,因为只有一个开放资源。

【讨论】:

    【解决方案2】:

    好的,找到了一些可以帮助您入门的代码。代码绑定到我的自定义代码库中,但我使用的函数可以轻松替换为您自己的。

    首先,我编写了一个简单的节点文件,该文件创建了一个 Chromium 实例,并保存了对 wsEndpoint 的引用,以后我们可以使用它来连接。

    文件:chromiumLauncher.js

    const writeText = require("mylib/core.io.file/write-text");
    const puppeteer = require("puppeteer");
    const path = require("path");
    const common = require("./common");
    
    (async () => {
      const launch_options = {
        args: ['--disable-features=site-per-process'],
        headless: false,
        devtools: false,
        defaultViewport: {width: 1200, height: 1000},
        userDataDir: common.userDataDir
      };
      const browser = await puppeteer.launch(launch_options);
      const wsEndpoint = browser.wsEndpoint();
      await writeText(common.fnSettings, JSON.stringify({wsEndpoint}, null, "  "));
    })();
    

    上面common.js只是我存储一些简单的配置设置,你可以自己替换,它只是简单的存储一些路径,它只是存储pupperteer放置它的数据文件的位置以及保存@987654325的位置@ 价值。而write-text 只是一个简单的基于promise 的用于编写文本文件的函数,基本上是fs.writeFile,编码设置为utf-8

    接下来我们创建另一个名为connect的js文件,

    const puppeteer = require("puppeteer");
    const cp = require('child_process');
    const delay = require("mylib/promise/delay");
    let browser = null;
    
    const readText = require("mylib/core.io.file/read-text");
    const common = require("./common");
    
    
    async function launch () {
      cp.spawn('node', ['chromiumLauncher.js'], {
        detached: true,
        shell: true,
        cwd: __dirname
      });
      await delay(5000); //lets wait 5 seconds
    }
    
    async function getSettings() {
      try {
        const settingsTxt = await readText(common.fnSettings);
        return JSON.parse(settingsTxt);
      } catch (e) {
        if (e.code !== 'ENOENT') throw e;
        return null;
      }
    }
    
    
    async function connect () {
      if (browser) return browser;
      let settings = await getSettings();
      if (!settings) {
        await launch();
        settings = await getSettings();
      }
      try {
        browser = await puppeteer.connect({browserWSEndpoint: settings.wsEndpoint});
      } catch (e) {
        const err = e.error || e;
        if (err.code === "ECONNREFUSED") {
          console.log("con ref");
          await launch();
          settings = await getSettings();
          browser = await puppeteer.connect({browserWSEndpoint: settings.wsEndpoint});
        }
      }
      return browser;
    }
    
    
    module.exports = connect;
    

    上面还有几个自定义库函数,但应该很容易替换。 read-text,与 write-text 正好相反,delay 只是一个简单的基于 promise 的延迟。

    就是这样,使用..

    const connect = require("path-to/connect");
    const browser = await connect();
    const page = await browser.newPage();
    

    因为我们以分离方式启动 Chromium,当进程关闭/连接时,它将保持打开状态。我有大约 7 个进程与在 Chromium 中打开的 70 个网页相关联,没有任何问题。需要注意的一点是,因为我确实在分​​离的 spawn 中启动了 chromium,所以如果你也需要,你可以手动关闭 chromium。另一种选择是在 PM2 https://www.npmjs.com/package/pm2 等进程管理器中启动 chromiumLauncher.js

    【讨论】:

    • '--disable-features=site-per-process' 这个论点拯救了我的一天,非常感谢先生
    猜你喜欢
    • 1970-01-01
    • 2022-01-16
    • 2022-12-28
    • 2012-12-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-18
    相关资源
    最近更新 更多