かわいいよおばけ。
でも、ものすごくぱわふる。
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
なんでもできちゃいますが、くれぐれも悪用厳禁でお願いします!