WSH/JScriptでIEを自動操作し,スクレイピングするライブラリの骨格


スクレイピングできるように。

jQueryセレクタを作り直すのは無駄なので,簡易的なメソッドチェインでDOM要素を選択・絞込みしてみる。 ====

// IE起動
var ie = WScript.CreateObject("InternetExplorer.Application")
ie.Visible = true;
ie_goto_url( ie, "〜〜" );


var links = WebScr(ie)
	.id("center" )
		.tag("dt")
			.tag("a")
	.elems()
;


links.map(function(elem){
	return elem.innerHTML + ", " + elem.href;
}).join("\n").log();


必要なソース:

 

lib_ie.js

/*
	IEをWSH/JScriptから自動操作するライブラリ

	用途:
	・Webアプリの自動化
	・スクレイピング
	・ブラウザテスト
*/




// ------------------ IEの基本操作 ------------------



// IEがビジー状態の間待ちます
function ie_wait_while_busy( ie, _url )
{   
	var timeout_ms      = 45 * 1000;
	var step_ms         = 100;
	var total_waited_ms = 0;
	
	while( ( ie.Busy ) || ( ie.readystate != 4 ) )
	{
		WScript.Sleep( step_ms );
		
		// タイムアウトか?
		total_waited_ms += step_ms;
		if( total_waited_ms >= timeout_ms )
		{
			log(
				"警告:タイムアウトのため,リロードします。("
				+ ie.LocationURL
					// http://blog.livedoor.jp/programlog/archives/298228.html
				+ ")"
			);
			
			// どこかに移動中なら,そこへの移動を再試行
			if( _url )
			{
				log( _url + "への遷移を再試行");
				ie_goto_url( ie, _url );
			}
			else
			{
				log( "リロード中");
				
				// 移動先が明示されていなければリロード
				ie.document.location.reload( true );
				ie_wait_while_busy( ie );
			}
			
			break;
		}
	}

	WScript.Sleep( 1000 )
}
	// http://d.hatena.ne.jp/language_and_engineering/20100310/p1
	// http://d.hatena.ne.jp/language_and_engineering/20100403/p1


// ページを移動
function ie_goto_url( ie, url ){
	ie.Navigate( url );
	ie_wait_while_busy( ie, url );
}



function $( ie, dom_id ){
	return ie.document.getElementById( dom_id );
}


// TagName
function gtn( ie, tag_name, parent ){
	if( !parent ){
		parent = ie.document;
	}
	
	return doms2arr( parent.getElementsByTagName( tag_name ) );
}


// DOM配列をArrayに変換
function doms2arr( elems ){
	return Array.src(
		elems,
		function( _arr, i ){ return _arr[ i ]; },
		function( _arr ){ return _arr.length; }
	);
}



// ------------------ 複雑なスクレイピング ------------------



// スクレイピング用
// セレクタの実装は大変なので,メソッドチェインで絞り込めるようにする
// jQueryの流用ができれば一番よいのだが。
function WebScr( ie ){
	return new IWebScr( ie );
};
function IWebScr( ie, params ){
	this._ie = ie;
	
	if( params && params.parents ){
		this._parents = params.parents;
	}else{
		this._parents = [ ie.document ];
	}
	
	return this;
}
IWebScr.prototype = {
	_ie : null,
	
	// 検索対象となる要素の配列
	_parents : null,
	
	id : function( elem_id ){
		var elem = this._parents.map(function(parent_elem){
			return parent_elem.getElementById( elem_id );
		})[0];
		
		// 一要素のみの配列とする
		return new IWebScr( this._ie, { parents : [ elem ] } );
	}
	,
	
	tag : function( tag_name ){
		var elems = this._parents.map(function( parent_elem ){
			return doms2arr( parent_elem.getElementsByTagName( tag_name ) );
		}).flatten();
		
		return new IWebScr( this._ie, { parents : elems } );
	},
	
	// 保持しているDOM要素を配列で返す
	elems : function(){
		return this._parents;
	}
};
 



 

lib_log.js

function log(s){ WScript.Echo(s); }

String.prototype.log = function(){
	log( this );
};



 

lib_arr.js

// 配列のイテレータ
Array.prototype.each = function( func ){
	for( var i = 0; i < this.length; i ++ ){
		func.call( this, this[i], i ); 
	}
	return this; // チェインを継続
};


// map
Array.prototype.map = function( func ){
	var _arr = [];
	this.each(function( item, ind ){
		_arr.push( func.call( _arr, item, ind ) );
	});
	return _arr;
};


// オブジェクトを配列に変換
Array.src = function( iterable, func_item, func_length ){
	var arr = [];
	
	var length = null;
	if( func_length ){
		length = func_length( iterable );
	}else{
		iterable.length;
	}
	
	for( var i = 0; i < length; i ++ ){
		if( func_item ){
			arr.push( func_item( iterable, i ) );
		}else{
			arr.push( iterable[ i ] );
		}
	}
	return arr;
};


// Rubyのinjectに相当するメソッド
// http://d.hatena.ne.jp/TipsMemo+computer-technology/20150110/p1
Array.prototype.reduce = function( func, init_value ){
	// 初期値をセット
	var result = init_value;
	
	// 各要素ごとに
	this.each(function( item ){
		// 結果を累積更新する
		result = func( result, item )
	});
	
	// 累積結果を返す
	return result;
};


// 多次元配列を1次元にならす関数。
// 内部でreduceを使用
Array.prototype.flatten = function(){
	return this.reduce(
		function( result, item ){
	    	return (
	    		//Array.isArray( item ) // WSHや古いIEでは動かない
	    		( item instanceof Array )
	    			// 対象要素が配列ならば,再帰する
	    			? result.concat( item.flatten() ) 

	    			// 対象要素が配列でなければ,要素として採用
	    			: result.concat( item )
	    	);
		},
		
		// 空配列からはじめる
		[]
	);
};

今後の課題:

  • DOM要素のクラス名を指定できるようにしたい。現在の要素の中からクラスを絞り込むのか,それとも,現在の要素の子要素の中から該当するクラス名のものを探すのか,を区別できるようにする必要がある。