Web 自動化測試與智能爬蟲利器:PhantomJS 簡介與實戰

估計部分同窗沒聽過這個工具,那先簡單介紹下它的背景與做用。javascript

一、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三個平臺,而且提供了對應的二進制安裝包。css

PhantomJS 的使用場景以下:html

  • 無需瀏覽器的Web測試:無需瀏覽器的狀況下進行快速的Web測試,且支持不少測試框架,如YUI Test、Jasmine、WebDriver、Capybara、QUnit、Mocha等。java

  • 頁面自動化操做:使用標準的DOM API或一些JavaScript框架(如jQuery)訪問和操做Web頁面。python

  • 屏幕捕獲:以編程方式抓起CSS、SVG和Canvas等頁面內容,便可實現網絡爬蟲應用。構建服務端Web圖形應用,如截圖服務、矢量光柵圖應用。jquery

  • 網絡監控:自動進行網絡性能監控、跟蹤頁面加載狀況以及將相關監控的信息以標準的HAR格式導出。git

PhantomJS 已造成了一個功能很是強大的生態圈內容,相關項目以下:github

  • CasperJS:一個開源的導航腳本處理和高級測試工具web

  • Poltergeist :測試工具Capybara的測試驅動chrome

  • 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 版本。

二、PhantomJS VS Selenium 

去年在《WEB 自動化測試工具 Selenium 簡介及其應用》一文中介紹過 Selenium 的用法與功能,其實它也是一個 Web 自動化測試工具,是 ThoughtWorks專門爲Web應用程序編寫的一個驗收測試工具。Selenium測試直接運行在瀏覽器中,就像真正的用戶在操做同樣。支持的瀏覽器包括IE(七、八、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 支持。上邊兩個的進一步封裝

這個重要的特性使得他們和一些爬蟲框架組合起來使用以後,目測一大波智能爬蟲正向咱們走來~    -_-|||

三、實戰:抓取某個頁面全部的子請求

簡單的入門教程這裏就不說了,能夠參考官方文檔或者文末連接,假設咱們如今有個需求,須要抓取、分析某個頁面加載時瀏覽器發起的全部的子請求,效果以下如所示:

其實,這個功能 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

四、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:\Python27\python.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 自動化測試或者爬蟲需求中,它的一些其它特性咱們或許剛好就能用得上~

五、Refer:

[1] 比 Selenium 更好用的新神器 Pyppeteer

https://mp.weixin.qq.com/s/pC5rW1h_N3OSgCHlWRq2Ow

[2] can't run puppeteer in centos7

https://github.com/GoogleChrome/puppeteer/issues/391

[3] 使用pyppeteer淘寶登陸

http://bit.ly/2L2OZNj

[4] centos安裝使用puppeteer和headless chrome

http://www.javashuo.com/article/p-kvucbikj-dq.html

[5] Katalon + 傻瓜 == selenium

http://bit.ly/2ZuU8l5

[6] 高級爬蟲: 讓 Selenium 控制你的瀏覽器幫你爬

https://morvanzhou.github.io/tutorials/data-manipulation/scraping/5-01-selenium/

相關文章
相關標籤/搜索