phantomjs實現了一個無界面的webkit瀏覽器。雖然沒有界面,但dom渲染、js運行、網絡訪問、canvas/svg繪製等功能都很完備,在頁面抓取、頁面輸出、自動化測試等方面有普遍的應用。javascript
安裝
下載phantomjs(官方下載,下載失敗請訪問另外一個下載點)。解壓到任意目錄,並將包含phantomjs.exe的目錄添加到系統路徑。css
若是要藉助phantomjs進行無頭測試,請參考各個測試框架的說明,或者參考phantomjs的官方文檔:http://phantomjs.org/headless-testing.html。html
使用說明
簡單示例
1
2
3
4
5
6
7
8
9
10
11
12
13
|
// 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
(
)
;
}
)
;
}
|
運行:java
|
phantomjs
.
/
test
.js
http
:
/
/
baidu
.com
|
這個例子簡單地展現了經過phantom訪問baidu.com,並輸入html內容。使用方式就像使用node運行js代碼同樣。在phantom運行時,它會向當前代碼運行環境注入phantom對象。如上面代碼中,經過phantom對象控制程序終結。示例中其餘代碼的含義以及更多深刻的用法,將在下文中展開。node
window對象
在使用phantom時,我首先關注的是DOM和BOM接口。不過這不是一個問題,看了下面的代碼就能瞭解:git
|
// test.js
console
.
log
(
window
===
this
)
;
phantom
.
exit
(
)
;
|
運行:github
結果爲 true。也就是說,就像瀏覽器環境同樣,咱們的代碼運行在window環境下,能夠很方便地進行DOM方面的操做。web
注:若是使用web page模塊打開頁面,則請不要在此window對象下進行任何DOM相關的操做,由於這個window並非page對象內的window。若是想要執行dom相關操做,請參閱 page.evaluate()部分。ajax
phantom對象
以前的例子中咱們已經初步認識了phantom對象。它的功能是定義和控制phantom運行環境的參數和流程。關鍵的API有:canvas
- 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
&
amp
;
&
amp
;
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()產生影響,如:
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」,那麼若是想獲得這段腳本的運行結果應該怎麼作呢?參考下面的腳本:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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.