PhantomjsでCUIからWEB画面キャプチャ


経緯

サーバに用意したWEBアプリのステータス把握のためにWEBアプリが提供しているステータス把握用のAPIからjsonデータをもらってパースして加工してスマホに通知(メール)するみたいなことをしているのですが、それよりWEBアプリのステータス画面をキャプチャしてそれを通知すれば手間が省けて幸せだなと思ったわけです。

Phantomjs?

http://phantomjs.org/
Pphantomjsはヘッドレスブラウザで、jsの記法でdocumentを扱えて大変便利。

ヘッドレスブラウザ?

CUI用のブラウザ。面倒なWEBのフロントエンドのテストやスクレイピングなんかを行うのに利用されたりします。

Install

環境はMacです。

$ brew install phantomjs
$ phantomjs -v
2.1.1

使い方

http://phantomjs.org/quick-start.html
上記に解りやすく書かれています。

とりあえずHello, Worldやってみます。

hello.js
console.log('Hello, world!');
phantom.exit();
bash
$ phantomjs hello.js
Hello world!

シンプルですね。
ほんのちょっと実行に時間かかった気もしますが恐らくブラウザの処理を行っているから(自分のマシンスペックによるところかもしれませんが。。。)でしょうかね。

キャプチャ

さて今回やりたいのはWEB画面のキャプチャです。
そして嬉しいことにphantomjsには最初からキャプチャ機能が組み込まれています!

普通にキャプチャ

というわけドキュメントからまんまコピペ。
対象をYahooさんに

capture.js
var page = require('webpage').create();
page.open('http://yahoo.co.jp', function(status) {
  console.log("Status: " + status);
  if(status === "success") {
    page.render('example.png');
  }
  phantom.exit();
});
command
$ phantomjs capture.js 
Status success
$ ls -l
...
-rw-r--r--@ 1 xxxxxxxxx  xxxx   875661  7  6 18:35 example.png
...

$ open example.png

キャプチャしたのがこちら。

いい感じです!

時間差でキャプチャ

あとはキャプチャした画像を通知しておしまい!
と思っていましたが実際にWEBアプリのキャプチャを取ろうとして落とし穴が。

なぜか画面の一部が正しく表示されません。
理由は至極単純で、HTMLレンダリングが終わってからAjaxなどで通信してる部分を取得される前にキャプチャしてしまったからです。

bingのサイトで再現してみます。
普通にキャプチャした場合

通常は背景なども出てくるのですが出ませんね。

では時間差で取得してみます

capture2.js
var page = require('webpage').create();
page.open('https://www.bing.com/', function(status) {
  console.log("Status: " + status);
  // 5000ミリ秒後にキャプチャ。
  setTimeout(function(){
    if(status === "success") {
       //screen capture
       page.render('5000ms.png');
     }
     phantom.exit();
  }, 5000);
});
command
$ phantomjs capture2.js
Status success
$ open 5000ms.png

綺麗に背景などが出てきて普段見ているページになりました!

クリックした結果を見たい!

次からは蛇足ですが、
yahooページの中央部にあるニュースのカテゴリーのように、直接URLで表示できないようなものってありますよね?
そういうところをキャプチャする場合は擬似的にクリックイベントを行うことで対処できるようです。

ドキュメントを参考に下記のように作りました。
http://phantomjs.org/page-automation.html

capture3.js
var page = require('webpage').create();

page.open('http://www.yahoo.co.jp/', function(status) {
  console.log('Status: ' + status);
  setTimeout(function(){

    // jquery 古い・・・
    page.includeJs('http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js', function() {

      page.evaluate(function() {

        // 経済のタブをクリック
        // (idやDOM構造が変わっている場合はjQueryの文法に沿ってセレクタの変更を行う必要があります。)
        $('#economy')[0].click();

      });

      setTimeout(function(){

        //screen capture
        page.render('yahoo.png');
        phantom.exit();

      }, 1000);

    });

  }, 1000);
});
command
$ phantomjs capture3.js
Status success
TypeError: undefined is not an object (evaluating 'i.innerHTML.replace')

  http://www.yahoo.co.jp/javascript/fp_base_bd_ga_6.0.73.js:1 in loadPanel
  http://www.yahoo.co.jp/javascript/fp_base_bd_ga_6.0.73.js:1 in tabAction
  http://www.yahoo.co.jp/javascript/fp_base_bd_ga_6.0.73.js:1
TypeError: undefined is not a constructor (evaluating 'h.removeChild($("srchAssistLists"))')

  http://www.yahoo.co.jp/javascript/fp_base_bd_ga_6.0.73.js:1 in ytopAssist

$ open yahoo.png

何かエラー出てるけどyahooさん側のエラーっぽい?とりあえずスルー。

分かりづらいかもしれませんがニュースのカテゴリーが経済に変わっています!

ここでポイントなのは二度のsetTimeoutです。
非同期処理なので上記のようにしないとクリックする前にキャプチャしてしまうので注意です。

まとめ

Phantomjsのおかげでびっくりするくらい簡単にキャプチャ画像の通知ができてほくほくです。
スクレイピングもjqueryなどを利用して行えますし、簡単にいろいろできそうです。

ただ悪用はダメ絶対。