Home > OS >  How can I launch a single process of puppeteer.launch() and just send pages to it in Node?
How can I launch a single process of puppeteer.launch() and just send pages to it in Node?

Time:02-26

The following code runs on every one of my requests and I'm afraid that it's trying to launch the browser every time and causing server issues on Heroku. I want to launch puppeteer like a Singleton instance where I only launch it once and then after that my requests will just trigger browser.newPage(). I'm not experienced in JS to resolve this.

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

      await page.on('response', interceptedResponse =>{
        let status = interceptedResponse.status();
        interceptedResponse.text()
          .then((text) => {          
            handleResponse(text)
            browser.close();
          })
          .catch(err => {
            console.error(`interceptedResponse error: ${err}`)
            browser.close();
          });
      });

      await page.goto(url);
    })();

CodePudding user response:

You can create a class handling this for you. It may not be "official singleton" but id does what you want:

checkout browser.js:

var puppeteer = require('puppeteer')

class PuppeteerApi {

    browser = null
    constructor(config) {
        this.config = config
    }
    
    setConfig(config) {
        this.config = config
    }

    async newBrowser() {
        return await puppeteer.launch(this.config)
    }

    async getBrowser() {

        if (!this.browser) {
            this.browser = await this.newBrowser()
        }

        return this.browser
    }

    async newPage() {
        const browser = await this.getBrowser()
        const page = await browser.newPage()
        return page
    }

    async handBack(page) {

        // close the page or even reuse it?.
        await page.close()

        // you could add logic for closing the whole browser instance depending what
        // you want.
    }

    async shutdown() {
        await this.browser.close()
    }


}

const config = {
    headless: false
}

const browserApi = new PuppeteerApi(config)
export default browserApi

// use it like:

// import and set config once!.
var browserApi = require('./browser.js')
const config = { headless: true }
browserApi.setConfig(config)

// in an request handler you could do this:
(async () => {
    
    var page = await browserApi.newPage()

    // do some stuff..

    // in the end hand the page back for eitehr closing it 
    // or maybe putting it in a pool? .
    await browser.handBack(page) 
})()

I do not know the behaviour of puppeteer when for example 30 pages would be opened. Here would be an example which could open a given amount of browser instances in parallel.

var puppeteer = require('puppeteer')

class PuppeteerApi {

    browsers = []
    index = 0

    constructor(browserLimit, config) {
        this.config = config
        this.browserLimit = browserLimit

        if (typeof this.browserLimit !== 'number' || this.browserLimit < 1) {
            throw 'BrowserLimit needs atleast to be 1!!'
        }
    }

    setConfig(config) {
        this.config = config
    }

    async newBrowser() {
        return await puppeteer.launch(this.config)
    }

    async getBrowser() {
        if (this.index >= this.browserLimit) {
            this.index = 0
        }

        if (!this.browsers[this.index]) {
            this.browsers[this.index] = await this.newBrowser()
        }

        // iterate through browsers.
        return this.browsers[this.index  ]

    }

    async newPage() {
        const browser = await this.getBrowser()
        const page = await browser.newPage()
        return page
    }

    async handBack(page) {
        await page.close()
    }

    async shutdown() {
        const proms = this.browsers.map(b => b.close())
        await Promise.all(proms)
    }

}

const config = {
    headless: false
}

const limit = 5
const browserApi = new PuppeteerApi(limit, config)
export default browserApi

If you like a functional style (which is less code), it is fastly to adapt. Here is the first example:

var puppeteer = require('puppeteer')

let browser = null

let config = {
    headless: false
}

const newBrowser = async() => {
    return await puppeteer.launch(this.config)
}

export const setPuppeteerConfig = (_config) => {
    config = _config
}

export const getPage = async() => {
    const browser = await getBrowser()
    return await browser.newPage()
}

const getBrowser = async() => {
    if (!browser) {
        browser = await newBrowser()
    }
    return browser
}

export const handback = async(page) => {
    await page.close()
}

export const shutdown = async() => {
    await browser.close()
}

// usage:

const { setPuppeteerConfig , shutdown, getPage, handback } = require('./browser')
// setconfig..
(async () => {
    const page = await getPage()

    // do some stuff..
    
    await handback(page)
}) 

Feel free to leave a comment if anything is not working as indendet.

  • Related