利用phantomjs和casperjs自动化页面访问

1 phantomjs

phantomjs可以用来对网站进行访问,基本上可以执行浏览器的所有行为,并且可以用脚本 来自动化。

1.1 phantomjs 安装

可以利用淘宝的镜像, http://npm.taobao.org/dist/phantomjs/ , 在这里下载其源文件 , phantomjs-2.1.1-linux-x86_64.tar.bz2 , 下载之后,解压,即 tar -jxvf <> , 解压完成,可以在 phantomjs-2.1.1-linux-x86_64/bin目录下看到可执行文件 phantomjs。 基本上,这样就可以了。

1.2 第一个实例,网页截图

拿其中一个例子试试,比如下面是一个修改过的实例,假定其名为aa.js, 其行为是打开 http://www.bagualu.net , 并保存该页面的截图。

var page = require('webpage').create();
page.open('http://www.bagualu.net', function(status) {
  console.log("Status: " + status);
  if(status === "success") {
    page.render('bagualu.png');
  }
  phantom.exit();
});

利用命令 phantomjs aa.js , 如果这个命令成功执行,那么就可以在当前目录下看到一 个图片文件 bagualu.png 。说明安装运行成功了。

1.3 在页面中抽取信息

下面的例子,在打开的页面中取出所有的连接,打印出连接名字和连接地址

var page = require('webpage').create();
page.onConsoleMessage = function(msg) {
  console.log(msg);
};
page.open('http://www.cninfo.com.cn/search/search.jsp', 
 function(status) {
  if (status !== 'success') {
    console.log('Unable to access network');
  } else {
    var ua = page.evaluate(function() {
       bb = document.getElementsByTagName("a");
       for( i = 0 ; i < bb.length ; i ++) {
		console.log(bb[i].innerText + "---" 
			     + bb[i].getAttribute("href"));
	}

    });
  }
  phantom.exit();
});

2 casperjs

在上面的例子中,抽取单个页面是方便的,但是如果要连续处理多个页面,则会比较麻烦, casperjs是建立在phantomjs之上,它提供了一些很方便的函数。

2.1 casperjs的安装

在debian系统中,可以直接用 sudo apt-get install casperjs 来安装。安装完以后, 可以用 which casperjs 来看是否安装完成。因为casperjs需要利用到phantomjs,因此 需要让casperjs能够找到phantomjs,比较简单的方法是在 /usr/local/bin/ 下创建一 个phantomjs的链结。 即 ln -s <phantomjs> /usr/local/bin/phantomjs

2.2 第一个例子,连续访问两个页面

以上步骤完成以后,试试这个例子

var casper = require('casper').create();

casper.start('http://www.bagualu.net', function() {
    this.echo(this.getTitle());
});

casper.thenOpen('http://www.bagualu.net/wordpress/archives/6609', function() {
    this.echo(this.getTitle());
});

casper.run();

这个例子将会依次打开两个页面,并且分别打印这两个页面的标题。而且程序看起来比较清晰。

2.3 提交表单

fillSelectors , 详细的文档在这里 http://docs.casperjs.org/en/latest/modules/casper.html

下面是一个例子,它查询给定日期的所有年报:

var casper = require('casper').create();

casper.start('http://www.cninfo.com.cn/search/search.jsp', function() {
    this.fillSelectors('form#queryHistoryForm', {
	'input[name="orderby"]':  'date11', 
	'select[name="marketType"]':  '012015', //  '深市创业板',
	'select[name="noticeType"]':  '010301', //  '年度报告',
	'input[name="startTime"]':  '2017-03-01',
	'input[name="endTime"]':  '2017-04-01',
	'input[name="pageNo"]':    '1'
    }, true);
});

casper.then(function(){
    //require('utils').dump(this.getElementsAttribute('td.qsgg a', "href"));
    require('utils').dump(this.getElementsInfo('td.qsgg a'));
});

casper.run();

2.4 连续提交表单

这里在casperjs的代码中,使用了一般的js来实现循环。实现抓取多个页面的代码。这里多 不同的参数多次调用了 fillSelectors 这个函数。

var casper = require('casper').create();

casper.start('http://www.cninfo.com.cn/search/search.jsp');

for( var page = 1 ; page < 10 ; page ++){

    casper.then(function() {
	this.fillSelectors('form#queryHistoryForm', {
	    'input[name="orderby"]':  'date11', 
	    'select[name="marketType"]':  '012015', //  '深市创业板',
	    'select[name="noticeType"]':  '010301', //  '年度报告',
	    'input[name="startTime"]':  '2017-02-01',
	    'input[name="endTime"]':  '2017-04-23',
	    'input[name="pageNo"]':    String(page)
	}, true);

    });

    casper.then(function(){
	var infos=this.getElementsInfo('td.qsgg a');
	for(var i = 0 ; i < infos.length ; i ++){
	    if(-1 == (infos[i].text.search(/摘要/)) ){
		this.echo(infos[i].text + "---" 
			  + infos[i].attributes.href);
	    }
	}
    });

}

casper.run();

2.5 casper debug

方法是在创建casper的时候,设置下面的两个参数:

var casper = require('casper').create({
    verbose:true,
    logLevel:"debug"
});

如此设置以后,就可以看到发出去的每个request。

2.6 关于上面的连续提交表单

在上面的例子中,通过debug可以看到,每次表单的提交pageNo都是10,这和我们预期的不 一样。看起来这种作法是有问题的。

可行的方法是不要用变量page,直接把代码写死,使用的是方便一点的lisp代码,如下所示:

;;generete the casperjs script and fetch the content
(defvar *res* "")

(setf *res*
      "var casper = require('casper').create({
    verbose:true,
    logLevel:\"debug\"
});

casper.start('http://www.cninfo.com.cn/search/search.jsp');

casper.then(function() {
    this.fillSelectors('form#queryHistoryForm', {
	'input[name=\"orderby\"]':  'date11', 
	'select[name=\"marketType\"]':  '012015', //  '深市创业板',
	'select[name=\"noticeType\"]':  '010301', //  '年度报告',
	'input[name=\"startTime\"]':  '2017-02-01',
	'input[name=\"endTime\"]':  '2017-04-23',
	'input[name=\"pageNo\"]':    '1'
    }, true);
});

")

(format  t "~A" *res*)

(loop for i from 1 to 29 do
     (format t "~A~d~A"
	     "
casper.then(function()
{
     this.clickLabel('"
	     i
	     "');
})

    casper.then(function(){
	var infos=this.getElementsInfo('td.qsgg a');
	for(var i = 0 ; i < infos.length ; i ++){
	  if((-1 == (infos[i].text.search(/摘要/))
	    && (-1 == (infos[i].text.search(/取消/))))){
		this.echo(infos[i].text + \"---\" 
			  + infos[i].attributes.href);
	    }
	}
    });
"))

(format t "casper.run();~%")

使用 sbcl --script fetch.lisp >fetch.js 生成js代码,然后使用casperjs来调用 这个代码。注意上面在进行第一次查询以后,就模拟了鼠标的点击过程,使用 clickLabel 来 模拟翻页的过程。从debug信息来看,得到了想要的结果。

3 总结

要自动化一些浏览器的动作,或者下载需要使用js的页面内容,要选择使用casperjs。对于 有很多页需要处理的情况,直接使用js在casperjs中使用循环需要小心处理。如果用其他语 言直接生成一系列的 casper.then 函数基本上是没有问题的。

模拟点击时,使用 casper.clickLabel 很有用。

4 附记

今天(2017-04-25)刚刚看到一个消息,说phantomjs的一个核心开发者(Vitaly Slobodin)退出,原因是 chrome 将支持 headless模式,他认为未来人们都会 转移到这个工具上去,继续维护phantomjs的意义不大。因此,这或许是一个趋势。



本文地址: http://www.bagualu.net/wordpress/archives/6623 转载请注明




发表评论

电子邮件地址不会被公开。 必填项已用*标注