CasperJSでブラウザ操作を自動化しよう

[NIJIBOX Advent Calendar](http://qiita.com/advent-calendar/2016/nijibox) の13日目の記事です。

# はじめに
業務の中にはWEBサイトで毎日手作業で行うルーチンワークもあるかと思います。

簡単な作業でも、ブラウザを開いて、フォームに何かを入力して、ページ遷移するなど
毎回やっていたら面倒なことも、プログラムにしてしまえばコマンド１発で済んでしまいます。

そんな操作をプログラム化できるCasperJSの使い方を覚えたので、まとめて見ました。

# 導入編
インストールから簡単な使い方までの紹介です。実行環境はmacを想定しています。
## CasperJSのインストール
[公式サイト](http://casperjs.org)からダウンロードできます。npmがあれば以下のコマンドで簡単に導入可能です。

```
sudo npm install -g phantomjs
sudo npm install -g casperjs
```

一応上記のコマンドについて補足すると、PhantomJSとCasperJSをグローバルにインストールしています。
PhantomJSはコマンドラインから実行するWEBブラウザで、CasperJSを実行するため必要になります。

## 基本的なプログラム
以下は、Googleを開いてページのタイトルを表示させるプログラムです。

```js:sample.js
var casper = require('casper').create();

casper.start(); //開始

casper.open("https://www.google.co.jp/"); //URLを開く

/*　実行したいステップをthenで追加する　*/
casper.then(function() {
  this.echo(this.getTitle()); //thisはcasperインスタンスを指す
})

casper.run(); //実行
```

runが実行されると、ヘッドレスブラウザ(GUIがないコマンドライン用のブラウザ)が立ち上がり
Googleのページが開かれ、その後にタイトルを取得してコンソールに出力します。

## 実行する
コマンドラインで`casperjs`と入力してファイル名を指定して実行します。

```
$ casperjs sample.js

Google
```

Googleと表示されたら成功です。が、あまり動いている感じがしないので
`--verbose --log-level=debug`オプションをつけて動作を確認してみましょう。

```
$ casperjs --verbose --log-level=debug sample.js

[info] [phantom] Starting...
[debug] [phantom] opening url: https://www.google.co.jp/, HTTP GET
[debug] [phantom] Navigation requested: url=https://www.google.co.jp/, type=Other, willNavigate=true, isMainFrame=true
[info] [phantom] Running suite: 1 step
[debug] [phantom] url changed to "https://www.google.co.jp/"
[debug] [phantom] Successfully injected Casper client-side utilities
[info] [phantom] Step anonymous 1/1 https://www.google.co.jp/ (HTTP 200)
Google
[info] [phantom] Step anonymous 1/1: done in 1877ms.
[info] [phantom] Done 1 steps in 1896ms
[debug] [phantom] Navigation requested: url=about:blank, type=Other, willNavigate=true, isMainFrame=true
[debug] [phantom] url changed to "about:blank"
```

上記のオプションをつけるとデバッグログが表示されるようになるので、
ブラウザの動きがわかりやすくなるかと思います。必要に応じて使い分けてください。

## Casperクラスのメソッドまとめ（簡易版）
以下は、自分がよく使うメソッドのアバウトなまとめです。詳しくは公式の [APIドキュメント](http://docs.casperjs.org/en/latest/modules/index.html) を見ると良いです。

|メソッド|用途|
|:--|:--|:--|
|then()|超重要。同期処理としてステップ実行したい手続きを登録します|
|caputure()|表示しているページの画像をキャプチャして保存できます|
|evaluate()|表示しているページに対してDOM操作を行うときに必須です|
|fill()|フォームに値を入力したり、そのまま送信したい時に使います|
|viewport()|ブラウザのビューポートを設定します。キャプチャ撮る時とか|
|userAgent()|ユーザーエージェントを偽装する時に使います|

かなりざっくりとしたニュアンスなので、間違っていたらゴメンなさい :sob:

# 実践編
ここから実際のソースコードでCasperJSを使った自動化の例を紹介します。

## Google検索で上位のデータを取得する
検索エンジンで特定ワードを検索して、結果をスクレイピンングして表示させる例です。
デフォルトだとリンクの内容がうまく取得できなかったので、ユーザーエージェントを偽装しています。

```js
var casper = require('casper').create();
var links = null;

casper.start();

/* UserAgentをiOS10のブラウザに設定 */
casper.userAgent('Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_1 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) Version/10.0 Mobile/14A403 Safari/602.1');

/* NIJIBOXでGoogle検索 */
casper.thenOpen("https://www.google.co.jp/", function(){
  this.fill('form', {'q':'NIJIBOX'}, true);
});

/* 実行結果をスクレイピングしてぺージ名とリンクを取得する */
casper.then(function() {
  links = this.evaluate(function(){
    var q = document.querySelectorAll('h3 > a'); //データが入っているタグを抽出
    return Array.prototype.map.call(q, function(e){
      return '[' + e.textContent + '](' + e.href + ')'; //ページ名とリンクだけを抜き出す
    });
  });
});

/* 引数に関数を渡すと完了後の処理が書ける */
casper.run(function(){
  for(var index in links) {
    this.echo(links[index]); //取得したページ名とリンクを表示
  }
  this.exit();
});
```

検索ワードはプログラム中に埋め込んでいますが、動的にすると良いと思います。
以下は、実行結果です。

```
$ casperjs google_search.js
[株式会社ニジボックス](http://nijibox.jp/)
[ABOUT](http://nijibox.jp/company/)
[SERVICE](http://nijibox.jp/service/)
[Webサイト制作／運用](http://nijibox.jp/service/web/)
[新規事業開発](http://nijibox.jp/service/new/)
[ゲーム開発/運用](http://nijibox.jp/service/game/)
[Nijibox engineer blog](http://tech.nijibox.jp/)
[お問い合わせ](http://nijibox.jp/contact/)
[Webサイト制作／運用](http://nijibox.jp/service/web/)
[新規事業開発](http://nijibox.jp/service/new/)
[株式会社ニジボックス 採用サイト](http://nijibox-recruit.jp/)
[避難所＠二次裏](http://www.nijibox.com/futaba/)
[株式会社ニジボックス（NIJIBOX Co.,Ltd.） | Facebook](https://m.facebook.com/nijibox/)
[株式会社ニジボックス - Qiita](https://qiita.com/organizations/nijibox)
[Monaca導入事例：株式会社ニジボックス](https://ja.monaca.io/showcase/nijibox.html)
[#NIJIBOX - Twitter Search](https://twitter.com/search?q=%23NIJIBOX)
```

## 指定したURLの画面をキャプチャしてダウンロードする
とあるサイトの表示の魚拓を取りたいなどという事があるかも知れません。以下は、そのプログラムの例です。

```js:capture_url.js
var casper = require('casper').create();

var args = casper.cli.args; //コマンドラインの引数を受け取る
var url = args[0];

if(url) {
    /* 開始と同時にURLのページを開き、処理を実行させる */
    casper.start(url, function() {
        this.viewport(1280, 1024); //画面サイズを指定
        this.capture('test.png'); //ページをキャプチャしてtest.pngに保存
    });
    casper.run();
} else {
    casper.echo('引数にURLを指定してください', 'ERROR');
    casper.exit(); //CasperJSを終了
}
```

今回は、コマンドライン引数を使ってキャプチャしたいページを指定できるようにしてあります。
以下は実行結果の例ですが、CasperJS公式サイトのページ画像が `test.png` として保存されます。

```
$ casperjs capture_url.js http://casperjs.org/
```

## Twitterにログインしてツイートする
本来ならAPIを使うべきですが、認証が必要なページにログイン ＋ フォームを送信する例 として紹介します。

```js:tweet.js
var casper = require('casper').create();

var id   = 'hogehoge@mailaddress'; //ログインID
var pass = 'password'; //パスワード

casper.start('https://twitter.com/login', function(){
  /* セレクタで取得したフォームにアイパスを設定して送信させる */
  this.fill('#page-container form', {
      'session[username_or_email]': id,
      'session[password]': pass
  }, true);
});

/* URLが切り替わるまで処理を待機させてから実行する */
casper.waitForUrl('https://twitter.com/', function(){
  /* ログイン後にツイートページを開く */
  this.thenOpen('https://twitter.com/intent/tweet', function(){
    /* フォームに入力してツイート */
    this.fill('#update-form', {
        'status': 'CasperJSのテスト投稿です'
    }, true); 
  });
});

casper.run(function(){
  this.echo('投稿完了', 'INFO'); //第二引数で色付き文字を指定できる
  this.exit();
});
```

TwitterにID・パスワードを入力してログイン、さらにその後ツイート投稿ページへ遷移して
フォームに定型文を入力してツイートしています。（ID・パスワードは自分のものに変えてください）

```
$ casperjs tweet.js

投稿完了
```

![test 22.39.59.png](https://qiita-image-store.s3.amazonaws.com/0/101638/cee8ee7f-9c9a-4624-a19c-7df03d8047dd.png)

# おわりに
いかがでしたでしょうか。CasperJSを使えば、あんなことやこんなこともできそうですね。
本当はもっと具体的な例をあげたかったのですが、グレーな部分もありそうなので自重しました。

完成したスクリプトはcron等のタスクスケジューラーに登録しておくと自動化が捗りそうですね :slight_smile: