2017-02-10

phantomjs , web crawler 的最終解

大概就是另外一群團隊需要,一直在找尋 web crawler 的最佳解,尤其面對未來 react / angular / vue 之類的 AJAX 的 web content 時,傳統人工光是分析 AJAX 行為模式就花費太多時間,更別說還要處理別的事情之類的

最完美的方式莫過於類似 wkhtmltopdf / wkhtmltoimage,開一個 webkit 的 browser 將網頁完整下載,並執行 CSS / JS / AJAX 後所剩下的 dom tree 轉為 source code,該 HTML 就是真正顯示到使用者螢幕上的資訊,也才是最終想要的成果

之後才看到 phantomjs,稍微練了一下,效果不錯,缺點後述

/*
  指令使用
  ./phantomjs --web-security=false --ignore-ssl-errors=true --load-images=false hello.js
  你可以增加 --debug=true --max-disk-cache-size=1000 來做 debug
*/

console.log('[[initialize]]');

//lib,等待 ajax call,判定 dom 的產生時機
function waitFor(testFx, onReady, timeOutMillis){
  var maxtimeOutMillis = timeOutMillis ? timeOutMillis : 10000, //最長 timeout 時間
    start = new Date().getTime(),
    condition = false,
    interval = setInterval(function(){
      if((new Date().getTime() - start < maxtimeOutMillis) && !condition){
        condition = testFx();
      }else if(!condition){
        console.log("...下載太久timeout或判定來源失誤");
        phantom.exit(1);
      }else{
        //console.log("'waitFor()' finished in " + (new Date().getTime() - start) + "ms.");
        onReady(condition);
        clearInterval(interval);
      }
    }, 250); //0.25一次巡迴
};

//這個來源其實可以從別頁來才是
var source = [
  'http://24h.pchome.com.tw/store/DBAC9R','http://24h.pchome.com.tw/store/DBAC9S',
  'http://24h.pchome.com.tw/store/DBAC9T','http://24h.pchome.com.tw/store/DBAC9U',
  'http://24h.pchome.com.tw/store/DBACA0','http://24h.pchome.com.tw/store/DBACA1',
  'http://24h.pchome.com.tw/store/DBACA2','http://24h.pchome.com.tw/store/DBACG7',
  'http://24h.pchome.com.tw/store/DBACEA','http://24h.pchome.com.tw/store/DBACFP'
];

var page = require('webpage').create();
var start_at = (new Date()).getTime() / 1000;

console.log(start_at);

var process_me = function(url){
  console.log(url);
  page.open(url , function(status){
    //下面這行顯示整頁的HTML
    //console.log(page.content);
    if(status !== "success"){
      console.log("斷線了??或是該網頁連線錯誤");
    }else{
      waitFor(function(){
        return page.evaluate(function(){
          var temp = []
          jQuery('#ProdGridContainer dd').each(function(index,dom){var t = jQuery(dom) ; temp.push([t.find('.prod_img img').attr('alt') , t.find('.price .value').html()])});
          return temp.length > 0 ? temp : false;
        });
      },function(ans){
        for(var i = 0 ; i < ans.length ; i++){
          console.log(ans[i][0] + " : " + ans[i][1]);
        }
        console.log('總數 : ' + ans.length + ', cost(sec) : ' + ((new Date()).getTime() / 1000 - start_at))
        
        start_at = (new Date()).getTime() / 1000;
        
        if(source.length == 0){
          console.log('結束惹')'
          phantom.exit();
        }else{
          process_me(source.pop());
        }
      });
    }
  });
}

process_me(source.pop());


裡面有執行方式,phantomjs 本身包含了 webkit 瀏覽器,所以要去官方網站下載各平台的執行檔才行,好處是相依性都包在裡面了不用另外處理,其餘請看註解

phantomjs 因為是 JS 控制瀏覽器,所以外面大部分的 code 都是針對 webkit 瀏覽器的操作,而 page.evaluate 這個 function 會在 webkit 的網頁內執行,很像是開了 chrome dev tool 然後把 code 貼上的感覺就是,所以腦袋裡面要分開操作瀏覽器的 JS 和在網頁裡面執行的 JS 兩個角色,而對方網頁有載入 jQuery,當然在 page.evaluate 內就可以用 jQuery,但其他地方無法就是(也不需要),然而記得從裡面傳出資訊到外面的 JS 時,一定只能用基本型別(上面的 return temp),否則 phantomjs 會把 prototype 之類的打光光包光光,會非常非常緩慢且肥大,這邊要注意就是

裡面建了一個 waitFor 的 listener,功用是等待 AJAX 後 dom 的生成,所以輸入兩個值,第一個 func 會 return temp || false,當不為 false 時則判定 dom 生成,該 temp 會傳到第二個 func 變成 ans,也就是取得後要做啥事情的意思而已,概念很簡單就是

所以以上,完美的機器人最終解法整套超漂亮的,然而缺點是所有的 JS / CSS 應該都會被下載回來,所以和傳統爬蟲會多 x N 隻的 JS 下載,和執行的等待時間,這花費的基數其實有點高,所以...看似完美解,但其實能不用就不用唄

事實上還有高級做法,類似把對方網址所有的需求先過一層 proxy,把對方的 JS / CSS 先 cache 起來,則可以大幅減少下載和等待的時間才是,不過這邊等待 dom 生成的時間還是省不了,所以保守估計還是會慢一般傳統做法的 20 倍左右唄

okay anyway 這邊大概就這樣而已,也當作自己的 memo 就是了 ...... 然而實際上不用 phantomjs 的話,還有非常多種"邪惡"的爬法,不過我怕我公開後被抓去關,就先略過唄 X"D

沒有留言:

張貼留言