ここまでのまとめ。
WEBスクレーピングをCSSセレクタで遣るためにPerl入れますか?Ruby入れますか?PerlでWeb::ScraperやRubyでScrapiと同じ事をWSHでやればいいじゃん。わざわざWSHで?うん、わざわざ、してみた。
ActivePerlでCPANモジュール使うの面倒じゃん*1
WindowsにRubyいれてパッケージ入れるのすら面倒(笑
WSHならJSだけで動くじゃん
JScriptでWeb.Scraperを作ればいいじゃん。
休日を返上してうんうん唸って捻って考えた結果。どうしてもWSHからIEを使いたかった。
スクレーピングのテストとしてYahooから株価を取得することを考えた。
株価取得コード
これを動作させるのが目標
var stocks = {};
stocks.NIKKEI = Utena.Scraper.YahooStock("998407.o");
stocks.TOPIX = Utena.Scraper.YahooStock("998405.t");
for ( i in stocks ){
str = i+"\n\n";
for( j in stocks[i] ){
str += j + "=" + stocks[i][j] + "\n";
}
WScript.Echo(str);
}
WSHでこういう事が出来ればいいなと。
Yahoo株価の取得関数を作る
実現するために、CSSセレクタで動けばいいな。
JavaScriptはXPATHが良いとかなんとか。id:amachangがどっかで書いてたのを読んだ。
でもオレはCSSセレクタが使いたいんだ!!!
CSS セレクタは
http://labnotes.org/svn/public/ruby/scrapi/cheat/scrapi.html
が使えるといいな。
CSSセレクタでスクレーピングするとrubyでもPerlでもセレクタ部分を再利用できる。
コレは何となく嬉しい。
Utena.Scraper.YahooStock = function( stock_code ){
var url = "http://quote.yahoo.co.jp/q?s="+stock_code;
var cssSelector = "div.invest table[border='1'] tr[align='right'] td";
this.moveTo(url);
e = this.query( cssSelector );
var stock = {};
stock.st_code = e[0].innerText;
stock.market = e[1].innerText;
stock.company = e[2].innerText;
stock.update_at = e[3].innerText;
stock.price = e[4].innerText;
stock.diff = e[5].innerText;
stock.d_ratio = e[6].innerText;
stock.volume = e[7].innerText;
return stock;
};
株価のページのCSSセレクタは
var cssSelector = "div.invest table[border='1'] tr[align='right'] td";
この部分。
ScraperはURLとCSSセレクタを変更するだけで使い回せる実装が良いな。
じゃぁUtena.Scraperを実装してみる。
スクレーピング本体をつくる
CSSセレクタは、ライブラリがいっぱいあるのでそれを流用する。
ゼロから書き直すなんて面倒だ。
WSHでeval出来、CSS実装の良いのライブラリを探した。cssQuery.jsが良かった。*2
Utena.Scraper = Utena.IEScriptEngine;
Utena.Scraper._libs=[
"cssQuery.js",
"cssQuery-level2.js",
"cssQuery-level3.js",
"cssQuery-standard.js"
];
Utena.Scraper.query=function(selector){
if(!this._window){
throw "no url or document load error";
}
if(!this._window.document.cssQuery){
this.loadLib(this._libs);
}
return this._window.cssQuery(selector);
}
cssQuery.jsはブラウザが前提なのでIEにスクリプトを埋め込む。ここが大事。
動作の流れは、
- WSHからIEを実行<--いまここ
- 該当URLへ移動
- JSライブラリをbookmarklet的に埋め込む
- CSSセレクタを実行
- WSHに結果を返す。
IEとWSHが共にJScript実装。なのでデータの共有やソース、メソッド呼び出しが容易。
ここではIEを呼び出してWSH側からソースをEVALさせる。
Utena.IEScriptEngine = {
_doc:null,
_ie:null,
_window:null,
_libs_src:[],
_ieObject:function(){
return new ActiveXObject("InternetExplorer.Application");
},
loadLib:function(){
if(!this._window){
return;
}
this.libs_src = Utena.lib.loadSource(this._libs);
this._ie_js_eval(this.libs_src.join('\n\n'));
},
moveTo:function(url){
if(!this._ie){
this._ie = this._ieObject();
this._ie.Visible=false;
}
this._ie.Navigate(url);
while( this._ie.Busy == true ){
WScript.Sleep(1);
}
this._doc = this._ie.Document;
this._window = this._doc.parentWindow;
},
_ie_js_eval:function(src){
this._window.eval(src);
}
}
"InternetExplorer.Application"より"htmlfile"の方が安定しそうだがIEを操作したいのでこの書き方になった。
WSHのFSO(FileSystemObject)でソースを読み込みIEでEvalする。ここ大事。
Utena.lib.read = function ( name ){
try{
if( !name || name.length < 1 ){
throw "ファイル名がない"
}
var fs = new ActiveXObject("Scripting.FileSystemObject");
var a = fs.OpenTextFile( name, 1 , fs.TristateUseDefault );
var src = a.ReadAll();
return src
}catch(e){
WScript.Echo( e.message );
return false;
}
return true;
}
Utena.lib.include = function (name){
try{
src = Utena.lib.read(name);
return eval(src);
}catch(e){
WScript.Echo( e.message );
return false;
}
}
Utena.lib.loadSource = function (files){
var srcs = new Array();
for ( i=0;i<files.length;i++ ){
srcs[srcs.length] = Utena.lib.read(files[i]);
}
return srcs;
}
おまけでMHTMLもライブラリに含めてみた。
MHTMLが使えると、RSSを読み込んで、URLをMHTMLにしてオフラインで閲覧できるようになる。これは便利じゃないかと。最近メインでつかってLivedoorReaderにオフライン閲覧greasemonkeyが作れないかなと。
Utena.lib.mhtml = function (url, filename){
if(!url || !filename){
throw "Usage mhtml(url, ファイル)";
}
var msg = WScript.CreateObject("CDO.Message");
msg.MimeFormatted = true;
msg.CreateMHTMLBody(url, 0, "", "");
msg.GetStream.SaveToFile(filename, 1);
}
今後この辺を改良しようかな。
ソース全体
var Utena = {IEScriptEngine:function(){},Scraper:function(){},lib:function(){}};
Utena.lib.read = function ( name ){
try{
if( !name || name.length < 1 ){
throw "ファイル名がない"
}
var fs = new ActiveXObject("Scripting.FileSystemObject");
var a = fs.OpenTextFile( name, 1 , fs.TristateUseDefault );
var src = a.ReadAll();
return src
}catch(e){
WScript.Echo( e.message );
return false;
}
return true;
}
Utena.lib.include = function (name){
try{
src = Utena.lib.read(name);
return eval(src);
}catch(e){
WScript.Echo( e.message );
return false;
}
}
Utena.lib.mhtml = function (url, filename){
if(!url || !filename){
throw "Usage mhtml(url, ファイル)";
}
var msg = WScript.CreateObject("CDO.Message");
var ie = new ActiveXObject( "InternetExplorer.Application" );
ie.Navigate(url);
while(ie.Busy){
WScript.Sleep(1);
}
msg.MimeFormatted = true;
msg.CreateMHTMLBody(url, 0, "", "");
msg.GetStream.SaveToFile(filename, 1);
}
Utena.lib.loadSource = function (files){
var srcs = new Array();
for ( i=0;i<files.length;i++ ){
srcs[srcs.length] = Utena.lib.read(files[i]);
}
return srcs;
}
Utena.IEScriptEngine = {
_doc:null,
_ie:null,
_window:null,
_libs_src:[],
_ieObject:function(){
return new ActiveXObject("InternetExplorer.Application");
},
loadLib:function(){
if(!this._window){
return;
}
this.libs_src = Utena.lib.loadSource(this._libs);
this._ie_js_eval(this.libs_src.join('\n\n'));
},
moveTo:function(url){
if(!this._ie){
this._ie = this._ieObject();
this._ie.Visible=false;
}
this._ie.Navigate(url);
while( this._ie.Busy == true ){
WScript.Sleep(1);
}
this._doc = this._ie.Document;
this._window = this._doc.parentWindow;
},
_ie_js_eval:function(src){
this._window.eval(src);
}
}
Utena.Scraper = Utena.IEScriptEngine;
Utena.Scraper._libs=[
"cssQuery.js",
"cssQuery-level2.js",
"cssQuery-level3.js",
"cssQuery-standard.js"
];
Utena.Scraper.query=function(selector){
if(!this._window){
throw "no url or document load error";
}
if(!this._window.document.cssQuery){
this.loadLib(this._libs);
}
return this._window.cssQuery(selector);
}
Utena.Scraper.YahooStock = function( stock_code ){
var url = "http://quote.yahoo.co.jp/q?s="+stock_code;
var cssSelector = "div.invest table[border='1'] tr[align='right'] td";
this.moveTo(url);
e = this.query( cssSelector );
var stock = {};
stock.st_code = e[0].innerText;
stock.market = e[1].innerText;
stock.company = e[2].innerText;
stock.update_at = e[3].innerText;
stock.price = e[4].innerText;
stock.diff = e[5].innerText;
stock.d_ratio = e[6].innerText;
stock.volume = e[7].innerText;
return stock;
};
var stocks = {};
stocks.NIKKEI = Utena.Scraper.YahooStock("998407.o");
stocks.TOPIX = Utena.Scraper.YahooStock("998405.t");
for ( i in stocks ){
str = i+"\n\n";
for( j in stocks[i] ){
str += j + "=" + stocks[i][j] + "\n";
}
WScript.Echo(str);
}
で、Defer属性はどこへ
もともと、Defer属性でスクリプトを読み込むテストするためだった。でも結局Evalした。
Deferだと動作早くなるかな。。。。