Puppeteerの使い方(スクレイピング, フロントテストで活用)

Puppeteerは、Headless Chromeを操作できるNode.jsのライブラリです。Chrome DevToolsチームがメンテナンスを行なっており、スクレイピングやフロントテストに活用できます。ここでは、Puppeteerの基本的な使い方を確認します。

前知識

  • ヘッドレスブラウザとは?
    • GUI(画面)を持たないブラウザです。
  • Chromiumとは?
    • Google Chrome の元となっているオープンソースのブラウザです。
    • Chromium - Wikipedia

Puppeteerをインストール

Chromiumを含める

nodeのversionは以下の通りです。

$ node -v
v8.10.0

Puppeteerをインストールします。

$ yarn add puppeteer
$ npm install --save puppeteer

Puppeteerをインストールすると Chromium も同時にダウンロードされます。
macでインストールした場合、以下フォルダに Chromium が存在します。

$ ls -l node_modules/puppeteer/.local-chromium/mac-624492/chrome-mac/Chromium.app/Contents/MacOS
total 368
-rwxr-xr-x  1 xxx  xxx  186028 Feb 22 12:02 Chromium

Chromiumを含めない

Chromium は容量が大きいので注意が必要です。例えば AWS Lambda で利用する場合、Chromium を含めてしまうとデプロイパッケージの容量制限に引っかかります。

そこで、 Chromium を含めないでPuppeteerをインストールする方法があります。

方法1

PUPPETEER_SKIP_CHROMIUM_DOWNLOAD という環境変数を設定した状態でインストールすると Chromium が含まれません。

$ PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1 yarn add puppeteer

方法2

puppeteer-core という Chromium を含まないパッケージがあります。

$ yarn add puppeteer-core

動作例

sample1.js というファイルで、puppeteerを利用してみます。

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({
    headless: false,  // 動作確認するためheadlessモードにしない
    slowMo: 500  // 動作確認しやすいようにpuppeteerの操作を遅延させる
  })
  const page = await browser.newPage()

  await page.goto('https://www.google.com/')
  await page.type('input[name=q]', 'スカイツリー', { delay: 100 })
  await page.click('input[type="submit"]')
  await page.waitForSelector('h3 a')
  await page.screenshot({ path: 'screenshot/sample1.png' })

  await browser.close()
})()

以下の処理を行っています。

  • https://www.google.com/ にアクセスして、
  • スカイツリー の検索結果のスクリーンショットをとり、
  • screenshotディレクトリ に保存

実行します。

$ mkdir screenshot
$
$ node sample1.js
$
$ ls screenshot
sample1.png
620-javascript-puppeteer_sample1.gif

headless: falseとしたので、画面上で動作を確認することができます。

要素取得

PageクラスとElementHandleクラス

以下のメソッドを利用して、要素を取得できます。

メソッド Class: Page Class: ElementHandle 概要
evaluate × JavaScriptで処理できる。
$ 要素( ElementHandle )を取得。
内部で querySelector が利用されいている。
$$ 複数の要素( Array<ElementHandle> )を取得。
内部で querySelectorAll が利用されいている。
$eval JavaScriptで処理できる。
Elementプロパティ を利用できる。
$$eval JavaScriptで処理できる。
Elementプロパティ を利用できる。

利用例

要素取得の実装例を示します。

<div id="result">
  <div class="item">
    <a href="http://xxx" data-test="aaaa"></a>
    <p>xxxxxxxx</p>
  </div>
  <div class="item">
    <a href="http://yyy" data-test="bbbb"></a>
    <p>yyyyyyyy</p>
  </div>
  <div class="item">
    <a href="http://zzz" data-test="cccc"></a>
    <p>zzzzzzzz</p>
  </div>
</div>

上記HTMLから要素を取得する処理を実装します。

const puppeteer = require('puppeteer')
const path = require('path')

const testItem = async page => {
  console.log('############ 要素を1つ取得 ############')

  // ElementHandleクラスのインスタンスを取得
  const item = await page.$('#result div.item')
  console.log(await (await item.getProperty('innerHTML')).jsonValue())
  console.log('--------------------------------------')
  console.log(await (await item.getProperty('textContent')).jsonValue())
}

const testItems = async page => {
  console.log('############ 要素を1つ取得 ############')

  // Array<ElementHandle> を取得
  const items = await page.$$('#result div.item')
  console.log(items.length)

  for (const item of items) {
    // data属性取得
    console.log('--------------------------------------')
    console.log(await item.$eval('a', element => element.getAttribute('data-test')))

    // href取得
    const aTag = await item.$('a')
    const href = await aTag.getProperty('href')
    const url = await href.jsonValue()
    console.log(url)
  }
}

(async () => {
  const browser = await puppeteer.launch()
  // Pageクラスのインスタンスを取得
  const page = await browser.newPage()
  await page.goto(`file:${path.join(__dirname, '/test_html/sample.html')}`)
  await testItem(page)
  await testItems(page)
  await browser.close()
})()

実行結果は以下の通りです。

$ node sample2.js 
############ 要素を1つ取得 ############

    <a href="http://xxx" data-test="aaaa"></a>
    <p>xxxxxxxx</p>
  
--------------------------------------

    
    xxxxxxxx
  
############ 要素を1つ取得 ############
3
--------------------------------------
aaaa
http://xxx/
--------------------------------------
bbbb
http://yyy/
--------------------------------------
cccc
http://zzz/

サーバーレスで実行する方法

Cloud Functions

サポートされているようです。

https://cloud.google.com/blog/products/gcp/introducing-headless-chrome-support-in-cloud-functions-and-app-engine

AWS Lambda

Chromium が必要とする共有ライブラリがLambda上に存在しないので対策が必要です。

serverless-chrome というのを利用すると、Lambda上でも実行できます。

serverless-chrome を利用して、Lambda上で動かすための下準備をしてくださったリポジトリがあったので、こちらを活用してみると良いと思います。

https://github.com/sambaiz/puppeteer-lambda-starter-kit

参考・補足