HerokuにPuppeteerの実行環境を構築する
はじめに
本文は Node.js 2 Advent Calendar 2017 25日目の投稿です。
またクリスマス直前で寂しく記事を書いていますが、皆さんの役に立つ情報であれば嬉しいです。
主要なキーワード
Puppeteer
PuppeteerはGoogleが開発したChromeのヘッドレス版のNode.js APIです。
ややこしいと思いますが、Phantom.jsのChrome版と思っていただくとわかりやすいです。
Heroku
Herokuはsalesforce.com傘下のPaaS提供者であり、条件付きで無料のDocker Runtimeも提供しています。
Herokuの上にPuppeteer実行環境を展開
Herokuは公式でもNode.js実行環境の構築方法(英語)を書いていますが、その環境でPuppeteerをインストールすると、様々な不具合(例えば日本語フォントが消えるとか)が生じるという報告がありました。
嬉しいことは、この問題はすでに有志により解決はしました。
それで早速、試してみましょう。
まず、こちらのページを参考して、Herokuのアカウントにログインしましょう。
そして、アプリ用レポジトリを作りましょう。
$ mkdir MYAPP # ここは自分のappの名前です
$ cd MYAPP
$ git init
$ npm init -y
# Herokuプロジェクトの作成
$ heroku create MYAPP
$ git push heroku master
# メインビルドパックをNode.jsに設定
$ heroku buildpacks:set heroku/nodejs
# Puppeteer用追加パケージのインストール
$ heroku buildpacks:add https://github.com/CoffeeAndCode/puppeteer-heroku-buildpack
注意点は、puppeteer-heroku-buildpack
系のビルドパックはheroku/nodejs
という土台が必要で、直接は使えません。
Puppeteerのインストール
$ npm i puppeteer
上記コマンド叩くと、node_module
に最新のChromeがインストールされますので、少々時間かかります。
ローカルでPuppteerを動かしてみる
Googleさんのデモコードを少し変更してみました。
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://news.ycombinator.com', { waitUntil: 'networkidle2' });
const newsTitles = await page.evaluate(() => {
const elenemts = document.querySelectorAll('.itemlist .title > a');
return [].map.call(elenemts, el => el.innerText);
});
console.log(newsTitles.slice(0, 5));
await browser.close();
})();
実行してみると、https://news.ycombinator.com から、トップ5の記事のタイトルが戻ってきます。
$ node puppteer_demo.js
[ 'The Effect of Atmospheric Nuclear Testing on American Mortality Patterns [pdf]',
'How to Print Integers Really Fast',
'Papercraft with Blender',
'Karnaugh map',
'A preview of the U.S. without pensions' ]
ご覧のように、Phatom.jsと比べるとモダーンな構文で非同期処理が書けます。
PuppeteerをHerokuにデプロイ
Koa.jsでHeroku用入り口の追加
別にKoaにこだわることはありませんが、async/await
スタイルで統一したいのでこちらを使わせていただきます。
$ npm i koa koa-router koa-bodyparser
index.js
を用意し、下記のようにロジックを追加します。
const Koa = require('koa');
const Router = require('koa-router');
const bodyParser = require('koa-bodyparser');
const app = new Koa();
const router = new Router();
router.get('/', async (ctx, next) => {
ctx.body = 'Hello World';
});
app.use(router.routes());
app.use(router.allowedMethods());
app.use(bodyParser());
app.listen(process.env.PORT || 3000);
下記コマンドを実行すると、localhost:3000
でHello World
が表示されます。
$ node index.js
Puppeteer Launch Optionの修正
HerokuでPuppeteerを実行するには、少し工夫が必要です。
まず、真・ヘッドレス環境で実行する時、デフォルトのLaunch Optionだとエラーが発生します。
また、ローカルで動かす時、ヘッドレスよりも画面表示した方がデバグしやすいので、puppteer_demo.js
の一部修正して、index.js
に追記します。
const Koa = require('koa');
const Router = require('koa-router');
const bodyParser = require('koa-bodyparser');
const app = new Koa();
const router = new Router();
router.get('/', async (ctx, next) => {
ctx.body = await crawler(); // クローラーの実行
});
app.use(router.routes());
app.use(router.allowedMethods());
app.use(bodyParser());
app.listen(process.env.PORT || 3000);
// ここからはクローラーのロジック
const puppeteer = require('puppeteer');
// Heroku環境かどうかの判断
const LAUNCH_OPTION = process.env.DYNO ? { args: ['--no-sandbox', '--disable-setuid-sandbox'] } : { headless: false };
const crawler = async () => {
const browser = await puppeteer.launch(LAUNCH_OPTION); // Launch Optionの追加
const page = await browser.newPage();
await page.goto('https://news.ycombinator.com', { waitUntil: 'networkidle2' });
const newsTitles = await page.evaluate(() => {
const elenemts = document.querySelectorAll('.itemlist .title > a');
return [].map.call(elenemts, el => el.innerText);
});
await browser.close();
return newsTitles;
}
修正したindex.js
を実行すると、localhost:3000
でタイトル一覧が(json形式で)表示されます。
Herokuにプッシュ
Herokuの環境はProcfile
というファイルで本番環境で実行するコマンド記載する必要があるので、MYAPP直下で作ります。
web: node index.js
そしてHerokuのプロジェクトは結局Gitのレポジトリなので、gitでプッシュすれば更新されます。
$ git add .
$ git commit -m 'init crawler'
$ git push heroku master
Herokuにプッシュするたびに、実行環境が作り直されるので、完成まで待ちます。
環境の作成が完成できたら、下記コマンドを叩くとブラウザーが開き、https://MYAPP.herokuapp.com/
でタイトル一覧が(json形式で)表示されます。
$ heroku open
もし何か異常が出されたら、heroku logs --tail
でログが確認できます。
参考
https://devcenter.heroku.com/
https://elements.heroku.com/buildpacks/jontewks/puppeteer-heroku-buildpack
https://timleland.com/headless-chrome-on-heroku/