概述
这是一个跨度很广的小记哦,使用node作为脚本,涵盖了三个工具:PhantomJS、CasperJS、SpookyJS。目前网上相关的资料比较少,请关注乱炖,我会断断续续更新。
那这三个工具有什么用呢,网上比较专业的说法是:“前端自动化测试工具”,通俗点来说就是一个“没有UI界面的终端浏览器”;如果完整的来看,他应该是NSCP(我个人写的简称,即:node + SpookyJS + CasperJS + PhantomJS)
注意哦,NSCP和传统的CURL不一样的地方在于:
- CURL是发起一个URL请求,单项的;而NSCP则是一个真正的网页请求,请求整个网页及网页包含的所有资源
- 效率来说,CURL远远比NSCP要快的多
- CURL更适合爬数据,NSCP更多是对于“无界的浏览网页”
那这个工具有什么用呢?举几个简单例子:
- 网页快照(截图)
- 前端自动化测试
- 其他…
PhantomJS
OK,说了这么多,那怎么了解NSCP呢?在分享之前必须先了解PhantomJS。CasperJS和SpookyJS都是在PhantomJS基础上拓展的工具。PhantomJS是一个无界的终端浏览器,他能够访问并测试指定网页。
安装方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
# Mac brew 安装
brew
install
phantomjs
# Npm 安装
npm
install
phantomjs
# CentOS 编译安装
sudo
yum
install
gcc
gcc
-c++
make
git openssl-devel freetype-devel fontconfig-devel
git clone git:
//github
.com
/ariya/phantomjs
.git
cd
phantomjs
git checkout 1.9
.
/build
.sh
|
简单的例子
将下面保存为simple.js文件
1
2
3
4
5
6
7
8
9
|
console.log(
'Loading a web page'
);
var
page = require(
'webpage'
).create();
var
url =
'http://levi.cg.am/'
;
page.open(url,
function
(status) {
// Page is loaded!
phantom.exit();
});
|
终端执行命令
1
|
phantomjs simple.js
|
更多示例见:http://phantomjs.org/examples/
有人说PhantomJS是node,这样理解不是很正确,PhantomJS是一个运行JS调用API接口的工具,允许通过npm方式下载安装并以模块方式存在node中。但是PhantomJS中是不支持node方法的哦,具体差异见官方文档!
同类的软件
挺多的,这里我只列举一个SlimerJS。SlimerJS与PhantomJS不同之处在于,PhantomJS是基于webkit内核的,而SlimerJS是基于Gecko(firefox),SlimerJS和PhantomJS语法很接近,不重复说明,具体见官网
http://www.slimerjs.org/index.html
在PHP中使用PhantomJS
方法一:通过exec
1
2
|
<?php
exec
(
'phantomjs simple.js'
);
|
方法二:PHP PhantomJS
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<?php
require
'../vendor/autoload.php'
;
use
JonnyW\PhantomJs\Client;
$client
= Client::getInstance();
$request
=
$client
->getMessageFactory()->createRequest();
$response
=
$client
->getMessageFactory()->createResponse();
$request
->setMethod(
'GET'
);
$request
->setUrl(
'http://google.com'
);
$client
->send(
$request
,
$response
);
var_dump(
$response
);
|
安装方法及使用见官方git:
https://github.com/jonnnnyw/php-phantomjs
CasperJS
通过上面提供的PhantomJS示例并了解后,你可能会发现一些问题,比如你要请求一系列网址,并且在一个请求之后,执行另一个请求。使用PhantomJS可能会像这样:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
var
page = require(
'webpage'
).create();
//新建一个页面
page.open(url1,
function
(status) {
//导航到第一个URL
if
(status ==
"fail"
) phantom.exit();
//如果发生错误,退出程序
page.open(url2,
function
(status) {
//否则在页面加载完成的回调函数中继续导航到第二个URL,依次类推
if
(status ==
"fail"
) phantom.exit();
page.open(url3,
function
(status) {
if
(status ==
"fail"
) phantom.exit();
page.open(url4,
function
(status) {
if
(status ==
"fail"
) phantom.exit();
// 我可以停下来了吗?
});
});
});
});
|
是不是很恐怖,而CasperJS的目的就在于更方便的操作PhantomJS。CasperJS是一个开源的,用JavaScript编写的,基于PhantomJS的导航脚本和测试工具,它简化了定义一个完成的导航操作所需的步骤,还提供了很有用的函数封装、方法、和语法糖、它可以完成下面这些常见任务:
- 定义 & 排序浏览器导航步骤
- 填充 & 提交表单
- 点击 & 跟踪链接
- 捕获网页截图 (还可以截取某一区域)
- 在远程DOM上进行断言测试
- 记录事件
- 下载资源,包括二进制文件
- 编写功能测试套件,结果保存为JUnit XML文件
- 抓取网页内容
CasperJS使用更方便的API解决了这种异步操作的问题:
1
2
3
4
5
6
7
8
|
var
casper = require(
'casper'
).create();
//新建一个页面
casper.start(url1);
//添加第一个URL
casper.thenOpen(url2);
//添加第二个URL,依次类推
casper.thenOpen(url3);
casper.thenOpen(url4);
casper.run();
//开始导航
|
终端执行文件
1
|
casperjs simple.js
|
更多示例见官方文档:
http://casperjs.readthedocs.org/en/latest/index.html
注意哦:CasperJS使用的是casper的方法,PhantonJS使用的是phantom的方法;他们有各自的使用方法,不可以用混淆执行
安装方法
目前我只在本地开发环境安装,安装方法如下:
1
|
brew
install
casperjs --devel
|
注意哦:目前PhantomJS的版本是1.9.2,正式版是不支持的,请安装开发版
SpookyJS
通过上面描述,应该了解到:
- PhantomJS,一个无界的终端浏览器
- CasperJS,一个改善PhantomJS操作的工具
而SpookyJS有什么用途呢?前面说了PhantonJS和CasperJS是可以作为模块进行npm安装在node中,但是执行的方法却是通过各自的bin文件,诸如:
1
2
|
phantomjs simple.js
casperjs simple.js
|
而SpookyJS则是一个驱动器,在node下能够正常使用PhantonJS和CasperJS,这就是我定义的NSCP (node + SpookyJS + CasperJS + PhantomJS)。你可以简单将其理解为:
- node:脚本语言
- Spooky:驱动器
- CasperJS:工具
- PhantomJS:无界浏览器
安装方法
既然SpookyJS作为node的驱动器,那么也需要通过node进行安装:
1
|
npm
install
spooky
|
SpookyJS需要依赖的环境
- Node.js >= 0.8
- PhantomJS >= 1.9
- CasperJS >= 1.0
简单示例
保存为hello.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
try
{
var
Spooky = require(
'spooky'
);
}
catch
(e) {
var
Spooky = require(
'../lib/spooky'
);
}
var
spooky =
new
Spooky({
child: {
transport:
'http'
},
casper: {
logLevel:
'debug'
,
verbose:
true
}
},
function
(err) {
if
(err) {
e =
new
Error(
'Failed to initialize SpookyJS'
);
e.details = err;
throw
e;
}
spooky.start(
'http://en.wikipedia.org/wiki/Spooky_the_Tuff_Little_Ghost'
);
spooky.then(
function
() {
this
.emit(
'hello'
,
'Hello, from '
+
this
.evaluate(
function
() {
return
document.title;
}));
});
spooky.run();
});
spooky.on(
'error'
,
function
(e, stack) {
console.error(e);
if
(stack) {
console.log(stack);
}
});
/*
// Uncomment this block to see all of the things Casper has to say.
// There are a lot.
// He has opinions.
spooky.on('console', function (line) {
console.log(line);
});
*/
spooky.on(
'hello'
,
function
(greeting) {
console.log(greeting);
});
spooky.on(
'log'
,
function
(log) {
if
(log.space ===
'remote'
) {
console.log(log.message.replace(/ \- .*/,
''
));
}
});
|
通过node执行脚本
1
|
node hello.js
|
更多内容请见官方git:
https://github.com/WaterfallEngineering/SpookyJS
需要注意的几个地方
通过SpookyJS配置PhantomJS和CasperJS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
var
spooky =
new
Spooky({
child: {
transport:
'http'
},
casper: {
logLevel:
'debug'
,
verbose:
true
,
viewportSize: {
width: 1440, height: 768
},
pageSettings: {
outputEncoding:
'gbk'
}
}
},
function
(err) {});
|
phantom和casper不同的方法调用
1
2
3
4
5
6
7
8
9
10
11
12
|
spooky.start(
'http://example.com/the-page.html'
);
spooky.then(
function
() {
// 这里是CasperJS的方法
});
spooky.thenEvaluate(
function
() {
// 这里是PhantomJS的方法
})
// this function (and the three spooky calls above) runs in Spooky's environment
spooky.run();
|
变量作用域
SpookyJS的作用域和js不一样哦,具体看下面几个来自官方的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
var
x =
'spooky'
;
spooky.start(
'http://example.com/the-page.html'
);
spooky.then(
function
() {
var
y =
'casper'
;
console.log(
'x:'
, x);
// -> x: undefined
});
spooky.thenEvaluate(
function
() {
console.log(
'x:'
, x);
// -> x: undefined
console.log(
'y:'
, y);
// -> y: undefined
});
spooky.run();
|
而PhantomJS的方法中可以通过传递一个对象过去
1
2
3
4
5
6
7
8
|
var
x =
'spooky'
;
// spooky.thenEvaluate accepts an options argument (just like Casper)
spooky.thenEvaluate(
function
(x) {
console.log(
'x:'
, x);
// -> x: spooky
}, {
x: x
});
|
而CasperJS的方法中需要将变量作为数组对象,以参数传过去
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
var
x =
'spooky'
;
var
y =
'kooky'
;
// spooky.then accepts a function tuple
spooky.then([{
x: x
},
function
() {
console.log(
'x:'
, x);
// -> x: spooky
}]);
// spooky.thenEvaluate accepts both a function tuple and an argument hash
spooky.thenEvaluate([{
y: y
},
function
(x) {
console.log(
'x:'
, x);
// -> x: spooky
console.log(
'y:'
, y);
// -> y: kooky
}], {
x: x
});
|
全局和局部变量
1
2
3
4
5
6
7
8
9
10
|
var
x =
'spooky'
;
spooky.then([{
x: x
},
function
() {
x =
'casper'
;
console.log(
'x:'
, x);
// -> x: casper
}]);
console.log(
'x:'
, x);
// -> x: spooky
|
一个混合的使用例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
spooky.start(
'http://example.com/the-page.html'
);
var
x =
'spooky'
;
spooky.then([{
x: x
},
function
() {
var
y =
'casper'
;
var
z =
this
.evaluate(
function
(x, y) {
console.log(
'x:'
, x);
// -> x: spooky
console.log(
'y:'
, y);
// -> y: casper
return
[x, y,
'and the-page.html'
].join(
', '
);
}, {
x: x,
y: y
});
console.log(
'z:'
, z);
// -> z: spooky, casper, and the-page.html
}]);
spooky.run();
|
那两个不同的方法之间,如何传递变量呢?来一个非官方的例子,同样来自github
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
spooky.then(
function
() {
window.pageCount = 0;
});
spooky.waitFor(
function
() {
var
isLastPage = !
this
.exists(
'#next'
);
if
(!isLastPage) {
window.pageCount++;
this
.click(
'#next'
);
}
return
isLastPage;
});
spooky.then(
function
() {
this
.emit(
'console'
,
'Page count: '
+ window.pageCount);
});
|
是不是有点不太好理解,如果你了解PHP的话,可以将其当作PHP闭包中的ues去看待。如果仍旧不懂,你可以给我留言哦
不允许使用中文
这个真的是官方、非官方都没有说明的,无论你是不是utf-8,在SpookyJS构造方法中都不允许使用中文
1
2
3
4
5
6
7
8
9
10
11
12
|
var
spooky =
new
Spooky({
// 此处省略...
},
function
() {
spooky.then(
function
() {
// 这样是不可以的哦
var
name =
'中文'
;
}
});
spooky.on(
'test'
,
function
(log) {
console.log(
'但是这里是可以用中文的哦!'
);
});
|
好在JS中有一个原生的方法 decodeURIComponent,可以这样
1
2
3
4
5
6
7
8
9
10
11
12
13
|
var
spooky =
new
Spooky({
// 此处省略...
},
function
() {
spooky.then(
function
() {
var
name =
this
.evaluate(
function
() {
return
window.decodeURIComponent(
'%E4%B8%AD%E6%96%87'
);
});
}
});
spooky.on(
'test'
,
function
(log) {
console.log(
'但是这里是可以用中文的哦!'
);
});
|
当然也可以使用base64,目前还没测。原因,我找了很多资料,唯一有个相关的说法是CasperJS不支持大于8b的字符
转载自:http://levi.cg.am/archives/3648