サイトを自由にスクレイピングしようというクエストの攻略方法
前提環境
- windows7
サイトのスクレイピングなんて簡単だと思っていた。
curlだ。
はい解決。
と思っていました。
そう思っている人も多いと思います。
でも実際には、そんなに簡単な道のりじゃなかったのです。
考えるよりも実際の方がきついことだってたくさんあるということです。
curl or wget
名前のわかっているイメージやzipをweb上からDLしたいなら、
curl, wgetで特に問題ありません。
言語すら必要ありません。
websiteは単にHTMLによって作成されているテキストに過ぎない以上、
読み込んできて、文字列を解析(DOM解析)が出来るいずれかのライブラリに通してやれば、
特定の文字列を取ってくることも出来ます。
もしそれだけならば、ここで解決。クエストクリアになります。
ちなみにもしwindowsでも、ActiveXObjectのXMLHttpRequestで可能です。
SPA
最近はSPA(Single Page Application)が増えています。
もしくは単に「動的ページ」でもいいです。
また、「遅延ロード」というものが組み込まれている場合も該当します。
SPAや動的サイトは、読み込まれたときはページの中身は入っておらず、
読み込まれた後にjavascriptによってデータから、ページを構築します。
また、ページ遷移もjavascriptで実際のurlの再読み込みなしに行います。
curlやwgetで引っ張ってくると、javascriptで読み込まれる前の、空のページしか取得できない、ということです。
ブラウザを制御する
人間がやっていることを、プログラムで代替すればいい、ということですので、
人間がやっているブラウザ操作を、プログラムで代替すればいいことになります。
人間がやっていることとは、ウェブブラウザの操作です。
この操作をプログラムから行えればいいわけです。
ActiveXObjet Internet Explorer
次の様なコードで、Internet Exlorer をコントロールできます。
function ie_wait(ie){
while( (ie.Busy) || (ie.readystate != 4) ) {
WScript.Sleep(100);
}
}
function ie_move_page(ie, url){
ie.navigate("http://"+url);
ie_wait(ie)
}
function with_ie(url, func){
var ie = new ActiveXObject("InternetExplorer.Application")
ie.visible = true
try{
ie_move_page( ie, url )
if(func != null){
func(ie)
}
}catch(e){
_l(e.name+": "+e.message)
}finally{
if( IE_TEST_MODE == 0 ){
ie.Quit()
}
}
}
これはInternetExplorerをコード中から呼び出して、操作するコードになります。
「IEであるということ」以外にはデメリットはなさそうです。
Selenium
Selenium というwebdriverがあります。
webdriiverとは、ウェブブラウザをプログラムによって制御するということです。
Selenium を使うと、実際にウェブブラウザをコントロールするように、
javascriptの実行も行わせて、ページを取得できます。
言語も色々あるようです。
phantomjs
IE以外、しかしSeleniumよりも簡単な方法はないか、と探したところ
phantomjs というものがありました。
このphantomjsは読み込めばjsファイルでbrowserが制御できるものです。
落とし穴
phantomjsを用いて開発時、気をつけなければならないことがあります。
それは、
「ブラウザがページを読み込み、javascriptを読み込み、javascriptがページを構築する」
には、一定の時間をきちんと待ってあげる必要がある、ということです。
phantomjsはこの「待つ」事を、行いません。
ページを読み込んだはずなのに、スクリーンショットに意図したものが映っていなかったり、
DOMが取得できなかったりする時は、たいてい読み込みが終わっていません。
実際には、ブラウザすら、どのタイミングでjsが完全にロードされ、
jsがやるべき初期化処理が終わっているか、判別がつきません。
ここで議論されているように、これは解決が難しい問題のようです。
そこで、「待つ」処理を簡単にかけるようにコード化するとよいでしょう。
下記は一例です。
ChainFunc = function(){
var _o = {}
_o.actions = []
_o.beat = 0
_o.add = function(s,f){
_o.actions[_o.actions.length] = [s,f]
return _o
}
_o.render = function(s,file_name){
return _o.add(s, function(){
page.render(file_name+'.png')
})
}
_o.fire = function(ff){
if( _o.actions[_o.beat] ){
var d = _o.actions[_o.beat]
console.log('waiting..'+d[0]+'s')
window.setTimeout( function(){
d[1]()
_o.beat += 1
_o.fire(ff)
}, d[0]*1000)
}else{
ff()
}
}
return _o
}
fo = ChainFunc()
fo.add(2, function(){/* [A] 2秒後にここが実行 */})
.add(3, function(){/* [B] Aの実行後、3秒後にここが実行 */})
.fire(function(){/*最後にここが実行*/}})
phantomjsの問題
phantomjsでもっとも困った問題は、ファイルのダウンロードが出来ない、ということです。
例えばVectorなどで、ダウンロードボタンを押した際、ZIPファイルが落とせますが、あれが出来ないのです。
casperjs
そこで、色々調べたところファイルのダウンロードを実装している、
phantomjsの発展版が、casperjsです。
内部的にphantomjsを利用しているので、パスが通ったところか、同じディレクトリにおいておく必要があります。
casperjsにはwaitする機能もついており、また、thenメソッドによってチェイン的に動作を指定でき、非常に扱いやすいです。
DLする部分は以下の様になります。
casper.on('page.resource.received', function(resource) {
if (resource.stage !== "end") {
return;
}
if( resource.contentType == "application/zip"){
this.download( resource.url , "dl.zip")
}
});
リソースを受け取る際に、そのcontentTypeがzipならば、dl.zipという名前でdlするというソースになります。
DOMの取得、操作
phantomjsでもcasperjsでも evaluate というメソッドで、browser内の挙動を制御可能です。
document.querySelector('div')
document.querySelectorAll('div')
document.querySelector('a[role=first]')
などの「querySelector」で、ほとんどのDOMを取得できるので、取得してclickするだけならば、他の特殊な作業はいらないです。
独自イベントの発行
ドロップダウンの選択時などのとき、選択後、eventを発行してあげないと動作したことがjsに伝わらない時があります。
その際は、以下のようなコードで発行できます。
var sel = document.querySelector('select')
sel.selectedIndex = 1; /* 0 ~ */
var event = document.createEvent('HTMLEvents');
event.initEvent("change", true, true )
sel.dispatchEvent(event);
これでwebアクセス自動化は完了です!