ここまでのまとめ。
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");//TOPIX 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でもセレクタ部分を再利用できる。
コレは何となく嬉しい。
//ScraperをにYahooStock関数を追加 Utena.Scraper.YahooStock = function( stock_code ){ //株価URL var url = "http://quote.yahoo.co.jp/q?s="+stock_code; //CSSセレクタ 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; };
//Yahoo株価をParseする設定 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); }
WSHからIEのスクリプト実装を呼び出す
cssQuery.jsはブラウザが前提なのでIEにスクリプトを埋め込む。ここが大事。
動作の流れは、
IEとWSHが共にJScript実装。なのでデータの共有やソース、メソッド呼び出しが容易。
ここではIEを呼び出してWSH側からソースをEVALさせる。
/////////////////////////////////////////// //IEのjavascriptをWSH側から操作する /////////////////////////////////////////// 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); } //IEからWindowオブジェクトを取得する this._doc = this._ie.Document; this._window = this._doc.parentWindow; }, _ie_js_eval:function(src){ this._window.eval(src); } }
"InternetExplorer.Application"より"htmlfile"の方が安定しそうだがIEを操作したいのでこの書き方になった。
JavaScriptライブラリをWSHに取り込む
WSHのFSO(FileSystemObject)でソースを読み込みIEでEvalする。ここ大事。
/** *JScript ファイルを再利用する関数 */ //{{ ///ファイル読み込み 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; } //perlのuseの代替 Utena.lib.include = function (name){ try{ src = Utena.lib.read(name); return eval(src); }catch(e){ WScript.Echo( e.message ); return false; } } /** * JavaScriptの外部ソースを配列に取り込む * @param array ファイル名 相対パスか絶対パス * @return array ソースファイルの中身の配列 */ 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が作れないかなと。
/** * URL をMHTML形式でローカルに保存する関数 */ //{{ 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); } //}}
今後この辺を改良しようかな。
ソース全体
/** * written by takuya morio * last modified 2007/10/12 * if you have anything to ask, contact me!. * id:takuya_1st * License: http://creativecommons.org/licenses/LGPL/2.1/ */ /** * Utena は名前空間 * WSHからIEを呼び出し、IEのソースにライブラリを突っ込む */ var Utena = {IEScriptEngine:function(){},Scraper:function(){},lib:function(){}};//基底名前空間 /** *JScript ファイルを再利用する関数 */ //{{ ///ファイル読み込み 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; } //perlのuseの代替 Utena.lib.include = function (name){ try{ src = Utena.lib.read(name); return eval(src); }catch(e){ WScript.Echo( e.message ); return false; } } //}} /** * URL をMHTML形式でローカルに保存する関数 */ //{{ 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); } //}} /** * JavaScriptの外部ソースを配列に取り込む * @param array ファイル名 相対パスか絶対パス * @return array ソースファイルの中身の配列 */ 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; } /////////////////////////////////////////// //IEのjavascriptをWSH側から操作する /////////////////////////////////////////// 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); } //IEからWindowオブジェクトを取得する 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利用例 var scraper = new Utena.Scraper; scraper.moveTo(url); var TDs = scraper.query( "body > div.class > table > tr.odd > td" ); var DIVs = scraper.query( "div.title"); var ELEMENT = scraper.query( "span#id")[0]; CSS セレクタの例 http://labnotes.org/svn/public/ruby/scrapi/cheat/scrapi.html */ //ScraperをベースにYahoo株価関数を追加する Utena.Scraper.YahooStock = function( stock_code ){ //株価コードURL var url = "http://quote.yahoo.co.jp/q?s="+stock_code; //YahooをParseする設定 CSSセレクタ var cssSelector = "div.invest table[border='1'] tr[align='right'] td"; // this.moveTo(url); e = this.query( cssSelector ); var stock = {}; //if( e.length==9 ){//Scraping妥当性チェック 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");//TOPIX for ( i in stocks ){ str = i+"\n\n"; for( j in stocks[i] ){ str += j + "=" + stocks[i][j] + "\n"; } WScript.Echo(str); }
CSSセレクタでスクレーピング
JavaScriptでスクレーピングすると何が嬉しいか。Windowsだとインストール実行が楽。やっぱコレが一番。あと、最近流行のJavaScriptを作ってる気分になれる。XMLHTTPで結果をPOSTするとAjaxっぽい。TrimPath.TemplateでHTMLやRSSにすると、Spidering Hacksっぽい。Yahooウィジェットに結果を放り込めたら嬉しい。CDO使えばメールで配信できる。JScript(ECMAScript)なのでJSON化も簡単かも???取得結果をデータベースに放り込むにはJSDBなどライブラリやODBCやADOを利用すれば可能っぽい。
できないこと、WindowsなのでDaemon化が面倒。というかBackGroundで定期実行するのはNTサービス作らなくちゃ無理。ただバックグラウンド実行はWScript.Shell.Run( cmd, 0 );で出来る。
よく考えたらxpathもCSSセレクタも同じもの???
相互変換できるようだし、殆ど同じものだと考えて良いのかも。個人的にはCSSセレクタの方が手軽いいなぁ。。。ブラウザ互換性とかあるし。
相互変換の記事とか
http://d.hatena.ne.jp/nazoking/20070205/1170673230
http://d.hatena.ne.jp/tociyuki/20070726/1185466930
XpathとCSS Selector混ぜるのは良いね。XPATHの互換性が担保されたら。JavaScriptもいいね。
で、Defer属性はどこへ
もともと、Defer属性でスクリプトを読み込むテストするためだった。でも結局Evalした。
Deferだと動作早くなるかな。。。。