1. 程式人生 > >關於phantomjs爬取需要登入頁面並截圖(頁面包含一些非同步請求的資料)

關於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);

至此,整個探究就結束了,可能後面還會遇到其他問題,但是一樣需要耐心解決;