Puppeteerでスクレイピング

JavaScript

静的サイトから 30 ページぐらいのテキストを抽出して json にしたかったため、
Puppeteer でスクレイピングをすることにした。

なぜPython3 + Selenium + BeautifulSoupじゃないのか

このブログがフロントエンドの備忘録のため。

Puppeteerとは

https://github.com/GoogleChrome/puppeteer
Chrome / Chromium を動かす Node のフレームワーク。
デフォルトは Headless(GUI を持たず、コマンドで操作できる)で、ブラウザを出しての操作も可能。

インストール

てきとうなプロジェクトを作成し、Puppeteer をインストール。

npm install puppeteer

とりあえず立ち上げる

公式に写真を撮るサンプルがあったのでそのまま貼ってみる。

const puppeteer = require('puppeteer')

(async () => {
  const browser = await puppeteer.launch()
  const page = await browser.newPage()
  await page.goto('https://example.com')
  await page.screenshot({path: 'example.png'})
  await browser.close()
})()

test.js で保存し、Node で起動。

node test.js

example.com のスクリーンショットが撮影できる。 これだけだとしょうもない。

puppeteerで撮影したexample.comのスクリーンショット

基本形

Nodeのバージョン

まず Puppeteer は Node のバージョンが 6.4.0 以上から対応している。
ただし非同期処理の都合上、async/await で書けた方がラクなので、バージョンは 7.6 以上が推奨されている。

最低限の処理

const browser = await puppeteer.launch()でブラウザを立ち上げ、
const page = await browser.newPage()でページを作成する。
このページを使って色々やる。

処理を終え次第、 browser.close() で自動的に閉じられる。 雛形はこんな感じになる。

const puppeteer = require('puppeteer')
(async () => {
  const browser = await puppeteer.launch() // 立ち上げ
  const page = await browser.newPage() // ページ作成
  await browser.close() // ブラウザ閉じる
})()

URLの指定

page.goto(url, options) を用いる。
url には url を入れ、オプションがあれば options を設定。
もっとも良く使われてそうなのが waitUntil オプション。
接続してから指定したパラメータのタイミングになるまで処理を待つ。

  • domcontentloaded: DOMContentLoaded(HTML リソースを読み込んだ)の時
  • networkidle0: 500ms 以内にネットワーク接続がなくなった時
  • networkidle2: 500ms 以内のネットワーク接続が 2 つ以下になった場合

デフォルトは load(ページの読み込みが完了)の時。
静的ページからテキスト取るなら DOMContentLoaded で問題ない。
動的ページなら、networkidle 系を用いないと完璧に取れない可能性がある。

const puppeteer = require('puppeteer')
(async () => {
  const browser = await puppeteer.launch()
  const page = await browser.newPage()
   // urlを開き、DOMContentLoadedまで待つ
  await page.goto('https://example.com', {
    waitUntil: 'domcontentloaded'
  })
  // ここに処理を書いていく
  await browser.close()
})()

ページの情報を取得してみる

page.title() で、現在ページのタイトルを返す。

console.log(await page.title())

要素を取得する

page.evaluate(function(){}) を使う。

example.com の This domain is... という概要みたいなのを抜き出してみる。
p 要素 2 つのうち 1 番目がその文章なので、 document.getElementsByTagName[0] を取る。

const summary = await page.evaluate(() => {
  return document.getElementsByTagName('p')[0].innerText
})
console.log(summary)

また、 page.$(element) という記述もある。
これは ElementHandle というオブジェクトが返る。
そこからまた取リ出すのが面倒なので、page.$eval(element, function(){}) を用いる。

const summary = await page.$eval('p', selector => {
  return selector.textContent
})
console.log(summary)

$ を 1 つ増やして、 page.$$eval(elements, function(){}) で一括指定可能。
これも document.getElementsByTagName 同様配列が返るので、[0] を取リ出す。

const summary = await page.$$eval('p', selector => {
  return selector[0].textContent
})
console.log(summary)

待つ

page.waitFor(number) で指定したミリ秒待たせる。
あまりガツガツアクセスすると怒られそうなので入れている。

await page.waitFor(1000)

ページ遷移の場合は、後述する waitForNavigation() で待たせることが可能。

移動する

page.click(element)a 要素を設定すると移動できる。
その後、 page.waitForNavigation() を実行することで、
ページ遷移を検知して待機してくれる。
waitForNavigation() が待機するのは URL の変更かリロードとある。

waitForNavigation()部分でタイムアウトが発生し上手く行かなかったので、
Promise.all の中に入れた。

await Promise.all([
  page.click('body > div > p:nth-child(3) > a'),
  page.waitForNavigation()
])
console.log(await page.title())

example.com のリンク先は IANA のサイトになるので、
"IANA — IANA-managed Reserved Domains" というタイトルが console.log() で表示される。

page.waitForNavigation() にもいくつかのオプションが設定可能で、その中には waitUntil もあるので、設定した方が良い。

viewportの変更

画面サイズのデフォルトは 800x600 だけど変えられる。
puppeteer.launch()に defaultViewport: {} を入れる。

const puppeteer = require('puppeteer')

(async () => {
  const browser = await puppeteer.launch({
    defaultViewport: {
      width: 200,
      height: 200
    }
  })
  const page = await browser.newPage()
  await page.goto('https://example.com')
  await page.screenshot({path: 'example_mini.png'})
  await browser.close()
})()

200 × 200 で撮影。

200×200のサイズでexample.comのスクリーンショットを撮影

isMobile(モバイル端末かどうか)、hasTouch(タッチをサポートするか)などのオプションもあった。

headlessの設定

デフォルトは headless での動作だけど、無効化できる。
puppeteer.launch()の引数に headless: false を入れることでブラウザを開く。

const browser = await puppeteer.launch({ headless:false })

実行すると Chronium が開く。

また、 devTools: true を入れても、自動的にブラウザが開く模様。
これは起動時にブラウザの開発者ツールを開いてくれる、開発者というか解析者向けのオプション。

const browser = await puppeteer.launch({ devTools:true })

また、 slowMo オプションを指定することで、指定したミリ秒処理を遅延させることができる。

const browser = await puppeteer.launch({
  headless: false,
  slowMo: 500
})

とりあえず json を作成できた。