Puppeteer: ๋งˆ์ง€๋ง‰ ํŽ˜์ด์ง€๊ฐ€ ๋‹ซํž ๋•Œ ๋ธŒ๋ผ์šฐ์ €๋ฅผ ๋‹ซ์œผ๋ฉด ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

์— ๋งŒ๋“  2018๋…„ 03์›” 27์ผ  ยท  9์ฝ”๋ฉ˜ํŠธ  ยท  ์ถœ์ฒ˜: puppeteer/puppeteer

๊ท€ํ•˜์˜ ํ™˜๊ฒฝ์— ๋Œ€ํ•ด ์•Œ๋ ค์ฃผ์‹ญ์‹œ์˜ค.

  • Puppeteer ๋ฒ„์ „ : 1.2.0
  • ํ”Œ๋žซํผ / OS ๋ฒ„์ „ : CentOS 7.4.1708
  • URL (ํ•ด๋‹นํ•˜๋Š” ๊ฒฝ์šฐ) : N / A
  • Node.js ๋ฒ„์ „ : v8.10.0

๋ฌธ์ œ๋ฅผ ์žฌํ˜„ํ•˜๋Š” ๋‹จ๊ณ„๋Š” ๋ฌด์—‡์ž…๋‹ˆ๊นŒ?

const puppeteer = require('puppeteer');

const run = async () => {
  const browser = await puppeteer.launch();

  // close any open pages
  let pages = await browser.pages();
  await Promise.all(pages.map(async page => await page.close()));

  // handle a page being closed
  browser.on('targetdestroyed', async target => {
    const openPages = await browser.pages();
    console.log('Open pages:', openPages.length);
    if (openPages.length == 0) {
      console.log('Closing empty browser');
      await browser.close();
      console.log('Browser closed');
    }
  });

  pages = await browser.pages();
  for (let i = 0; i < 5; i++) {
    await browser.newPage();
  }

  pages = await browser.pages();
  pages.forEach((page) => page.close());
};

run();

์˜ˆ์ƒ๋˜๋Š” ๊ฒฐ๊ณผ๋Š” ๋ฌด์—‡์ž…๋‹ˆ๊นŒ?

Count: 0
Open pages: 4
Open pages: 3
Open pages: 2
Open pages: 1
Open pages: 0
Closing empty browser
Browser closed

๋Œ€์‹  ์–ด๋–ป๊ฒŒ ๋˜๋‚˜์š”?

Open pages: 4
Open pages: 3
Open pages: 2
Open pages: 1
Open pages: 0
Closing empty browser
(node:29074) UnhandledPromiseRejectionWarning: Error: Protocol error (Target.closeTarget): Target closed.
    at Promise (node_modules/puppeteer/lib/Connection.js:86:56)
    at new Promise (<anonymous>)
    at Connection.send (node_modules/puppeteer/lib/Connection.js:85:12)
    at Page.close (node_modules/puppeteer/lib/Page.js:802:36)
    at pages.forEach (test.js:27:32)
    at Array.forEach (<anonymous>)
    at run (test.js:27:9)
    at <anonymous>
    at process._tickCallback (internal/process/next_tick.js:188:7)
(node:29074) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:29074) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
Browser closed

์ด๊ฒƒ์€ ๋งค๋ฒˆ ์‹คํ–‰๋˜๋Š” ๊ฒƒ์€ ์•„๋‹ˆ์ง€๋งŒ ๋งค๋ฒˆ ์ž์ฃผ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ๋‚ด๊ฐ€ ์—ฌ๊ธฐ์„œ ๋ญ”๊ฐ€ _bad_ํ•˜๊ณ ์žˆ๋Š” ๊ฒƒ์ด ์ „์ ์œผ๋กœ ๊ฐ€๋Šฅํ•˜๋‹ค!

bug chromium

๋ชจ๋“  9 ๋Œ“๊ธ€

+1

์•ฝ๊ฐ„์˜ ๋น ๋ฅธ ๋””๋ฒ„๊น…์„ ์ˆ˜ํ–‰ํ•˜๋ฉด targetDestroyed page.close() ๋ฉ”์‹œ์ง€๊ฐ€ ํ™•์ธ๋˜๊ธฐ ์ „์— targetDestroyed ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค (๋˜๋Š” ์ธํ˜•์ด ํ™•์ธ์„ ์ฒ˜๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค).

๋‚˜๋Š” ๊ทธ๊ฒƒ์„ ๊ณ ์น˜๋Š” ๋ฐฉ๋ฒ•์„ ์ƒ๊ฐํ•  ์ˆ˜์žˆ๋‹ค-์˜ˆ๋ฅผ ๋“ค๋ฉด targetDestroyed ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์ „์— Target.closeTarget ์Šน์ธ์„ ๊ธฐ๋‹ค๋ฆฐ๋‹ค. ๊ทธ๋ž˜๋„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๋˜ ๋‹ค๋ฅธ ์งˆ๋ฌธ์ž…๋‹ˆ๋‹ค!

์กฐ์‚ฌ์— ๋Œ€ํ•ด @MJPA ์—๊ฒŒ ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค.

์ด ๋ฌธ์ œ๋Š” ๊ด€๋ จ ํŽ˜์ด์ง€๊ฐ€ ๋‹ซํžˆ๊ธฐ ์ „์— browser ์ด ๋ชฉ๋ก์—์„œ ๋Œ€์ƒ์„ ์ œ๊ฑฐํ•œ๋‹ค๋Š” ์‚ฌ์‹ค์—์„œ ๋น„๋กฏ๋ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ ์˜ˆ์—์„œ๋Š” page.close() ํ˜ธ์ถœ์ด ์™„๋ฃŒ ๋  ๋•Œ๊นŒ์ง€ browser.pages() ๊ฐ€ 1 ํŽ˜์ด์ง€๋ฅผ๋ณด๊ณ  ํ•  ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒํ•ฉ๋‹ˆ๋‹ค.

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const [page] = await browser.pages();
  browser.on('targetdestroyed', async () => console.log('Target destroyed. Pages count: ' + (await browser.pages()).length));
  console.log('closing page...');
  await page.close();
  console.log('page closed.');
  await browser.close();
})();

์ถœ๋ ฅ :

closing page...
Target destroyed. Pages count: 0
page closed.

@JoelEinbinder ์–ด๋–ป๊ฒŒ ์ƒ๊ฐํ•˜์„ธ์š”?

@MJPA ์™€ ๋™์ผํ•œ ๋ฌธ์ œ

        await page.waitForSelector('aside.aside a[href="/checkout"]')
    .then(() => {
        logMessage('Checkout is available');
    })
    .catch(err => {
        log('Checkout not available');
        success = false;
        browser.close();
    });
    // click checkout button
    await Promise.all([
        page.click('aside.aside a[href="/checkout"]'),
        page.waitForNavigation()
    ]);

์ด ๊ฒฝ์šฐ ํ•ญ์ƒ ๋™์ผํ•œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.

์ด ์ธํ˜•๊ทน ์˜ค๋ฅ˜ ์ผ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

์•ฝ๊ฐ„ ์œ ์‚ฌํ•œ ๊ตฌํ˜„์œผ๋กœ ๋™์ผํ•œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜์ง€๋งŒ page ์—์„œ close ์ด๋ฒคํŠธ๋ฅผ ์ˆ˜์‹ ํ•˜๋ฉด๋ฉ๋‹ˆ๋‹ค. ๋ฌธ์„œ๋Š” "๋‹ซ๊ธฐ"์˜ ์˜๋ฏธ์— ๋Œ€ํ•ด ์ง€๋‚˜์น˜๊ฒŒ ๋ช…์‹œ ์ ์ด ์ง€ ์•Š์ง€๋งŒ ๊ตฌํ˜„์— ๋”ฐ๋ผ ํŽ˜์ด์ง€๊ฐ€ ๋‹ซํžˆ๊ธฐ ์ „์— close ๊ฐ€ ์‹คํ–‰๋˜๋ฏ€๋กœ ๋‹ค์Œ ์˜ˆ์ œ๋Š” targetdestroyed ์™€ ์œ ์‚ฌํ•˜๊ฒŒ ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

const puppeteer = require('puppeteer');
const browser = await puppeteer.launch();

// create 5 test pages
for(let i=0; i<5; i++){

  let page = await browser.newPage();

  // listen to page close event and close browser
  // when all get closed
  page.on('close', async () => {
    let pages = await browser.pages();
    if(!pages.length){
      // will not get here!
      browser.close();
    }
  });

}

let pages = await browser.pages();

pages.forEach(async (page) => await page.close());  

์ด๋ฒคํŠธ๊ฐ€ closed ์•„๋‹ˆ๋ผ close ๋ผ๊ณ  ๋ถˆ๋ฆฌ๊ธฐ ๋•Œ๋ฌธ์—์ด _sort of_๊ฐ€ ์˜๋ฏธ๊ฐ€ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. close() promise ์ฝœ๋ฐฑ์—์„œ ๋‚ด ์ž์‹ ์˜ closed ์ด๋ฒคํŠธ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๋Œ€์‹  ์ˆ˜์‹ ํ•˜์—ฌ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ–ˆ์Šต๋‹ˆ๋‹ค.

page.close().then(() => {
  page.emit('closed');
});

์ด์•ผ๊ธฐ์˜ ๊ตํ›ˆ์€ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์–ธ์ œ ๋‹ซํ˜€๋„ ์•ˆ์ „ํ•œ์ง€ (์˜ˆ์™ธ๋ฅผ ํฌ์ฐฉํ•˜์ง€ ์•Š๊ณ ) ์•Œ ์ˆ˜์žˆ๋Š” 100 % ๋ณด์žฅ ๋œ ๋ฐฉ๋ฒ•์ด ์—†๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋‚ด๊ฐ€ ๋น ์ง„ ๊ฒƒ์ด ์žˆ์œผ๋ฉด ์•Œ๋ ค์ฃผ์„ธ์š”.

# 3423๊ณผ ๊ด€๋ จ์ด์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ?

๋ธŒ๋ผ์šฐ์ €์˜ ๋ชจ๋“  ํƒญ์„ ๋‹ซ์€ ๋‹ค์Œ ์ƒˆ ํŽ˜์ด์ง€๋ฅผ ์—ด๋ ค๊ณ ํ•˜๋ฉด์ด ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ ์˜ˆ์—์„œ๋Š” page.close() ํ˜ธ์ถœ์ด ์™„๋ฃŒ ๋  ๋•Œ๊นŒ์ง€ browser.pages() ์—์„œ 1 ํŽ˜์ด์ง€๋ฅผ๋ณด๊ณ  ํ•  ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒํ•ฉ๋‹ˆ๋‹ค.

์œ„์˜ ์˜ˆ์—์„œ ์ฝ”๋“œ๊ฐ€ ์†์ƒ๋ฉ๋‹ˆ๋‹ค. ๋งˆ์ง€๋ง‰์œผ๋กœ ๋‹ซํžŒ ํŽ˜์ด์ง€, brower.pages (). length๋Š” 1์ด๊ณ  ๋ธŒ๋ผ์šฐ์ €๋Š” ๋‹ซํžˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ๋” ์ด์ƒ ํƒ€๊ฒŸ ํŒŒ๊ดด ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๊ณ  ๋ธŒ๋ผ์šฐ์ €๋Š” ์˜์›ํžˆ ์‚ด ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋ฏธ๋ฌ˜ํ•˜์ง€๋งŒ ์ด๊ฒƒ์ด ์‚ฌ์šฉ์ž ์˜ค๋ฅ˜๋ผ๊ณ  ๋งํ•˜๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค. ๋ฅผ ํ˜ธ์ถœํ•˜๊ธฐ page.close ํ˜ธ์ถœํ•˜๊ธฐ ์ „์— ๊ธฐ๋‹ค๋ ค์˜จํ•ด์•ผ browser.close() .

์‚ฌ์šฉ์ž ์ œ์–ด ํŽ˜์ด์ง€๊ฐ€ ์—†์œผ๋ฉด Promise.all ๋ชจ๋“  ํŽ˜์ด์ง€๊ฐ€ ๋‹ซํžˆ๊ณ  ๋ธŒ๋ผ์šฐ์ €๋ฅผ ๋‹ซ์Šต๋‹ˆ๋‹ค. targetdestroyed ์ด๋ฒคํŠธ๋ฅผ ์ˆ˜์‹  ํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๋˜๋Š” browser.close๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ๋ชจ๋“  ํŽ˜์ด์ง€๋ฅผ ์ž๋™์œผ๋กœ ๋‹ซ์Šต๋‹ˆ๋‹ค.

์‚ฌ์šฉ์ž ์ œ์–ด ํŽ˜์ด์ง€๊ฐ€์žˆ๋Š” ๊ฒฝ์šฐ targetdestroyed ์ด๋ฒคํŠธ๋ฅผ ์ˆ˜์‹ ํ•˜์ง€๋งŒ page.close ๋ฅผ ํ˜ธ์ถœ ํ•  ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค.

+1.
page.on("popup") ํŒ์—…์„ ๋‹ซ์€ ํ›„์ด ์˜ค๋ฅ˜๊ฐ€ ํ‘œ์‹œ๋˜๊ธฐ ์‹œ์ž‘ํ–ˆ์Šต๋‹ˆ๋‹ค. ํ”„๋ ˆ์ž„ ์ปจํ…์ŠคํŠธ ๋‚ด์—์„œ ํŒ์—…์ด ๋ฐœ์ƒํ•˜๋ฉด "popup.close ()"๋กœ ํŒ์—…์„ ๋‹ซ์„ ์ˆ˜์—†๊ณ  ํผ ํ”ผํ„ฐ๊ฐ€ ํƒญ์„ ์—ด์–ด๋‘๊ธฐ ๋•Œ๋ฌธ์— ์ „์ฒด ๋ธŒ๋ผ์šฐ์ €๋ฅผ ๋‹ซ์œผ๋ ค๊ณ ํ•˜๋ฉด "Target Closed"์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์ค‘์ง€๋ฉ๋‹ˆ๋‹ค.

Chrome 73๊ณผ ํ•จ๊ป˜ puppeteer 1.14๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค (Chromium์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ).
๋ธŒ๋ผ์šฐ์ €๋ฅผ ๋‹ซ๋Š” ๋ฐ ์‚ฌ์šฉํ•˜๋Š” ์ฝ”๋“œ

async close_browser() {
    for (let page of await this.browser.pages()) {
      await page.close({
        "runBeforeUnload": true
      });
    }
   await this.browser.close();
}
์ด ํŽ˜์ด์ง€๊ฐ€ ๋„์›€์ด ๋˜์—ˆ๋‚˜์š”?
0 / 5 - 0 ๋“ฑ๊ธ‰