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 のスクリーンショットが撮影できる。 これだけだとしょうもない。
基本形
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 で撮影。
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 を作成できた。