SpookyJSでJavaScript有効状態のページをスクレイピング

SpookyJSは、PhantomJSというヘッドレスブラウザを使って色々と便利なことをするラッパーであるCasperJSを、Node.jsスクリプトから操作するためのNodeモジュールです (ちょっとややこしい)。

https://github.com/SpookyJS/SpookyJS

CasperJSはシングルページアプリの自動テストや、JSでレンダリングされるページのスクレイピングをするのに便利ですが、Node.js特有の機能を有するモジュールは動作しないようです。

SpookyJSからCasperJSを使うことで、Cheerio等の外部のNodeモジュールと連携できますし、また逆にCasperJSによってスクレイピングした結果をexportし、他のNodeスクリプトからrequireすることもできたりと、何かと便利そうなので今回試してみることにしました。

### インストール

SpookyJSを使うにはPhantomJSとCasperJSが必要です。全てnpmでインストールしてもいいですが、僕が使っているArch Linuxの公式リポジトリにPhantomJSが置いてあったので、なんとなくPhantomはArchのパッケージマネージャでインストールし、他はnpmでローカルに入れてみました。

```
$ sudo pacman -S phantomjs
$ cd (プロジェクトルート)
$ npm i casperjs spooky
```

あと何故かnpmでphantomjsをインストールしている環境だと``tiny-jsonrpc``が無いと怒られたので、その場合は `` $ npm i tiny-jsonrpc`` してください。

### サンプル

Qiitaのトップページのタイトルを取得するサンプルです。これが最小構成だと思います。

```js
var Spooky = require('spooky');

var options = {
  child: {transport: 'http'},
};
var spooky = new Spooky(options, function(err) {
  spooky.start('https://qiita.com/');
  spooky.then(function() {
    this.emit('p', this.getTitle());
  });
  spooky.run();
});

spooky.on('p', function(msg) {
  console.log(msg);
});
```

ポイントは下記の2点かと思いました。

1.  ``spooky.then``内はCasperJSが作っている独自のJavaScript環境なので外部の変数を参照できないし、Node.js特有の機能は動作しない
2.   ``spooky.then``内で得たデータは``this.emit``でイベントをトリガーして外部に渡す。

試しに``spooky.then``の部分を以下のように書き換えてみます。

```js
  var hello = 'hello?';
  spooky.then(function() {
//    this.emit('p', this.getTitle());
    this.emit('p', hello);
  });
```

すると``ReferenceError: Can't find variable: hello``と怒られます (エラー出力するにはそのためのイベントリスナーが必要)。

一応``spooky.then``内で``console.log``したものをコンソールに表示させる方法もありますが、読み込んだサイト内のスクリプトによって``console.log``で出力されたものまで表示してしまうため、スクレイピングが目的であればイベントリスナーで取る方がよいかと思います。

### Ajaxで表示しているコンテンツの取得

私事ですが、現在転職活動中ということで、お世話になっているGreenという転職サイトのトップページに表示される掲載求人数を取得してみます。求人数はAjaxで読み込んでいるのでJSオフだと表示されませんがSpooky (CasperJS) なら問題ありません。

```js
var Spooky = require('spooky');

var options = {
  child: {transport: 'http'},
};
var spooky = new Spooky(options, function (err) {
  spooky.start('https://www.green-japan.com/');
  spooky.then(function() {
    var client_count = this.evaluate(function() {
      return document.querySelector('#job_count').innerText;
    });
    this.emit('p', client_count);
  });
  spooky.run();
});

spooky.on('p', function (msg) {
  console.log(msg);
});
```

``evaluate``はCasperJS固有のAPIで、ブラウザでページを開いてデベロッパーツール上でJSをインスタント実行していることと同等になるようです。

### Nodeモジュールの利用

良い例が思い浮かばないのでかなり強引ですが、例えば特定の要素の取得を``querySelector``では無く、Cheerio等の外部のパーサーで行うとします。

その場合は下記のように``this.emit``でCasperJS領域の外側に飛ばしてパースできます。

```js
var Spooky = require('spooky');
var cheerio = require('cheerio');

var options = {
  child: {transport: 'http'},
};
var spooky = new Spooky(options, function (err) {
  spooky.start('http://qiita.com/');
  spooky.then(function() {
    this.emit('body', this.getHTML());
  });
  spooky.run();
});

spooky.on('body', function (body) {
  var $ = cheerio.load(body);
  console.log($('title').text());
});
```

``getHTML``はページのHTML全体を返すCasperJSのAPIです。他にも特定のDOM要素をクリックさせる``click``や、ページ全体のキャプチャをpngで出力する``capture``等、CasperJSには便利なAPIがたくさんあります。詳しくは下記のドキュメントページをご参照ください。

以上、ほぼCasperJSの話になってしまいましたが、Node.jsからCasperJSを簡単に扱う場合のサンプルとしてここに記録しておきます。

### 参考ページ
API Documentation &mdash; CasperJS 1.1.0-DEV documentation
http://docs.casperjs.org/en/latest/modules/

SpookyJS/SpookyJS: Drive CasperJS from Node.js
https://github.com/SpookyJS/SpookyJS