読者です 読者をやめる 読者になる 読者になる

はぐれエンジニアの雑記場

エンジニアぽいけどエンジニアな生き方してない人のゆるいブログ

CasperJSめも

CaperJSを触り始めたのだが、まとまってる記事がそんなにない。徒然なるメモをここに。

そもそも論

CaperJSはNode.jsのスクレイピングモジュールでありスクレイピングツールである。スクレイピングツールじゃなくてテストツールかも知れないが、スクレイピングに使えるのは間違いない。

また、Node.jsのモジュールなので当然Javascriptで書く。

CasperJSはPhantomJSというヘッドレスブラウザをハンドルする。ヘッドレスって何かというと、GUIがないブラウザ。かといってLynxとかW3mとかみたいなCUIブラウザとも違う。JavaScriptも基本実行してくれたりとレンダリング処理は内部で走るのだが、ユーザに表示する画面を持たない。だから、うまいことPhantomJSをハンドルすれば、「あそこのここをクリックして表示されたページのここんとこのこのテキストを抽出!」とかを自動化できたりする(雑)。

導入

CasperJSはnpmパッケージにある。nodejsとnpmは先に入れておくこと。

Macbrewが入ってるなら下記で両方導入できる。

$ brew install nodejs

ubuntuなどのdebian系でもapt-getで可能だが、npmも指定する必要があったはず(下記)

$ sudo apt-get install nodejs npm

まぁ普通にnode.jsのページからダウンロードするのでもいい。Windowsだとこれがお手軽(というか他は知らない)。

Node.js

node.jsを導入してnpmコマンドが使えるようになったら、 下記コマンドでCasperJSをインストールする。

$ npm install -g casperjs

なお、カレントディレクトリのみにインストールしたい場合は、-gオプションをつけないこと。

PhantomJSの導入

PhantomJSもnpmでインストールできるはず。

$ npm install -g phantomjs

ただ、筆者のlubuntu環境ではうまく行かなかった。(windowsMacでは大丈夫だった) だが、普通にapt-getでインストールできた(下記)。

$ sudo apt-get install phantomjs

一応補足で、PhantomJSは単体でインストールできる。PhantomJSはCasperJSのものではなく、Seleniumなどのスクレイピングツールでも利用される。というかむしろCasperJSがPhantomJSしか使えない。いや、正確にいうと他にも使えるのだが…そこらへんは知識がない。

というかCasperJS使うと、いちいちthenの中にfunctionとか書かなきゃで面倒だし今CasperJSの制御なのかPhantomJSの制御なのか意味不明になるしそもそも2つの間で共通でないObjectとかあったりしてややこしさ極まりないので素直にSeleniumで書くべきだと思う。

げふんげふん。

とりあえず動くもの

習うより慣れろが一番早い習得方法だと思ってる。

はてなブログの、おすすめ記事一覧を取得するサンプルコード(ファイル名sample.js)。

var url = "http://hatenablog.com/";
var useragent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36";

var casper = require('casper').create();

casper.userAgent(useragent);
casper.start(url, function() {
    var ret = casper.evaluate(function() {
        /////////// ここからPhantomJS内でのコンソール実行だと思うといい //////////////
        var recommends = document.querySelectorAll(".serviceTop-entry-title > a");
        return Array.prototype.map.call(recommends, function(self){ return self.text });
        //////////////////////////////////////////////////////////////////////////////
    });

    casper.echo("<<< おすすめ記事一覧 >>>");
    casper.eachThen(ret, function(_ret) {
        casper.echo(_ret.data);
    });
});


casper.run();

実行

$ casperjs sample.js

<<< おすすめ記事一覧 >>>
『友人の家でのえ?』な話
今までで面白かったナンパ、怖かったナンパ厳選まとめ
ZOZOTOWN(ゾゾタウン)がツケ払いOKに!今後は洋服や靴を買ってから最大2ヶ月間、支…
人間の闇を掘り尽くすNHKの「ねほりんぱほりん」が凄い
2016秋クールのアニメ&ドラマがおもしろすぎてやっぱりテレビは最高
涼宮ハルヒ、29歳。
これから先10年、フロントエンドに関する予言
グッド・モーニング
あなたはこの文章を読んで「こいつめんどくせえな」と思う
これから先10年、フロントエンドに関する予言
プログラミング学習日記 PHP編2-3日目 -ゲストブック作成-
PHPカンファレンス2016で発表してきました #phpcon2016
git/githubのマイナーな便利コマンド
風邪をひきやすい時期は、鍋で体を温めよう!
weekend Ramen|牡蠣と四方竹の中華炒めあんかけ醤油ラーメン
砂糖・バター不使用 はちみつヨーグルトケーキ
なすのミートソースグラタン。
システム手帳論考9_【スピンオフ】
男性の失業で起きるお金以外の4つのデメリットとは
「ソフトウェアテスト勉強会~テスターと創る開発現場~」感想
10月のブログ・クラウドソーシングの収入と今後の目標など
小学校高学年に読んでほしい50冊。いや、「子どもと一緒に読みたい本」。
たった4ヶ月でTOEIC350点から日常英会話が出来るようになった英語勉強方法
【お題】 まだヒートテックなんかで冬を過ごしているの?

ざっくり解説

var casper = require('casper').create();

CasperJSのモジュール読み込み。createでコンストラクタ的なことやってくれると思えばいい。

createの引数にユーザエージェントやタイムアウトなどの設定も書ける。例えばユーザエージェントを指定する場合

var casper = require('casper').create(
    {
        userAgetnt : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36");
    }
);

みたいに書ける(ただ、筆者はうまく行かなかったので、サンプルコードでは別途ユーザエージェントを指定した)。

casper.start(url, function() {
    var ret = casper.evaluate(function() {
        /////////// ここからPhantomJS内でのコンソール実行だと思うといい //////////////
        var recommends = document.querySelectorAll(".serviceTop-entry-title > a");
        return Array.prototype.map.call(recommends, function(self){ return self.text });
        //////////////////////////////////////////////////////////////////////////////
    });

    .....

});

casper.startでオープンするurlと、オープンしたあとの処理を記述した関数を渡す。

別のurlを再度オープンしたいときはcasper.thenOpenを使うこと。

casper.evaluateに渡された関数は、PhantomJS内で実行される。途中、わざわざArray.prototypeを駆使してmap関数を利用したが、これは取得したDOM配列(もどき)はArrayオブジェクトではなくNodeListオブジェクトであり、map関数を持たないためである。

(試しにChromeデバッグコンソールでdocument.querySelelctorAll()を実行して返ってきたオブジェクトでmap関数を使うとエラーになる)

casper.evaluate内は基本的にブラウザ側での実行処理になるので、Chromeデバッグコンソールで記述できる制御は、casper.evaluateでも記述できると思っていいはず。細かい仕様の差分はあるかもしれないが。

カンのいい方はお気づきかも知れないが、casper.evaluate内で実行される関数はPhantomJS側で実行されるので、console.log("hogehoge")とか書いても、何も表示されない。これは、Itsukaraさんのブログに細かく記載されている。

itsukara.hateblo.jp

こちらのブログは、CasperJSのデバッグ方法についても細かく書かれているので、ぜひ参考にされてほしい。

casper.evaluate内で最後に

return Array.prototype.map.call(recommends, function(self){ return self.text });

と書いているが、CasperJS側に単純なオブジェクトなら返すことができる。逆に、CasperJS側からPhantomJSにオブジェクトを渡したいとき(グローバル変数だろうとcasper.evaluate内では別の(PhantomJSの)世界での出来事になるので、その変数は参照できなくなる)は、

casper.evaluate(function(_data){
    ...
}, data);

と書けば、data_dataに渡される。

単純なオブジェクトと書いたが、これが地味に厄介である。単純な配列や連想配列程度なら問題なく渡せるのだが、連想配列の値に配列をもつ、など多少複雑になった途端、なんだかよくわからないオブジェクトが渡される。

思いつきで、

return document.querySelectorAll(".serviceTop-entry-title > a");

とか書いてCasperJS側でDOMをごにょごにょやりたい気持ちはとてもわかるが、残念ながらできない。謎のオブジェクトが返ってきて、持て余す。

ただこれは、自分で定義したオブジェクトくらいなら、後述する裏技で無理やり回避しようと思えばできないこともない。

    casper.echo("<<< おすすめ記事一覧 >>>");
    casper.eachThen(ret, function(_ret) {
        casper.echo(_ret.data);
    });

この出力部分で注意する必要があるのは、下記のように書くと出力されないことである。

    casper.echo("<<< おすすめ記事一覧 >>>");
    for(var idx in ret) {
        casper.echo(ret[idx]); // 何も出力されない\(^o^)/
    }

正直細かい理由はわかってないが、for文で書くと、CasperJSとは同期しない何かで動くらしい(すごく曖昧)。

casper.run();

これまで書いてきたcasper.start内の記述は、言ってみればプログラムの実行計画部分で、実際に走査処理(スクレイピング処理)が行われるのは、casper.run()してからである。これを書かないと、何もおきない。

【おまけ】CasperJSとPhantomJS間のオブジェクトの受け渡し

前述したように、多少複雑なオブジェクトはCasperJSとPhantomJS間で受け渡しがそのままできない。

これは会社の同僚のひらめきから生まれた裏技なのだが、いっそJSON文字列にして受け渡してしまえば、なんとかなったりする。

具体的には、casper.evaluate内でretObjを返すならreturn JSON.stringify(retObj)すればいい。

受け取った側はJSON.parseでもとのオブジェクト(にそっくりなもの)を復元できる。

ただ、これはDOMオブジェクトほど複雑になるとうまくJSON文字列化できなかったので、限界がある。 藁にもすがりたいときの藁程度の知識として、一応知っておくといいと思う。