PhantomJS + Node.js + Docker + Arukas で Web ページのスクリーンショットを撮る

PhantomJS + Node.js + Docker + Arukas を使って、Web ページのスクリーンショットを撮る Web サービスを作ってみる。

サービス仕様

HTTP でリクエストを待ち受け、ユーザからリクエストが来ると、指定された URL のスクリーンショットを撮影し、ユーザに返答する。

f:id:akagisho:20170226082403p:plain

環境

開発環境

Mac OS 10.11

本番環境

Ubuntu 16.04 Server on Docker on Arukas

使用するもの

PhantomJS

ヘッドレスなウェブブラウザ。スクリーンショットを撮るのに使用する。

http://phantomjs.org/

Node.js

サーバサイドで動く JavaScript 環境。Web サーバを上げるのに使用する。

http://nodejs.jp/nodejs.org_ja/

Docker

軽量な仮想環境。今回作るアプリケーションは Docker のコンテナにする。

https://www.docker.com/

Arukas

Docker コンテナのホスティングサービス。さくらインターネットが提供しており、現在はβ期間中につき無償である。

https://arukas.io/

実装

スクリーンショットの撮影

まず PhantomJS をインストールする。Mac なら brew でインストールできる。

$ brew install phantomjs
$ phantomjs -v
2.1.1

PhantomJS の Quick Start を参考に、次のサンプルコードを作成する。ファイル名は yahoo.js とする。

var page = require('webpage').create();
page.viewportSize = { width: 800, height: 600 };
page.clipRect = { top: 0, left: 0, width: 800, height: 600 };
page.open('http://www.yahoo.co.jp/', function (status) {
  console.log('Status: ' + status);
  if (status === 'success') {
    page.render('yahoo.png');
  }
  phantom.exit();
});

作った js ファイルを実行する。

$ phantomjs yahoo.js
Status: success

すると、次のようにページのスクリーンショットが撮れる。

f:id:akagisho:20170225102013p:plain

Web サーバの起動

Node.js と Web フレームワークExpress を使って Web サーバを上げる。

まず Node.js をインストールするために、同じく brew で nodebrew をインストールする。

$ brew install nodebrew
$ echo 'export PATH=$HOME/.nodebrew/current/bin:$PATH' >> ~/.bashrc
$ . ~/.bashrc
$ mkdir -p ~/.nodebrew/src

nodebrew で最新版の Node.js をインストールする。

$ nodebrew install-binary stable
$ nodebrew ls
v7.6.0

current: none
$ nodebrew use v7.6.0
$ node -v
v7.6.0

次に npm コマンドでプロジェクトを作成して、Express パッケージをインストールする。

$ mkdir myapp
$ cd myapp
$ npm init
(enter 連打)
$ npm install express --save

Express の「Hello World」の例 を参考にして、Express で Web サーバを起動するプログラム app.js を作成する。

var express = require('express');
var app = express();

app.get('/', function (req, res) {
  res.send('Hello World!');
});

app.listen(3000, function () {
  console.log('Example app listening on port 3000!');
});

作ったプログラムを実行する。

$ node app.js
Example app listening on port 3000!

3000番ポートで Web サーバが上がるので、別のターミナルでアクセスしてみる。

$ curl -i http://localhost:3000/
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 12
ETag: W/"c-7Qdih1MuhjZehB6Sv8UNjA"
Date: Sat, 25 Feb 2017 07:51:16 GMT
Connection: keep-alive

Hello World!

アクセスできた。

webshot の利用

PhantomJS のラッパ webshot を使ってスクリーンショットを撮る。画像は Base64 エンコードして返すようにする。

先ほどと同様に npm で webshot をインストールする。

$ npm install webshot --save

app.js を次のように書き換える。Node.js は非同期で実行されるので、res.end(); を送るタイミングがポイントとなる。

var express = require('express');
var webshot = require('webshot');

var app = express();

app.get('/', function (req, res) {
  var options = {
    screenSize: { width: 800, height: 600 }
  };
  var rs = webshot(req.query.url, options);
  rs.on('data', function (data) {
    res.write(data.toString('base64'));
  });
  rs.on('end', function () {
    res.end();
  });
});

app.listen(3000, function () {
  console.log('Example app listening on port 3000!');
});

同じく app.js を実行する。

$ node app.js
Example app listening on port 3000!

アクセスしてみる。

$ curl 'http://localhost:3000/?url=http://www.goo.ne.jp/' \
    | base64 -D > goo.png

指定した URL のスクリーンショットが撮れている。

f:id:akagisho:20170225175100p:plain

Docker コンテナ化

Node.js アプリケーションを Docker のコンテナ上で動かす。Docker はあらかじめダウンロードしてインストールしておく。

今回は「できあがったものがこちらです」的に、Hiroshi K. さんが作成したより高機能なアプリを使うこととする。Node.js の起動には supervisord が使われている。

Docker イメージの作成には Dockerfile を使用する。今回の場合、おおまかに次のような流れになる。

  • FROM でベースとなるコンテナを作成する
  • RUN で必要な apt パッケージと Node.js、PhantomJS をインストールする
  • RUNGitHub からアプリを clone し、必要な npm パッケージをインストールする
  • EXPOSE でポートをコンテナの外部に開く
  • ENTRYPOINT で supervisord を起動する

完成した Dockerfile は gist に置いておいた。

この Dockerfile を使ってコンテナをビルドしてイメージ化する。

$ wget -O Dockerfile https://gist.githubusercontent.com/akagisho/466374d7dda0d9bf55321b3b554e91f9/raw/Dockerfile
$ docker build -t akagisho/snapshot .

ビルドした Docker イメージからコンテナを起動する。

$ docker run -d -p 3000:3000 akagisho/snapshot

localhost の3000番ポートにアクセスすると、Base64 デコードされたスクリーンショットが返ってくる。

$ curl -XPOST --data 'target=http://www.google.com/' http://localhost:3000/ \
    | base64 -D > google.png

Arukas にデプロイ

さくらインターネットが提供する Docker コンテナホスティングサービスの Arukas に、作成したコンテナをデプロイしてみる。

Arukas は現在はβ期間中につき無償とのこと。アカウント発行まで2週間くらいかかるので、登録は事前にやっておく。

Arukas でコンテナを使うには、あらかじめコンテナを Docker Hub に登録しておく必要があるので、Docker Hub のアカウントもあらかじめ登録しておく。

Docker Hub のアカウントを登録したら、ログインする。

$ docker login

先に作成した Docker イメージを Docker Hub に push する。

$ docker push akagisho/snapshot

Docker Hub に push したイメージを Arukas に登録する。

f:id:akagisho:20170225230404p:plain

登録したアプリケーションを起動する。

f:id:akagisho:20170225230606p:plain

初回はデプロイに10分弱くらいかかる。デプロイが完了するとアプリケーションが起動する。

f:id:akagisho:20170225231203p:plain

起動したアプリの Endpoint の URL にアクセスしてみる。

$ curl -XPOST --data "target=http://www.ugtop.com/spill.shtml" \
  https://sleepy-euler-3682.arukascloud.io/ \
  | base64 -D > screenshot.png

スクリーンショットが保存されれば成功だ。

なおこのアプリケーションは認証等がなく、URL さえわかれば誰でも使用できてしまうので、必要ないときは停止しておく。

P.S.

既に同じようなことをやってる人がいた。