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

Itsukaraの日記

最新IT技術を勉強・実践中。最近はDeep Learningに注力。

初心者としてCasperJSで迷ったこと、困ったこと(2)

ITのお勉強 スクレイピング

初心者としてCasperJSで迷ったこと、困ったこと - Itsukaraの日記の続きです。CasperJSのpage DOM環境からCasperJS環境に、任意のスクリプトを渡して実行する機能を試作しました。ご参考まで。(通常は(下図参照)、CasperJS環境からpage DOM環境にスクリプトを渡して実行するので、その逆となります)
http://docs.casperjs.org/en/latest/_images/evaluate-diagram.png

背景

通常、page DOM環境からreturn文でデータを返せば、これがevaluate()の値になるので、これをCasperJS環境でファイルに書き込めば、page DOM環境で作成したデータをファイルに書き込めます。

しかし、page DOM環境内のスクリプトでsetTimeoutを使った処理をしたい場合は、このようなことができません。詳細は、前の記事「初心者としてCasperJSで迷ったこと、困ったこと - Itsukaraの日記」を参照ください。

また、page DOM環境では、Node.jsやCasperJSのfsが使えません。

そのため、page DOM環境で作成したデータをファイルに直接出力することができません。

page DOM環境でのファイル出力

CasperJSのAPIを少し調べたところ、page DOM環境からCasperJS環境へのコールバックができることが分かりました。そこで、これを利用すれば、page DOM環境のデータをCasperJS環境に渡し、CasperJS環境でデータをファイルに書き込むことができます。例えば、下記のようになります。

var casper = require("casper").create();
casper.on('remote.callback', function(data) {
  var fs = require("fs");
  var fname = data.fname;
  var text  = data.text;
  fs.write(fname, text);
});

casper.start("https://en.wikipedia.org/wiki/Test", function() {
  casper.evaluate(function() {
    window.callPhantom({
      fname: "test.txt",
      text : document.body.outerHTML
    });
  });
  casper.wait(2000);
});

casper.run();

page DOM環境からCasperJS環境へのリモート実行

上記を一般化して、page DOM環境からCasperJS環境へのリモート実行機能を試作しました。window.callPhantom()の呼び出しでは、CasperJS環境で実行するスクリプトと、そのスクリプトで使うデータの2つを組み合わせたObjectを引数にしています。これによって、page DOM環境で作成したデータに対して、任意のスクリプトをCasperJS環境で実行できます。ご参考まで。

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

// デバッグ用の各種イベント登録
casper.on('remote.alert', function(msg) {
  this.echo("REMOTE.ALERT: " + msg);
});

casper.on('remote.message', function(msg) {
  this.echo("REMOTE.MESSAGE: " + msg);
});

casper.on("page.error", function(msg, trace) {
  this.echo("PAGE.ERROR: " + msg);
  this.echo("TRACE:");
  for (var i = 0; i < trace.length; i++) {
    var trec = trace[i];
    this.echo("file: " + trec.file + " , line: " + trec.line + " , function: " + trec.function);
  }
  this.die("TRACE END and die");
});

casper.on('step.error', function(err) {
  this.die("STEP.ERROR: " + err);
});

casper.on("run.start", function() {
    casper.echo("CASPER RUN.START")   
});

casper.on("run.complete", function() {
    casper.echo("CASPER RUN.COMPLETE")   
});

// page DOM環境から渡されたスクリプトの実行
//
// page DOM環境から渡されたデータは、「remARGS」で参照可能
//
casper.on('remote.callback', function(data) {
  this.echo("REMOTE.CALLBACK:");
//  this.echo(JSON.stringify(data)); 
  var command = data.command;
  var remARGS = data.remARGS;

  this.echo("REMOTE.CALLBACK: <command is as follows>");
  this.echo(command);
//  this.echo("<remARGS is as follows>");
//  this.echo(JSON.stringify(remARGS)); 

  eval(command);

  this.echo("REMOTE.CALLBACK END");
});

casper.start("https://en.wikipedia.org/wiki/Test", function() {
  casper.echo("POINT 1");
  casper.evaluate(function() {
    // 以下のスクリプトは、page DOM環境で実行される
    var i = 0;
    f1();
    
    function f1() {
      console.log("f1(): i = " + i);
      if (++i < 6) {
        setTimeout(f1, 1000);
      } else {
        console.log("f1(): FINISHED");

        // PhantomJS+CasperJS環境で実行するコマンド
        var command = 
          "var fs = require('fs');\n" +
          "fs.write('test.txt', " + "remARGS[0]" + ");\n" +
          "casper.die('FINISHED and DIE');\n";

        // PhantomJS+CasperJS環境に渡すデータ
        var remARGS = [document.body.outerHTML];
        
        window.callPhantom({
          command: command,
          remARGS: remARGS
        });
      }
    }
  });
  // 下記により、evaluate()が完了するまで待つ
  casper.wait(20000);
});

casper.run();

参考までに、上記の実行結果は下記です。

CASPER RUN.START
POINT 1
REMOTE.MESSAGE: f1(): i = 0
REMOTE.MESSAGE: f1(): i = 1
REMOTE.MESSAGE: f1(): i = 2
REMOTE.MESSAGE: f1(): i = 3
REMOTE.MESSAGE: f1(): i = 4
REMOTE.MESSAGE: f1(): i = 5
REMOTE.MESSAGE: f1(): FINISHED
REMOTE.CALLBACK:
REMOTE.CALLBACK: <command is as follows>
var fs = require('fs');
fs.write('test.txt', remARGS[0]);
casper.die('FINISHED and DIE');

FINISHED and DIE
REMOTE.CALLBACK END