CasperJSめも
CaperJSを触り始めたのだが、まとまってる記事がそんなにない。徒然なるメモをここに。
そもそも論
CaperJSはNode.jsのスクレイピングモジュールでありスクレイピングツールである。スクレイピングツールじゃなくてテストツールかも知れないが、スクレイピングに使えるのは間違いない。
また、Node.jsのモジュールなので当然Javascriptで書く。
CasperJSはPhantomJSというヘッドレスブラウザをハンドルする。ヘッドレスって何かというと、GUIがないブラウザ。かといってLynxとかW3mとかみたいなCUIブラウザとも違う。JavaScriptも基本実行してくれたりとレンダリング処理は内部で走るのだが、ユーザに表示する画面を持たない。だから、うまいことPhantomJSをハンドルすれば、「あそこのここをクリックして表示されたページのここんとこのこのテキストを抽出!」とかを自動化できたりする(雑)。
導入
CasperJSはnpmパッケージにある。nodejsとnpmは先に入れておくこと。
$ brew install nodejs
ubuntuなどのdebian系でもapt-getで可能だが、npmも指定する必要があったはず(下記)
$ sudo apt-get install nodejs npm
まぁ普通にnode.jsのページからダウンロードするのでもいい。Windowsだとこれがお手軽(というか他は知らない)。
node.jsを導入してnpmコマンドが使えるようになったら、 下記コマンドでCasperJSをインストールする。
$ npm install -g casperjs
なお、カレントディレクトリのみにインストールしたい場合は、-g
オプションをつけないこと。
PhantomJSの導入
PhantomJSもnpmでインストールできるはず。
$ npm install -g phantomjs
ただ、筆者のlubuntu環境ではうまく行かなかった。(windowsとMacでは大丈夫だった) だが、普通に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さんのブログに細かく記載されている。
こちらのブログは、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文字列化できなかったので、限界がある。 藁にもすがりたいときの藁程度の知識として、一応知っておくといいと思う。