phantomjs api文件
phantomjs實現了一個無介面的webkit瀏覽器。雖然沒有介面,但dom渲染、js執行、網路訪問、canvas/svg繪製等功能都很完備,在頁面抓取、頁面輸出、自動化測試等方面有廣泛的應用。
安裝
下載phantomjs(官方下載,下載失敗請訪問另一個下載點)。解壓到任意目錄,並將包含phantomjs.exe的目錄新增到系統路徑。
如果要藉助phantomjs進行無頭測試,請參考各個測試框架的說明,或者參考phantomjs的官方文件:http://phantomjs.org/headless-testing.html。
使用說明
簡單示例
// test.js var page = require('webpage').create(), system = require('system'), address; if (system.args.length === 1) { phantom.exit(1); } else { address = system.args[1]; page.open(address, function (status) { console.log(page.content); phantom.exit(); }); }
執行:
phantomjs ./test.js http://baidu.com
這個例子簡單地展示了通過phantom訪問baidu.com,並輸入html內容。使用方式就像使用node執行js程式碼一樣。在phantom執行時,它會向當前程式碼執行環境注入phantom物件。如上面程式碼中,通過phantom物件控制程式終結。示例中其他程式碼的含義以及更多深入的用法,將在下文中展開。
window物件
在使用phantom時,我首先關注的是DOM和BOM介面。不過這不是一個問題,看了下面的程式碼就能瞭解:
// test.js console.log(window === this); phantom.exit();
執行:
phantomjs ./test.js
結果為 true。也就是說,就像瀏覽器環境一樣,我們的程式碼執行在window環境下,可以很方便地進行DOM方面的操作。
注:如果使用web page模組開啟頁面,則請不要在此window物件下進行任何DOM相關的操作,因為這個window並不是page物件內的window。如果想要執行dom相關操作,請參閱page.evaluate()部分。
phantom物件
之前的例子中我們已經初步認識了phantom物件。它的功能是定義和控制phantom執行環境的引數和流程。關鍵的API有:
- phantom.args String[]
獲取傳給本JS程式的引數,需要與 system.args進行區分(system模組詳見下文),後者表示傳給phantomjs引擎的引數。例如 phantomjs ./test.js http://baidu.com這句語句,通過phantom.args,我們能得到的引數列表為 ["http://baidu.com"],而通過 system.args則得到 ["./test.js", "http://baidu.com"]這樣的引數列表。差異就在於是否包含當前指令碼名稱。不過 phantom.scriptName這個API提供了獲取指令碼名稱的功能。 - phantom.cookies Object[]
獲取或設定cookies,不過對於設定建議使用其他的API完成。同時相關的API還有:- phantom.addCookie(Object) Boolean:新增cookie值
- phantom.deleteCookie(cookieName) Boolean:刪除指定Cookie值
- phantom.clearCookies():清空所有的cookie
- phantom.cookiesEnabled Boolean:獲取或設定是否支援cookie
- phantom.injectJs(fileName) Boolean:
把指定的外部JS檔案注入到當前環境。執行這個方法時,phantomjs首先會從當前目錄檢索此檔案,如果找不到,則再到 phantom.libraryPath指定的路徑尋找。 phantom.libraryPath這個API基本上就是為 phantom.injectJs()服務的。 - phantom.onError
當頁面存在js錯誤,且沒有被 page.onError處理,則會被此handler捕獲。下面是使用此API的一個例子。由於phantom環境下程式碼除錯很困難,瞭解這些錯誤捕獲的API也許會對我們的實際使用有所幫助。phantom.onError = function(msg, trace) { var msgStack = ['PHANTOM ERROR: ' + msg]; if (trace && trace.length) { msgStack.push('TRACE:'); trace.forEach(function(t) { msgStack.push(' -> ' + (t.file || t.sourceURL) + ': ' + t.line + (t.function ? ' (in function ' + t.function +')' : '')); }); } console.error(msgStack.join('\n')); phantom.exit(1); };
- phantom.exit(returnValue)
這個API已經見過多次了,它的作用是退出程式,可以設定一個退出程式碼,預設是0。
web page 模組
web page模組的功能是處理具體的頁面。使用時需要引入模組,並建立例項:
var webPage = require('webpage'); var page = webPage.create();
本文中不經說明, page指代 require("webpage").create()的例項。
- page.cookies Object[]
與上文中的 phantom.cookies類似,表示本url下的cookie的讀取。同樣類似的API還有addCookie()、 deleteCookie()、 clearCookies()。 - 頁面內容相關的API
- page.content String:獲取或設定當前頁面的html。
- page.plainText String:這是一個只讀屬性,獲取頁面去除html標記的文字(考慮$.text())。
- page.url String:只讀,獲取當前頁面的url。
- page.setContent():允許修改 page.content和 page.url內容,會觸發reload。
- page.settings Object
對於當前頁面的一些配置項。此API必須在 page.open()呼叫之前設定,否則不會起作用。以下是配置項:
* javascriptEnabled 預設 true:是否執行頁面內的javascript- loadImages 預設 true:是否載入圖片
- userAgent :傳遞給伺服器的userAgent字串
- userName :用於http訪問授權的使用者名稱
- password :用於http訪問授權的密碼
- XSSAuditingEnabled 預設 false:是否監控跨域請求
- resourceTimeout 單位 ms:定義資源請求的超時時間。如果設定了此項,則頁面中如果有任何資源超過此時限未請求成功,則頁面其他部分也會停止請求,並觸發onResourceTimeout()事件處理。
- page.customHeaders Object
phantom允許在請求時在http請求頭部新增額外資訊,此設定項對這個page裡面所有的請求都生效(包含頁面和其他資源的請求)。新增的資訊並沒有限制,但如果設定 User-Agent的值,那麼這個值會覆蓋掉 page.settings裡的設定值。示例:page.customHeaders = { "X-Test": "foo", "DNT": "1" };
page.libraryPath String - 與 phantom.libraryPath類似,page物件也支援設定js檔案路徑,同時可以通過相應的page.injectJs()方法注入javascript檔案。除了 page.injectJs()方法外,還有page.includeJs()也可以加入javascript檔案。它們的區別在於, page.injectJs()不強求此檔案能訪問得到,即使是一個不可訪問的資源也可以。
- page.navigationLocked Boolean 預設 fasle
設定是否允許離開當前頁面,預設是允許。 - page.open()
此方法用於開啟一個網頁,是一個很重要的API,它有三種呼叫形式:- open(url, callback)
- open(url, method, callback)
- open(url, method, data, callback)
聯想一下 $.ajax(),可以更好理解這個API。對於這些引數,需要單獨闡述的是 callback。callback()會在頁面載入完成後呼叫,由 page.onLoadFinished呼叫(時機晚於page.onLoadFinished)。這個 callback會接受一個引數 status,可能值為"success"和 "fail",指示頁面是否載入成功。示例可以參考“簡單示例”一節的例子。
- page.close()
與 page.open()對應,呼叫 page.close()之後,會釋放page所佔用的記憶體,我們不可以在此之後再呼叫page例項。在實際的操作中,呼叫此方法並不會完成清空所佔記憶體;javascript的垃圾回收機制也不會回收page例項。但在實際使用中,常常會遇到將一個page例項反覆open的情況。在一個頁面用完後,記得一定要執行 page.close(),這樣在下一次open的時候,才不會重複分配堆疊空間。 - page.evaluate(fn, [param])
對於page開啟的頁面,往往需要與其進行一些互動。 page.evaluate()提供了在page開啟頁面的上下文(下文直接用page上下文指代)執行function的功能(類比Chrome開發者工具的控制檯)。如下例:page.open('http://m.bing.com', function(status) { var title = page.evaluate(function(s) { return document.querySelector(s).innerText; }, 'title'); console.log(title); phantom.exit(); });
在這個例子中, page.evaluate()接受兩個引數,第一個是必需的,表示需要在page上下文執行的函式 fn;第二個是可選的,表示需要傳給 fn的引數 param。 fn允許有一個返回值return,並且此返回值最終作為 page.evaluate()的返回值。這邊對於剛剛命名的 param和return有一些額外的說明和注意事項。對於整個phantom程序而言, page.evaluate()是跑在一個沙盒中, fn無法訪問一切phantom域中的變數;同樣 page.evaluate()方法外部也不應該嘗試訪問page上下文中的內容。那麼如果兩個作用域需要交換一些資料,只能依靠 param和 return。不過限制很大, param和 return必須為能夠轉化為JSON字串,換言之,只能是基本資料型別或者簡單物件,像DOM 節點、$物件、function、閉包等就無能為力了。這個方法是同步的,如果執行的內容對後續操作不具備前置性,可以嘗試非同步方法以提高效能:page.evaluateAsync()。
- page.render(filename)
page.render()能夠把當前頁面渲染成圖片並輸出到指定檔案中。輸出的檔案格式由傳入的副檔名決定,目前支援 PNG、 JPEG、 GIF、 PDF。var page = require('webpage').create(); page.open('http://github.com/', function() { page.render('github.png'); phantom.exit(); });
還有其他一些API會對 page.render()產生影響,如:- page.zoomFactor Number: 設定縮放比率
- page.clipRect Object:設定輸出的矩形區域,例如:
page.clipRect = { top: 14, left: 3, width: 400, height: 300 };
還有一些頁面設定引數,如果紙張大小,側邊距等,在此不詳述。web page也支援輸出圖片base64格式的字串,API為 page.renderBase64(),也不再詳述。
- page.sendEvent()
為了互動的需要(測試的需要),phantom允許通過程式碼模擬一些互動事件(注意與DOM事件的區分)。- 滑鼠事件:
API: sendEvent(mouseEventType[, mouseX, mouseY, button='left'])
mouseEventtype可能的取值為: 'mouseup'、 'mousedown'、 'mousemove'、'doubleclick'和 'click',這個引數為必須的。
後兩個引數為滑鼠事件的座標位置。最後一個引數為滑鼠按鍵,只對需要按鍵的事件有效,預設為 'left',可能值為 'right'、 'left'、 'middle'。 - 鍵盤事件:
API: sendEvent(keyboardEventType, keyOrKeys, [null, null, modifier])
keyboardEventType可能的取值為 'keyup'、 'keydown'、 'keypress',第2個引數傳入一個鍵值或一個字串。鍵值可以通過 page.event.key來查詢呼叫。第三和第四個引數無效,第五個引數表示同時按下的修飾鍵。取值情況如下:- 0: 未使用修飾鍵
- 0x02000000: Shift鍵被按下
- 0x04000000: Ctrl鍵被按下
- 0x08000000: Alt鍵被按下
看一個示例:
page.sendEvent('keypress', page.event.key.A, null, null, 0x02000000 | 0x08000000);
- 滑鼠事件:
- page.switchToFrame(frameName/framePosition)
預設page對應的是frame,如果一個頁面中還有其他frame,則可以通過此方法切換page對應的frame。其他類似的方法還有 switchToChildFrame()、 switchToParentFrame()、switchToFocusedFrame()、 switchToMainFrame()等,不再贅述。 - page.uploadFile(selector, file)
頁面中常常會有上傳檔案的操作,但phantom沒有介面,因而也就沒有辦法選擇檔案上傳,通過此方法可以模擬檔案上傳操作。示例如下:page.uploadFile('input[name=image]','/path/to/some/photo.jpg');
- 一些事件處理介面
- page.onAlert:phantom沒有介面,所以也就不能處理alert視窗,但可以通過此介面捕獲到alert。
- page.onPrompt:類似的,phantom不能處理prompt視窗,通過這個介面可以捕獲prompt。
- page.onConfirm:類似的,phantom不能處理confirm視窗,通過這個介面可以捕獲confirm。
- page.onConsoleMessage:類似的,phantom不能顯示console視窗,通過這個介面可以捕獲console訊息。
varwebPage=require('webpage'); varpage=webPage.create(); page.onAlert=function(msg){ console.log('ALERT: '+msg); }; page.onPrompt=function(msg,defaultVal){ if(msg==="What's your name?"){ return'PhantomJS'; } // 返回值就是prompt得到的值 returndefaultVal; }; page.onConfirm=function(msg){ console.log('CONFIRM: '+msg); // 返回true相當於點選“確定”,返回false相當於點選“取消” returntrue; }; page.onConsoleMessage=function(msg,lineNum,sourceId){ console.log('CONSOLE: '+msg+' (from line #'+lineNum+' in "'+sourceId+'")'); };
- page.onInitialized:在page建立後觸發。
- page.onUrlChanged:在url發生變化時觸發。它接受新的url作為引數。首次載入頁面,page.onUrlChanged是在 page.onInitialized之後觸發。
- page.onNavigationRequested:如果在 page.navigationLocked中允許頁面跳轉,此接口才會有意義(參見 page.navigationLocked)。它接受4個引數,先看示例:
page.onNavigationRequested=function(url,type,willNavigate,main){ console.log('Trying to navigate to: '+url); console.log('Caused by: '+type); console.log('Will actually navigate: '+willNavigate); console.log('Sent from the page\'s main frame: '+main); }
- url表示要跳轉到的url
- type表示產生跳轉的原因,可能值有 'Undefined'、 'LinkClicked'、'FormSubmitted'、 'BackOrForward'、 'Reload'、 'FormResubmitted'、'Other'
- willNavigate表示是否會跳轉,由 page.navigationLocked控制
- main表示發生跳轉的是否是主frame,如果是主frame則為true,如果為其他frame則為false
- page.onLoadStarted:在開始載入資源時觸發。
- page.onLoadFinished:頁面所有資源載入完成後觸發。其實與 page.open()的回撥函式等價。它接受一個引數 status,表示載入是否成功。參見 page.open()。
- page.onClosing:當在phantom域呼叫 page.close()或page上下文呼叫window.close()時觸發。
- page.onError: 此介面捕獲所有page上下文發生的javascript錯誤。引數是錯誤資訊和呼叫堆疊,參見 phantom.onError。如果page不處理錯誤,那麼這些錯誤會冒泡到phantom的onError處理器。
- page.onCreate:當page建立子視窗時觸發,例如在page上下文中使用window.open,但是子視窗再建立子視窗不會觸發此事件。
- page.onResourceRequested:當頁面請求一個資源時觸發的事件,它接受兩個引數,第一個引數是 requestData物件,它有如下屬性:
- id : 資源請求編號
- method : http請求方法,get/post等
- url:請求的URL
- time : 一個Date object,包含響應接收的時間
- headers : http頭部的資訊列表
第二個引數是 networkRequest例項,它包含3個方法:
- abort():中斷當前的請求。這樣做會觸發onResourceError
- changeUrl(url):改變當前請求的目標url
- setHeader(key, value):修改/新增http頭部資訊
- page.onResourceReceived:當一個資源請求的響應接收到後觸發此事件,它接受一個response物件,這個物件有如下屬性:
- id : 資源請求編號
- url:請求的URL
- time : 一個Date object,包含響應接收的時間
- headers : http頭部的資訊列表
- bodySize : 已接收到的資料大小(全部資料或已接收的部分資料)
- contentType : 指定的內容型別
- redirectURL : 如果是一個重定向響應,那麼此處是重定向到的url
- stage : “start”/ “end”
- status : http狀態碼,如:200
- statusText : http狀態描述,如:OK
- page.onResourceError:當資源載入失敗時,觸發此事件。它接收一個resourceError物件,這個物件有如下屬性:
- id:資源請求的編號
- url:請求的URL
- errorCode:錯誤程式碼
- errorString:錯誤資訊
可參考如下示例:
page.onResourceError=function(resourceError){ console.log('Unable to load resource (#'+resourceError.id+'URL:'+resourceError.url+')'); console.log('Error code: '+resourceError.errorCode+'. Description: '+resourceError.errorString); };
- page.onResourceTimeout:在講 page.settings時曾經提到過這個事件。如果設定了page.settings.resourceTimeout,並且資源在這個時間內沒有載入完成,則會觸發此事件,它接受一個 request物件,這個物件包含如下屬性:
- id : 資源請求編號
- method : http請求方法,get/post等
- url:請求的URL
- time : 一個Date object,包含響應接收的時間
- headers : http頭部的資訊列表
- errorCode:錯誤程式碼
- errorString:錯誤資訊
Child Process模組
通過Child Process模組,我們能建立子程序,藉助 stdin、 stdout、 stderr來實現程序間通訊(很C++)。使用子程序能夠做很多事情,如列印、發郵件、呼叫指令碼或其他程式(不侷限於javascript)。
要使用Child Process模組,我們需要在程式碼中新增 require("child_process")。
以下內容缺乏文件支援,並未經過充分測試,可能存在一定的理解偏差。這部分功能是極有用的,希望在專案中使用的時候注意測試。
Child Process模組本身應該也並完全開發完全。 spawn()、 execFile()可用, exec()和fork()尚未實現。
- spawn(command, [args], [options])
最基本的建立程序的方法。前兩個引數比較重要,例如現在想從phantom程序中執行一段nodejs指令碼,指令碼路徑為 “main.js”,這個指令碼接受一個引數,假定為 “helloworld”,那麼如果想得到這段指令碼的執行結果應該怎麼做呢?參考下面的指令碼:var spawn = require("child_process").spawn; child = spawn('node', ['main.js', 'helloworld']); child.stdout.on("data", function (data) { console.log("spawnSTDOUT:", JSON.stringify(data)) }); child.stderr.on("data", function (data) { console.log("spawnSTDERR:", JSON.stringify(data)) }); child.on("exit", function (code) { console.log("spawnEXIT:", code) }); setTimeout(function () { phantom.exit(0) }, 2000);
其實 spawn()方法沒什麼神祕的,它就是執行第一個引數表示的命令,第二個引數就是這個命令的引數列表。所以如果要開啟一個新的phantom程序,第一個引數為 phantom就行。同樣的道理,指定好程式的路徑或者是指令碼語言直譯器的路徑,通過這個方法可以做的事情很多。
比較不方便的是,程序間的通訊只能通過 stdin、 stdout、 stderr來完成,呼叫spawn()方法後,還需要對這些互動資訊進行監聽,上面的例子中演示了監聽 stdout和stderr的方法。 - execFile(cmd, args, opts, cb)
就像剛剛說的, spawn()方法稍微感覺有點麻煩,使用 execFile()能夠稍稍簡化上面的程式碼。 execFile()的前三個引數與 spawn()的三個引數完全一樣,不同的是它多了一個 cb回撥函式,看一個例子就知道這個回撥函式有什麼用了:var execFile = require("child_process").execFile; child = execFile('node', ['main.js', 'helloworld'], null, function (err, stdout, stderr) { console.log("execFileSTDOUT:", JSON.stringify(stdout)) console.log("execFileSTDERR:", JSON.stringify(stderr)) }); setTimeout(function () { phantom.exit(0) }, 2000);
在 execFile()中,對 stdout、 stderr的監聽做了封裝,簡化了我們的程式碼,不過功能上與 spawn()並無區別。
file system模組
雖然與node.js中檔案系統模組名稱和呼叫方法( require("fs"))一樣,但不得不說,phantom的檔案系統模組總體是比較簡單的,API不多但夠用,API也不同於node.js的非同步回撥風格,而是採用stream+同步的風格,濃濃的C++風味。在使用的時間請一定要注意與node.js的檔案系統模組做區分。
- fs.open(path, mode/opts) File
open()方法接受兩個引數,第一個引數是要開啟的檔案路徑,第二個引數後面還會見到,這裡統一說明。如果是字串,則代表檔案開啟的模式,可選的有 'r'、 'w'、 'a/+'、'b'(read時僅支援 'b');如果是一個物件,則表示配置項,一共有兩個配置項,分別是mode和 charset, mode就是剛剛提到的開啟模式, charset表示檔案的編碼型別。參閱下面的示例:var fs = require("fs"); var file = fs.open("main.js", 'r'); console.log(file.read()); file.close(); file = fs.open("main.js", 'a'); file.write("123"); file.close(); setTimeout(function () { phantom.exit(0) }, 2000);
對開啟的檔案,我們可以進行讀寫操作(具體使用與開啟模式有關)。如果對一個檔案執行了open,請別忘了在檔案使用完成後,再對其執行close。 - fs.read(path, mode/opts) String
fs.read()方法對檔案讀取做了封裝,不必關心檔案的開啟關閉,返回值為檔案內容。 - fs.write(path, content, mode/opts)
fs.write()方法對檔案寫入做了封裝,不必關心檔案的開啟關閉。 - 其他API:
- fs.size(path) Number:獲取檔案大小
- fs.copy(source, destination):複製檔案
- fs.copyTree(source, destination):複製目錄樹
- fs.move(source, destination):移動檔案
- fs.moveTree(source, destination):移動目錄樹
- fs.remove(file):刪除檔案
- fs.removeTree(path):刪除目錄
- fs.join(partialPath[]) String:組合路徑
- fs.split(path) String[]:切割路徑
- fs.exist(path) Boolean:檔案或目錄是否存在
- fs.isFile(path) Boolean:指定路徑是否是檔案
- fs.isDirectory(path) Boolean:指定路徑是否是目錄
- fs.list(path) String[]:獲取指定目錄下的檔案/目錄名稱列表
System模組
在文件一開始就已經提到過system模組,一開始的例子中,我們使用了system模組提供的 args屬性。現在重新來認識一下system模組。system模組主要管理著一些與執行環境有關的屬性。
- system.args String[]
獲取執行phantomjs時傳入的所有引數,這個不再贅述。 - system.env Object
獲取當前的環境資訊。包含作業系統資訊、環境變數資訊等等。通過下面的程式碼來檢視一下吧:var system = require('system'); var env = system.env; Object.keys(env).forEach(function (key) { console.log(key + '=' + env[key]); }); setTimeout(function () { phantom.exit(0) }, 2000);
- system.os Object
獲取作業系統資訊,返回一個簡單物件,這個物件有3個屬性: architecture:架構,如“32bit”; name:作業系統名稱; version:作業系統版本。 - system.pid Number
獲取當前程序的pid。 - system.platform String
永遠返回 'phantomjs'
Web Server模組
phantomjs支援一個簡單的web server模組, require('webserver')即可引入。web server模組基於mongoose。不過最好不要在生產環境使用這樣的伺服器模組,因為現階段此模組僅允許10個併發請求。
看一個簡單的例子吧:
var webserver = require('webserver'); var server = webserver.create(); var service = server.listen(8080, function(request, response) { response.statusCode = 200; response.write('<html><body>Hello!</body></html>') response.close(); });
首先需要建立伺服器例項,然後呼叫 listen()方法監聽, listen()方法的第一個引數可以為一個埠號,也可以中 ip:port這樣的ip+port組合方式。第二個引數是處理請求的回撥方法。下面描述一下 request和 response兩個物件。
- request:
- method:http請求的方法,get、post等
- url: 包含http請求URL和get請求的query string(如果有的話)
- httpVersion:當前採用的http協議的版本
- headers:所有http請求頭部資訊,以鍵值對的形式提供
- post:請求主體,僅對post和put方法的請求有效
- postRaw:如果Content-type為 'application/x-www-form-urlencoded'(表單上傳的預設值)時,post的原始資訊會暫存在此屬性中。
- response:
- headers:以鍵值對的形式儲存所有的HTTP請求頭部的資訊,在第一次呼叫 write()方法前一定要設定
- setHeader(name, value):設定或新增特定的頭部資訊
- header(name):獲取特定的頭部資訊
- statusCode:設定HTTP狀態碼
- setEncoding(encoding): 標明傳給 write()的資料需要轉換成什麼格式,預設為UTF-8。如果資料為二進位制字串,則設定為“binary”
- write(data):向response中傳送資料塊,可以多次呼叫
- writeHead(statusCode, headers):向response中傳送響應頭部。 statusCode是一個3位數字,表示HTTP狀態碼(如404)。後一個引數程式碼響應頭部
- close():關閉HTTP連線
- 為了避免客戶端檢測到連線中斷,記得最後再用 write()方法傳送一個空字串(如: response.write(""))。
- closeGracefully():功能與 close()一樣,不過更安全可靠,它能保證響應頭部先發送,並自動在響應最後加上 response.write("")