🍃このブログは移転しました。
3秒後、自動的に移動します・・・。

CasperJSを使ってHARファイルを作る

かわいいよおばけ。

でも、ものすごくぱわふる。

HARをつくる

PhantomJSにはnetsniff.jsというサンプルが同梱されており、それをベースにCasperJSで動くように移植します。

参考:phantomjs/examples/netsniff.js at master · ariya/phantomjs · GitHub

HARって?

HttpARchive のことです。
Fire bugやChrome Dev toolsのネットワークのパネルを開いて、右クリックで保存できたりもするアレです。
ページ容量やらリクエスト数、ロード時間などなどWebページのパフォーマンスを調べることができます。

詳しくは、このリンクがよさげ。

参考:Web Performance Power Tool: HTTP Archive (HAR) - igvita.com

ディレクトリ構成

さてさて、構成はこんな感じで。

app.js
modules/
  createHAR.js
har/

実行は普通に。

casperjs app.js

すると、harディレクトリ配下にHARファイルが出力されます。

以下ソース。

ソース

メイン実行ファイル

// app.js
// Initialize.
//////////////////////////////////////////////////////
var fs = require('fs'),
  createHAR = require('./utils/createHAR').createHAR;


// For create HAR file.
//////////////////////////////////////////////////////
var harResources = [],
  harAddress = null,
  harStartTime = null,
  harEndTime = null,
  harBody = null,

  initHarVars = function(url) {
    harResources = [];
    harAddress = url;
    harStartTime = new Date();
    harEndTime = null;
    harBody = null;
  };


// CasperJS and PhantomJS settings.
//////////////////////////////////////////////////////
var casper = require('casper').create({
  viewportSize: {
    width: 320,
    height: 410
  },
  pageSettings: {
    userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A403 Safari/8536.25'
  },
  onPageInitialized: function() {
    harStartTime = new Date();
  },
  onResourceRequested: function(self, req) {
    harResources[req.id] = {
      request: req,
      startReply: null,
      endReply: null
    };
  },
  onResourceReceived: function(self, res) {
    if (res.stage === 'start') {
      harResources[res.id].startReply = res;
    }
    if (res.stage === 'end') {
      harResources[res.id].endReply = res;
    }
  }
});


// Get target urls and start to create HAR.
//////////////////////////////////////////////////////
var urls = {
  lealog: 'http://lealog.hateblo.jp/',
  google: 'http://www.google.co.jp/',
  amazon: 'http://www.amazon.co.jp/gp/aw/h.html'
  },
  targets = Object.keys(urls),

  createHar = function(self, target){
    self.then(function() {
      self.echo('Create HAR for ' + target, 'INFO');
      // Reset previous data.
      initHarVars(urls[target]);
    }).thenOpen(urls[target], function() {
      harEndTime = new Date();
      harBody = createHAR(harAddress, this.getTitle(), harStartTime, harResources);
      fs.write('har/' + target + '.har', JSON.stringify(harBody, undefined, 4), 'w');
      self.echo(target + '.har created!', 'INFO');
    });
  };


// Construct scripts.
//////////////////////////////////////////////////////
casper.start('http://lealog.hateblo.jp/', function() {// Dummy url.
  this.echo('Start script!', 'GREEN_BAR');
});

casper.each(targets, createHar);


// Execute CasperJS and exit.
//////////////////////////////////////////////////////
casper.run(function() {
  this.echo('End script!', 'GREEN_BAR');
  this.exit();
});

createHARモジュール

// modules/createHAR.js
// See https://github.com/ariya/phantomjs/blob/master/examples/netsniff.js
exports.createHAR = function(address, title, startTime, resources){

  if (!Date.prototype.toISOString) {
    Date.prototype.toISOString = function () {
      function pad(n) { return n < 10 ? '0' + n : n; }
      function ms(n) { return n < 10 ? '00'+ n : n < 100 ? '0' + n : n }
      return this.getFullYear() + '-' +
        pad(this.getMonth() + 1) + '-' +
        pad(this.getDate()) + 'T' +
        pad(this.getHours()) + ':' +
        pad(this.getMinutes()) + ':' +
        pad(this.getSeconds()) + '.' +
        ms(this.getMilliseconds()) + 'Z';
    }
  }

  var entries = [];

  resources.forEach(function (resource) {
    var request = resource.request,
      startReply = resource.startReply,
      endReply = resource.endReply;

    if (!request || !startReply || !endReply) {
      return;
    }

    // Exclude Data URI from HAR file because
    // they aren't included in specification
    if (request.url.match(/(^data:image¥/.*)/i)) {
      return;
  }

    entries.push({
      startedDateTime: request.time.toISOString(),
      time: endReply.time - request.time,
      request: {
        method: request.method,
        url: request.url,
        httpVersion: "HTTP/1.1",
        cookies: [],
        headers: request.headers,
        queryString: [],
        headersSize: -1,
        bodySize: -1
      },
      response: {
        status: endReply.status,
        statusText: endReply.statusText,
        httpVersion: "HTTP/1.1",
        cookies: [],
        headers: endReply.headers,
        redirectURL: "",
        headersSize: -1,
        bodySize: startReply.bodySize,
        content: {
          size: startReply.bodySize,
          mimeType: endReply.contentType
        }
      },
      cache: {},
      timings: {
        blocked: 0,
        dns: -1,
        connect: -1,
        send: 0,
        wait: startReply.time - request.time,
        receive: endReply.time - startReply.time,
        ssl: -1
      },
      pageref: address
    });
  });

  return {
    log: {
      version: '1.2',
      creator: {
        name: "PhantomJS",
        version: phantom.version.major + '.' + phantom.version.minor +
          '.' + phantom.version.patch
      },
      pages: [{
        startedDateTime: startTime.toISOString(),
        id: address,
        title: title,
        pageTimings: {
          onLoad: harEndTime - harStartTime
        }
      }],
      entries: entries
    }
  };
};

toISOStringの関数を中に入れたのと、ページonLoadの差分を取る変数名を合わせただけ。

YSlowに通す

HARの解析といえばYSlow。
コマンドラインからの利用には、npmでのインストールが必要です。

npm install -g yslow

そこからの、

 $ yslow lealog.har 
{"w":112690,"o":84,"u":"http%3A%2F%2Flealog.hateblo.jp%2F","r":21,"i":"ydefault","lt":1655}
 $ yslow google.har 
{"w":368265,"o":91,"u":"http%3A%2F%2Fwww.google.co.jp%2F","r":10,"i":"ydefault","lt":419}
 $ yslow amazon.har 
{"w":218248,"o":88,"u":"http%3A%2F%2Fwww.amazon.co.jp%2Fgp%2Faw%2Fh.html","r":15,"i":"ydefault","lt":1110}

たしかcacheは効かない設定で収集したはず。
ページ容量デカいくせに早い!さすがGoogleさまですね・・。

CasperJSさまさま・・・!

参考:CasperJS, a navigation scripting and testing utility for PhantomJS | CasperJS 1.0.2

スクレイピングするのにはこれ以上なくわかりやすい。
URLひらいて、処理やって、違うページいって、あれやって、みたいな処理が直感的に書ける!素敵!

そしてAPIのドキュメントがすごくちゃんとしてる!w

なんでもできちゃいますが、くれぐれも悪用厳禁でお願いします!