この記事はWebスクレイピング Advent Calendar 2017の17日目の記事です。puppeteerでクローリングします。
SPAみたいなちょっと凝ったWebサイトをクローリングするときは一昔前はSelenium + PhantomJSあたりが鉄板でしたが、今後はSelenium + Headless ChromeもしくはPuppeteer + Headless Chromeが主流となっていく見通しです。 (HeadlessとはGUIアプリケーションのGUIを介さないモードのことです。)
前者についてはWeb上に情報も多いですが、Puppeteerでクローリングする話をそんなに見ない気がするのでクローリングの歴史を踏まえてやってみます。
古くはサーバーでPerlなりPHPなりRoRなりが吐いた、あるいは静的に配信されたHTMLがえっちらおっちらとネットワーク越えて手元のブラウザにやってきて、それをクライアント側でよっこらしょとレンダリングするというのがほぼ唯一のWebサイトのアーキテクチャでした。現在も往時ほど支配的ではないにしても、このようなアーキテクチャのサイトが大半でしょう。
このアーキテクチャだとHTMLがそのままもらえるため、クローリングやスクレイピングをするときにはHTMLのことだけ考えていれば情報が取得できます。正規表現やパーサーでほぼすべての用がなせるでしょう。単純なテキスト処理プログラムだけで話が終わっていたため、このようなWebサイトのクローリングはだいたいメモリ消費量やCPU使用率に優れています。
ときは移ろい、Ajaxの誕生でサーバーはとりあえずHTML全部吐いて渡してくれるという常識が覆り、さらにはSPAの流行も手伝いWebサイトを巡回してコンテンツを取得するのにJavaScriptを処理する必要が出てきました。こうなると旧来型のHTMLを素朴に処理するだけでは対処しきれない場面が多く出てきます。
そこで、こういったアーキテクチャを採用するWebサイトに対してはSelenium + PhantomJS(ヘッドレスWebブラウザ、登場時は希少だった)はじめ自動化ツール+Webブラウザによるクローリングが一般化してきます。
そこからもろもろあってHeadless Chromeが登場します。デファクトだったPhantomJSはQtWebKitを利用した ブラウザのような何か で現実で使われているブラウザとは若干の違いがありました。それに対して、モバイルは言うに及ばず、デスクトップでも多大なシェアを誇るChromeを(GUIのオーバーヘッドなしに)自動化に使えたため、Headless Chromeに人気が集中します。後追う形でHeadless Firefoxも登場しています。
今後、WebテストやクローリングにはHeadless Chromeが支配的なポジションを占めていくのではないでしょうか。
現状は把握できたので早速手を動かしていきましょう。今回はSeleniumでもPuppeteerでもどっちでもよかったのですが、Headless Chrome向けで使うのに手間の少ないPuppeteerを使うことにします。
npm install puppeteer
がつづりが難しくて最大の難関になっていますが、特にブラウザを別途取得したりする必要もなくサクサク書けます。
Chromeの開発者向けサイトから記事一覧、記事の画像を取得する操作を例に一通り書いてみます。(API参照)
// main.js
// npm install puppeteer@0.13.0
const pptr = require('puppeteer');
async function run(){
// ブラウザを起動する
const browser = await pptr.launch({
args: ['--lang=ja,en-US,en'] // デフォルトでは言語設定が英語なので日本語に変更
})
// ページつくる
const page = await browser.newPage()
// サイズ決める
await page.setViewport({ width: 720, height: 600 })
// Chromeの開発者向け資料Webサイトに移動
await page.goto('https://developer.chrome.com/home/platform-pillar')
// ページタイトル取得
console.log(await page.title())
// スクリーンショット取っちゃう
await page.screenshot({path: 'sample1.png', fullPage: true})
// ページ遷移してスクリーンショット取る
await page.click('article a:not([href^=http])') // セレクタ指定してクリックだけと楽
await page.screenshot({path: 'sample2.png', fullPage: true})
// 終了
await browser.close()
}
run() //node main.jsで実行
SPAでもやってみましょう。Angularの公式サイトで検索してみます。
const pptr = require('puppeteer');
async function run(){
const browser = await pptr.launch({})
const page = await browser.newPage()
await page.setViewport({ width: 640, height: 720 })
await page.goto('https://angular.io')
console.log(await page.title())
await page.type('input[aria-label=search]', 'cli') // 要素に文字列cliを入力
await page.waitFor(5000) // sleep
await page.screenshot({path: 'sample3.png'})
await browser.close()
}
run()
SPAもほとんど何も考える必要もなくばっちりです。 そのままだと早すぎて検索結果が返ってこないので今回はwaitを入れました。
2例とも大したコードではないので魅力や使い勝手は伝わりづらいかもしれませんが、ブラウザ操作の自動化に関して気にするところが少なくていいのはありがたいところです。 Puppeteerを使うとクローリング回り特に意識せず雑に書けるのは大きな魅力ではないでしょうか。
今回は紹介しませんでしたが、かなり自由度の高い操作ができる強力さも魅力です。
クローリングで何を使おうか迷ったときはPuppeteerから始めるとJavaScriptを多用するページに特に考えずに対応できるためおすすめです。
puppeteerにはpyppeteerという有志のPythonポートがあります。クローリング、スクレイピングといえばPythonみたいなところもあるし、こっちも要チェックですね。
miyakogi/pyppeteer https://github.com/miyakogi/pyppeteer
- --headless時代の本命? Chrome を Node.jsから操作するライブラリ puppeteer について
- Puppeteerの基礎知識をしっかりまとめつつ、CIでの実行なども考慮していて実践的な内容です。
- puppeteerでフロントエンドISUCONのためのパフォーマンス計測ツールを作りたい
- Puppeteerの高度な利用法についてわかりやすく提案してくれています。
- Pythonクローリング&スクレイピング
- クローリングやスクレイピングの用語他、相当部分この本を参考にしました。ダイレクトマーケティングですが買いましょう。
- apiドキュメント https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md
- そろそろ1.0.0が出て結構APIが変わります。今回のサンプルも動かなくなるはず...。
- yujiosaka/headless-chrome-crawler
- Puppeteerもとにクローリング向けに使いやすくしたライブラリ