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.