關於phantomjs爬取需要登入頁面並截圖(頁面包含一些非同步請求的資料)
專案有個需求是需要捕獲某個頁面的資料(後端完成),因為之前用過phantom,所以就毫不猶豫的選擇了它,關於phantom的介紹,安裝和簡單使用百度很容易找到,這裡就不再贅述了。 之後就開始大刀闊斧的碼起來了,興致沖沖的利用網上找到的擷取某網頁的程式碼(見附錄1)測試。
'use strict'; var page = require('webpage').create(), system = require('system'), args = system.args, page_url = 'http://www.oschina.net/', filename = '../phantomjs_temp/capture0.png'; function capture(url,filename,callback){ console.log("ready to capture"); page.open(url,function(status){ if("success" === status){ console.log("open page succeed"); onPageReady(url,filename,callback); }else{ console.log("open page failed"); closePhantom(); } }); } function onPageReady(url,filename,callback){ page.render(filename); closePhantom(); } function closePhantom(){ console.log("page is closing..."); page.close(); console.log("phantom is closing..."); phantom.exit(1); } capture(page_url,filename);
沒問題。然後發現了問題, 我oschina明明登入了,為什麼這裡是未登入狀態呢(因為專案需求截圖的頁面也有許可權驗證),查閱相關資料之後,找到了解決方案 方案1:將使用者登入的cookie加入到phantomjs中(登入oschina後開啟控制檯,檢視請求裡面的cookie,有一條是oscid的) 如下圖 程式碼如下
'use strict'; var page = require('webpage').create(), system = require('system'), args = system.args, page_url = 'http://www.oschina.net/', filename = '../phantomjs_temp/capture1.png'; function capture(url,filename,callback){ console.log("ready to capture"); page.open(url,function(status){ if("success" === status){ console.log("open page succeed"); onPageReady(url,filename,callback); }else{ console.log("open page failed"); closePhantom(); } }); } function onPageReady(url,filename,callback){ page.render(filename); closePhantom(); } function closePhantom(){ console.log("page is closing..."); page.close(); console.log("phantom is closing..."); phantom.exit(1); } phantom.addCookie({"name":"oscid","value":"mljV7ERwRhP3eH62HnFisZP1qaXlr2txLKufSq%2FUuhCTXQq%2B1RKVm0vp96Iu7MfX6O9lOOYfQG3DmlglDvlk8YvI0DSaPefEGJtGLkSfdZQ%2F5qN340KTUg0PiaZwDvHaucuWHExhfuavuZfodZNJKtGWRFkZxL6V","domain":'www.oschina.net'}); capture(page_url,filename);
執行,binggo,完成。 方案2:開啟oschina的登入頁面,用phantom模擬登陸過程,然後截圖 依然沒問題
'use strict'; var page = require('webpage').create(), system = require('system'), args = system.args, page_url = 'http://www.oschina.net/', login_url = 'https://www.oschina.net/home/login?goto_page=http%3A%2F%2Fwww.oschina.net%2F', filename = '../phantomjs_temp/capture2.png'; function login(){ page.open(login_url,function(status){ if("success" === status){ page.evaluate(function(){ document.querySelector("#userMail").value = 'your user name'; document.querySelector("#userPassword").value = 'your password'; document.querySelector(".btn-login").click(); }); setTimeout('print_cookies()',15000); } }); } function capture(url,filename,callback){ console.log("ready to capture"); page.open(url,function(status){ if("success" === status){ console.log("open page succeed"); onPageReady(url,filename,callback); }else{ console.log("open page failed"); closePhantom(); } }); } function print_cookies(){ console.log("running print_cookies"); for(var i in page.cookies){ console.log(JSON.stringify(page.cookies[i])); } capture(page_url,filename); } function onPageReady(url,filename,callback){ page.render(filename); closePhantom(); } function closePhantom(){ console.log("page is closing..."); page.close(); console.log("phantom is closing..."); phantom.exit(1); } login();
這裡已經完成了一大筆工作了,長長的出口氣吧~
如果需要Python方面的入門知識可以點選這個連結獲取入門資料
但是我專案裡面有另外一個問題就是非同步請求特別多,截圖的時候雖然頁面載入完成了,但是部分非同步請求資料還沒返回,沒有渲染到頁面裡,所以截圖會有部分loading。。
找了很多資料,有個拙劣的解決方法,就是在截圖前在wait一段時間(自己根據實際情況約定,幾秒到幾分鐘都可以),但是這明顯不合理,時間定的太短,可能還是有上面的問題,定的太長,可能頁面在就等著你截圖了,你還在那傻傻的wait,多不合適啊。最合適的不過頁面所有資源和元素都完成了返回和渲染的時刻,這個時刻怎麼得到呢。
這時候就發現百度好坑。搜尋的結果全是重複的,還不能解決這個問題 所以我就把目光放到了QQ群裡,這裡感謝highchart中文站長的幫助,他告訴我可以用document.readyState是不是等於‘complete’來判斷,測試一下確實可以啦 然而多次測試還是存在巧合,當我在伺服器端將非同步請求的處理方法增加sleep阻塞後,這部分就又回到了loading狀態,氣氣氣氣氣。。。
多次翻閱資料,終於在stackoverflow上面找到個類似的問題http://stackoverflow.com/questions/11340038/phantomjs-not-waiting-for-full-page-load
最後Dave的方法解決了我的問題,就是用page.onResourceReceived 和 page.onResourceRequested 一個是page傳送請求執行的callback 一個是page接收到返回執行的callback API:http://phantomjs.org/api/webpage/
每次requested的時候增加一個請求,每次received的時候減少一次請求,當所有請求都得到反饋了,那麼他們差值不就是0了嗎?
懷著忐忑的心情測試了以下,oh,yeah!終於解決了,程式碼如下
var page = require('webpage').create(),
system = require('system'),
args = system.args,
page_url = 'url***********',
filename = '../phantomjs_temp/'+Math.random()+'.png',
countTotal = 1000,
seconds = 1000,
requestIDArr = [];
function capture(url,filename,callback){
console.log("ready to capture");
page.open(url,function(status){
if("success" === status){
console.log("open page succeed");
checkReadyState(url,filename,callback);
}else{
console.log("open page failed");
closePhantom();
}
});
}
function checkReadyState(url,filename,callback,count){
var count = count || 0;
console.log("this is the "+count+"time check ready state");
var timeout = setTimeout(function(){
if(requestIDArr.length==0){
onPageReady(url,filename,callback);
}else{
console.log("still waiting for resoinse id is "+requestIDArr.join(","))
if(count>countTotal){
clearTimeout(timeout);
console.log("has tryed "+(countTotal*seconds/1000)+" seconds,but still failed get correct data");
closePhantom();
return false;
}
count++;
checkReadyState(url,filename,callback,count);
}
},seconds);
}
function onPageReady(url,filename,callback){//頁面完全載入完了(包含非同步請求的資料的渲染也完成了)
var scroll = page.evaluate(function(){
var mainDiv = document.querySelector(".main");
return {"height":mainDiv.scrollHeight,"width":mainDiv.scrollWidth};
});
page.clipRect.height = scroll.height || page.clipRect.height;
page.clipRect.width = scroll.width || page.clipRect.width;
page.viewportSize.width = scroll.width || page.viewportSize.width;
page.render(filename);
closePhantom();
}
function closePhantom(){
console.log("page is closing...");
page.close();
console.log("phantom is closing...");
phantom.exit(1);
}
page.viewportSize = {
width: 400,
height: 550
};
page.clipRect = {
top: 95,
left: 191,
width: 1100,
height: 2200
};
page.onResourceRequested = function (request) {
requestIDArr.push(request.id);
console.log("add is ",request.id);
};
page.onResourceReceived = function (response) {
spliceRequestID(response.id);
};
function spliceRequestID(id){
var spliceTimeout = setTimeout(function(){
var index = requestIDArr.indexOf(id);
if(index>=0){
requestIDArr.splice(index,1);
console.log("delete is ",id);
}else{
spliceRequestID(id);
}
},100);
}
phantom.addCookie({"name":"JSESSIONID","value":"00AF0CF1FB333A5268A9CD5C8FF0487A","domain":'192.168.12.35','path':'/local_adreport/'});
capture(page_url,filename);
至此,整個探究就結束了,可能後面還會遇到其他問題,但是一樣需要耐心解決;