Web 自動化測試與智慧爬蟲利器:PhantomJS 簡介與實戰
估計部分同學沒聽過這個工具,那先簡單介紹下它的背景與作用。
1、PhantomJS 是什麼?
PhantomJS是一個基於WebKit的伺服器端JavaScript API,它無需瀏覽器的支援即可實現對Web的支援,且原生支援各種Web標準,如DOM 處理、JavaScript、CSS選擇器、JSON、Canvas和可縮放向量圖形SVG。PhantomJS主要是通過JavaScript和CoffeeScript控制WebKit的CSS選擇器、可縮放向量圖形SVG和HTTP網路等各個模組。PhantomJS主要支援Windows、Mac OS、Linux三個平臺,並且提供了對應的二進位制安裝包。
PhantomJS 的使用場景如下:
- 無需瀏覽器的Web測試:無需瀏覽器的情況下進行快速的Web測試,且支援很多測試框架,如YUI Test、Jasmine、WebDriver、Capybara、QUnit、Mocha等。
- 頁面自動化操作:使用標準的DOM API或一些JavaScript框架(如jQuery)訪問和操作Web頁面。
- 螢幕捕獲:以程式設計方式抓起CSS、SVG和Canvas等頁面內容,即可實現網路爬蟲應用。構建服務端Web圖形應用,如截圖服務、向量光柵圖應用。
- 網路監控:自動進行網路效能監控、跟蹤頁面載入情況以及將相關監控的資訊以標準的HAR格式匯出。
PhantomJS 已形成了一個功能非常強大的生態圈內容,相關專案如下:
- CasperJS:一個開源的導航指令碼處理和高階測試工具
- Poltergeist :測試工具Capybara的測試驅動
- Guard::Jasmine:能夠基於Rails實現自動化測試Jasmine的Specs
- GhostDriver:遠端 WebDriver 有線協議的開源實現
- PhantomRobot:PhantomJS機器人測試框架
- Mocha-PhantomJS:JavaScript測試框架Mocha的客戶端
此外,生態圈還包括基於PhantomJS實現了眾多截圖工具,如capturejs、pageres、phantomjs-screenshots、manet、screenshot-app等;以及Node.js、Django、PHP、Sinatra等語言的截圖API和Confess、GhostStory、Grover等眾多工具。
PhantomJS當前最新版本是2.0,目前除了 Linux 的二進位制版本未釋出之外,其它跨平臺的版本都發布了二進位制與原始碼包可供選擇,本文所用的測試環境來源於 Windows 二進位制 2.0 版本。
2、PhantomJS VS Selenium
去年在《WEB 自動化測試工具 Selenium 簡介及其應用》一文中介紹過 Selenium 的用法與功能,其實它也是一個 Web 自動化測試工具,是 ThoughtWorks專門為Web應用程式編寫的一個驗收測試工具。Selenium測試直接執行在瀏覽器中,就像真正的使用者在操作一樣。支援的瀏覽器包括IE(7、8、9)、Mozilla Firefox、Mozilla Suite等。這個工具的主要功能包括:測試與瀏覽器的相容性——測試你的應用程式看是否能夠很好得工作在不同瀏覽器和作業系統之上。測試系統功能——建立衰退測試檢驗軟體功能和使用者需求。支援自動錄製動作和自動生成 .Net、Java、Perl等不同語言的測試指令碼。
用過的同學估計都有感受,就是這貨本質上是依賴於瀏覽器的,每一步操作都是直接操縱圖形化的瀏覽器,這樣無論是從效能還是可程式設計性上來說都差多了,而今天介紹的 PhantomJS 則不然,它除了擁有 Selenium 的絕大部分功能之外,更強大的地方在於他是一個“無頭瀏覽器”,沒有圖形化介面,直接面向程式 API 介面,效能和可操作性比 Selenium 高了很多。這兩個工具最重要的就是能執行頁面 JS,現在流行的基本如下幾種:
- QtWebKit,已知有 Python 和 C++ 支援
- PhantomJS,已知有 JavaScript、CoffeeScript 和 Python 支援,也是 Webkit 核心
- SlimerJS,已知有 JavaScript 支援,Gecko 核心,和火狐是一樣的,也可以運行於火狐之上
- CasperJS,已知有 JavaScript 支援。上邊兩個的進一步封裝
這個重要的特性使得他們和一些爬蟲框架組合起來使用之後,目測一大波智慧爬蟲正向我們走來~ -_-|||
3、實戰:抓取某個頁面所有的子請求
簡單的入門教程這裡就不說了,可以參考官方文件或者文末連結,假設我們現在有個需求,需要抓取、分析某個頁面載入時瀏覽器發起的所有的子請求,效果如下如所示:
其實,這個功能 phantomjs examples 的 netlog.js 已經實現了,但是官方的例子在網路不好、頁面複雜的時候容易漏掉請求,我這裡稍作了修改:
var page = require('webpage').create(),
system = require('system'),
address;
if (system.args.length === 1) {
console.log('Usage: netlog.js <some URL>');
phantom.exit(1);
} else {
address = system.args[1];
page.onResourceRequested = function (req) {
//console.log('requested: ' + JSON.stringify(req, undefined, 4));
console.log(JSON.parse(JSON.stringify(req, undefined, 4)).url);
};
//page.onResourceReceived = function (res) {
// console.log('received: ' + JSON.stringify(res, undefined, 4));
//};
page.open(address, function (status) {
if (status !== 'success') {
console.log('FAIL to load the address');
}
window.setTimeout(function () {
phantom.exit(1);
}, 5000);
});
}
效果:
phantomjs netlog.js http://bj.fang.ooxx.com
http://bj.fang.ooxx.com/
http://include.aifcdn.com/aifang/res/2015042706/b/Aifang_Web_Loupan_List_ListIndex.css
http://pages.aifcdn.com/prism/performance.js?v=1416480080
http://pages.aifcdn.com/js/jquery-1.9.1/jquery-1.9.1.min.js
http://pages.aifcdn.com/js/aa/bb.js
http://include.aifcdn.com/aifang/res/2015042706/b/Aifang_Web_Loupan_List_ListIndex.js
http://tracklog.ooxx.com/referrer_ooxx_pc.js
http://ifx.fang.ooxx.com/s?p=918&c=14&o=1&st=ajk
http://ifx.fang.ooxx.com/s?p=2000&c=14&r=0&sr=0&pa=&o=1&t=&st=ajk
http://pic1.ajkimg.com/display/xinfang/c43a03221d9fd83d6b409f31909ce19a/160x120.jpg
http://chart.aifcdn.com/average/price/city/?id=14&w=210&h=100&limit=6&date=20150428025144&logo=1
http://ifx.fang.ooxx.com/s?p=2001&c=14&o=1&st=ajk
......
另一個例子 netsniff.js 實現了將抓捕到的 網路請求匯出成 HAR 格式然後視覺化分析,有興趣的同學可以參考這個官方的例子。
注意:
(1)phantomjs 的 page.settings.resourceTimeout 只能用於當前頁面父請求的超時控制,並不能用於子請求的超時控制,這樣當一個頁面上百個請求有一個請求阻塞了,會導致整個請求卡死,好在如果它的子請求是非同步的,你可以選擇中斷請求,獲取已有的資料:
timeout 3 phantomjs netlog.js http://bj.fang.ooxx.com/|grep tracklog
(2)儘管 phantomjs 到了 2.0 已經相對成熟了,但部分文件和API功能還不完善,比如 evaluateJavaScript 的文件不完善,函式貌似還有 bug:
var webPage = require('webpage');
var page = webPage.create();
function add(arg1, arg2) {
console.log(arg1 * arg2);
};
add(2, 3);
page.evaluateJavaScript('
function add(arg1, arg2) {
console.log(arg1 * arg2);
};
add(2, 3);
');
phantom.exit();
//結果
6
SyntaxError: Expected token ')'
phantomjs://webpage.evaluate():1 in evaluateJavaScript
SyntaxError: Expected token ')'
phantomjs://webpage.evaluate():1 in evaluateJavaScript
4、Python 下的 PhantomJS:ghost.py
其實 Python 下的 ghost.py 和 PhantomJS 沒有關係,這裡只是對不熟悉 JS 的同學推薦下。
如果要實現第三節中的例子,ghost.py 也能做到,而且整體功能和 PhantomJS 類似:
# coding=utf-8
# 測試utf-8編碼
from multiprocessing.pool import Pool
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
from ghost import Ghost
import time
def requestUrl(url):
resultStr = url + "n"
t1 = time.clock()
ghost = Ghost()
try:
page, resources = ghost.open(url, wait=True, timeout=30)
REQ_FOUND_FLAG = 0
for index, trackReq in enumerate([res.url for res in resources if "tracklog" in res.url]):
resultStr = resultStr + str(index) + "t" + trackReq + "n"
REQ_FOUND_FLAG = 1
if REQ_FOUND_FLAG == 0:
resultStr = resultStr + "requests not found tracklog's URL: " + url + "n"
except Exception, e:
resultStr = resultStr + str(e) + ": " + url + "n"
ghost.exit()
t2 = time.clock()
resultStr = resultStr + str(t2 - t1) + ": " + url + "n"
print resultStr + "-----------------------" + "n"
if __name__ == "__main__":
ts = time.time()
urls = [
'http://bj.ooxx.com/test/',
'http://bj.ooxx.com/',
'http://bj.ooxx.com/job.shtml',
'http://bj.ooxx.com/chuzu/',
'http://bj.ooxx.com/ershoufang/',
'http://bj.fang.ooxx.com/?from=58_home_top',
'http://bj.ooxx.com/ershouche/',
'http://che.ooxx.com/',
'http://bj.ooxx.com/sale.shtml',
'http://bj.ooxx.com/dog/',
'http://bj.ooxx.com/huangye/',
'http://pic2.ooxx.com/m58/app58/m_static/home.html',
'http://bangbang.ooxx.com/jc_pc_homepage_3.html',
'http://jinrong.ooxx.com/k?from=58_index_ss',
'http://about.ooxx.com/hr/'
]
p = Pool(4)
p.map(requestUrl, urls)
print("cost time is: {:.2f}s".format(time.time() - ts))
//結果:
C:Python27python.exe F:/sourceDemo/test.py
http://bj.ooxx.com/test/
requests not found tracklog's URL: http://bj.ooxx.com/test/
0.585803057247: http://bj.ooxx.com/test/
-----------------------
http://bj.ooxx.com/
requests not found tracklog's URL: http://bj.ooxx.com/
0.619204294693: http://bj.ooxx.com/
-----------------------
http://bj.ooxx.com/job.shtml
0 http://tracklog.ooxx.com/referrer4.js
1 http://tracklog.ooxx.com/referrer4.js
2 http://tracklog1.ooxx.com/pc/empty.js.gif?fromid=referrer4&site_name=58&tag=pvstatall&referrer=&type=index&post_count=-1&_trackParams=NA&version=A&loadtime=376&window_size=614x454&trackURL={'new_uv':'1','new_session':'1','init_refer':'','GTID':'14301578423960.8530509551055729','cate':'9224','area':'1','pagetype':'index','GA_pageview':'/index/zhaopin/job/'}&rand_id=0.2580734915100038
1.19415236255: http://bj.ooxx.com/job.shtml
-----------------------
......
雖說 ghost.py 整個功能和 PhantomJS 類似,但它的相容性還是要差一大截:
(1)請求沒有優化,對於頁面上多個相同的引用請求,ghost.py 會老老實實的請求多次,而不會只請求一次。
(2)對於 js 的非同步程式碼和函式封裝的執行,相容性不夠,無法捕獲請求或執行,如下兩種寫法在 ghost 下都有問題:
</script><script src="//tracklog.ooxx.com/referrer_ooxx_pc.js" type="text/javascript" async defer></script>
<script type="text/javascript">
readyToDo("$",function(){
$.getScript('http://tracklog.ooxx.com/referrer4.js');
});
</script>
(3)和 PhantomJS 一樣,ghost 也存在請求超時控制不夠友好的問題,但 ghost 的問題似乎更嚴重,不請求完成就拿不到資料。
好了,本文就介紹 PhantomJS 到這裡,主要通過一個實際的例子來展示 PhantomJS 的強大功能與特性,而在實際的 web 自動化測試或者爬蟲需求中,它的一些其它特性我們或許恰好就能用得上~
5、Refer:
[1] PhantomJS:基於WebKit、開源的伺服器端JavaScript API
http://www.infoq.com/cn/news/2015/01/phantomjs-webkit-javascript-api
[2] phantomjs not waiting for “full” page load
http://stackoverflow.com/questions/11340038/phantomjs-not-waiting-for-full-page-load
[3] phantomJS webpage timeout
http://stackoverflow.com/questions/16854788/phantomjs-webpage-timeout
[4] 有沒有能解析js的庫啊?
http://segmentfault.com/q/1010000000533061
[5] java呼叫phantomjs採集ajax載入生成的網頁
http://blog.csdn.net/imlsz/article/details/24325623
[6] 使用Selenium和PhantomJS解析帶JS的網頁
http://smilejay.com/2013/12/try-phantomjs-with-selenium/
[7] PhantomJS快速入門教程
http://duchengjiu.iteye.com/blog/2201868
[8] PhantomJS API
[9] Ghost.py
http://carrerasrodrigo.github.io/Ghost.py/
[10] slimerjs
[11] 使用python/casperjs編寫終極爬蟲-客戶端App的抓取
http://blogread.cn/it/article/5878?f=wb
[12] 走進 phantomjs 嵌入式測試
[13] 用PhantomJS來給AJAX站點做SEO優化