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

CasperJSでクローリングしながらキャプチャを撮る

CasperJSを使ってクローリングしながらキャプチャを撮りました。

node.jsでクローラを作成していたのですが、
画面キャプチャも撮りたかったので、CasperJSを使うことにしました。

casperjs.org

CasperJSはPhantomJSを利用するためのライブラリです。
PhantomJSとはコマンドラインのWebブラウザで、画面表示のないブラウザです。
PhantomJSを使うと、コマンドラインからブラウザ操作をしたり、
画面キャプチャを撮ることができます。

PhantomJSはレンダリングエンジンとしてWebkitを使用しています。

インストール

CentOS7に必要な依存関係をインストールしておきます。

# yum install freetype
# yum install fontconfig

次にPhantomJSとCasperJSをグローバルインストールします。

# npm install -g phantomjs
# npm install -g casperjs
# phantomjs -v
2.1.1
画面キャプチャの取得

node.jsの日本語サイトを画面キャプチャしてみます。

screenshot.js

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

casper.start();
casper.open('http://nodejs.jp/');

casper.then(function () {
	casper.capture("nodejs.png");
});

casper.run();

処理の概要です。
1行目でCasperJSを読み込み、オブジェクトを生成。

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

open()で対象URLを指定し、then()にその後の処理を記述します。
capture()で画面キャプチャを取得して、引数の名前で保存します。

casper.start();
casper.open('http://nodejs.jp/');

casper.then(function () {
	casper.capture("nodejs.png");
});

最後のrun()で実際に実行されます。

casper.run();

実行してみます。

# casperjs screenshot.js

画面キャプチャの結果がこちら。
なんかめっちゃ長くなりました。

nodejs.png
f:id:pppurple:20160207194805p:plain

原因は画面サイズの指定がデフォルトでは400x300であること。
これでは困るので、viewport()でブラウザと同じくらいの大きさに設定します。

screenshot2.js

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

casper.start();
casper.open('http://nodejs.jp/');
casper.viewport(1500, 1000);

casper.then(function () {
	casper.capture("nodejs2.png");
});

casper.run();

再実行。

# casperjs screenshot2.js

撮り直した結果がこちら。
いい感じに撮れました。

nodejs2.png
f:id:pppurple:20160207195430p:plain:w600

クローリング

クローリングしながらキャプチャを撮ってみました。
藤子F不二雄大全集のサイトをクローリングしてみます。

ソースはこんな感じになりました。

crawl_capture.js

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

var LINK_LEVEL = 4;
var TARGET_URL = "http://www.shogakukan.co.jp/fzenshu/4thseason";
var list = {};

casper.start();
casper.viewport(1500, 1000);

function getLinks() {
	var links = document.querySelectorAll('a');
	return Array.prototype.map.call(links, function(e) {
		return e.getAttribute('href');
	});
}

casper.then(function() {
	casper.emit('crawl', TARGET_URL, 0);
});

casper.run();

casper.on('crawl', function (url, level){
	var links = [];
	if (level >= LINK_LEVEL) return;
	if (list[url]) return;
	list[url] = true;

	var us = TARGET_URL.split("/");
	us.pop();
	var base = us.join("/");
	if (url.indexOf(base) < 0) return;

	casper.open(url).then(function () {
		links = casper.evaluate(getLinks);

		Array.prototype.map.call(links, function (href) {
			var absolutePath = casper.evaluate(function (path) {
				var e = document.createElement('span');
				e.innerHTML = '<a href="' + path + '" />';
				return e.firstChild.href;
			}, href);
			if (!absolutePath) return;
			absolutePath = absolutePath.replace(/\#.+$/, "");
			casper.emit('crawl', absolutePath, level + 1);
		});

		casper.open(url).then(function () {
			var savepath = url.split("/").slice(2).join("/");
			if (savepath.substr(savepath.length - 1, 1) == '/') {
				savepath += "index.html";
			}
			casper.download(url, savepath);

			var capturePath = savepath.split(".").slice(0, -1).join(".") + ".png";
			casper.capture(capturePath);
		});
	});
});


処理の概要です。
クロールする階層の深さと、対象のURLを指定。

var LINK_LEVEL = 4;
var TARGET_URL = "http://www.shogakukan.co.jp/fzenshu/4thseason";

getLinks()で対象ページからaタグのhref要素を取り出します。

function getLinks() {
	var links = document.querySelectorAll('a');
	return Array.prototype.map.call(links, function(e) {
		return e.getAttribute('href');
	});
}

クロールで再帰処理をするので、メイン処理はcrawlというカスタムイベントとして定義します。
emit()で対象URLと階層の深さを指定して初期呼び出します。

casper.then(function() {
	casper.emit('crawl', TARGET_URL, 0);
});

カスタムイベントを定義

casper.on('crawl', function (url, level){
 :
 :
}

階層の深さのチェックと一度クロールしたURLの場合はスキップ。

	if (level >= LINK_LEVEL) return;
	if (list[url]) return;

リンク先のページに対して再帰的に処理を実行。

		Array.prototype.map.call(links, function (href) {
			var absolutePath = casper.evaluate(function (path) {
				var e = document.createElement('span');
				e.innerHTML = '<a href="' + path + '" />';
				return e.firstChild.href;
			}, href);
			if (!absolutePath) return;
			absolutePath = absolutePath.replace(/\#.+$/, "");
			casper.emit('crawl', absolutePath, level + 1);
		});

htmlページの取得と画面キャプチャの取得。

		casper.open(url).then(function () {
			var savepath = url.split("/").slice(2).join("/");
			if (savepath.substr(savepath.length - 1, 1) == '/') {
				savepath += "index.html";
			}
			casper.download(url, savepath);

			var capturePath = savepath.split(".").slice(0, -1).join(".") + ".png";
			casper.capture(capturePath);
		});

実行してみます。

# casperjs crawl_capture.js

確認するとうまく取れているようです。

# tree --charset=x www.shogakukan.co.jp/
www.shogakukan.co.jp/
`-- fzenshu
    |-- 4thseason
    |   |-- index.html
    |   |-- index.png
    |   |-- lineup.html
    |   |-- lineup.png
    |   |-- sakuhin.html
    |   |-- sakuhin.png
    |   |-- tokuchou.html
    |   `-- tokuchou.png
    |-- fgoods
    |   |-- index.html
    |   `-- index.png
    |-- lassie
    |   |-- index.html
    |   `-- index.png
    `-- lineup
        |-- 21emon01.html
        |-- 21emon01.png
        |-- 21emon02.html
        |-- 21emon02.png
        |-- bakeru.html
        |-- bakeru.png
        |-- bekkan.html
        |-- bekkan.png
        |-- berabo.html
        |-- berabo.png
        |-- boysf01.html
       :
       :
       :
        |-- umino02.html
        |-- umino02.png
        |-- umino03.html
        |-- umino03.png
        |-- utopia.html
        |-- utopia.png
        |-- yamabiko.html
        `-- yamabiko.png

5 directories, 246 files

トップページの画面キャプチャを確認。

www.shogakukan.co.jp/fzenshu/4thseason/index.png
f:id:pppurple:20160208003821p:plain:w600

うまく撮れているみたいです。

各書籍の詳細ページを確認してみます。
www.shogakukan.co.jp/fzenshu/lineup/pokonyan.png
f:id:pppurple:20160208004042p:plain:w600

きちんとポコニャンのキャプチャが撮れています。

次触る時はページの操作をやってみたいと思います。

ソースは一応あげときました。
github.com

【参考】