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.