`

2. 一个无界浏览器内核: PhantomJS + CasperJS

 
阅读更多
官网:http://casperjs.org/
API: http://docs.casperjs.org/en/latest/modules/index.html
官方例子: https://github.com/n1k0/casperjs/tree/master/samples

使用 CasperJS 进行简单的 UI 测试 http://www.oschina.net/translate/simpler-ui-testing-with-casperjs
CasperJS : 基于 PhantomJS 的javascript Web Crawler 工具 http://abbypan.blogspot.com/2013/02/casperjs-phantomjs-javascript-web.html
注: casperjs 命令是用 Python 脚本编写, 也可以使用 Python 来执行.


CasperJS它的设定参数方式:
var casper = require('casper').create({  
    verbose: true, 
    logLevel: 'debug', 
    pageSettings: { 
         loadImages:  false, 
         loadPlugins: true,  
         userAgent: 'Mozilla/5.0 (Windows NT 6.1; rv:17.0) Gecko/20100101 Firefox/17.0' 
    },
    viewportSize: {
        width: 1024,
        height: 768
    },
    clientScripts:  [
        //'/home/pandy/PhantomJS/demo/toupiao/jquery.js' //引入jquery, 本地
        'http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js' //引入jquery, 远程
        //但是类似this.test.assertExists(".rh_ui_icon li a:first","存在链接")测试失败,
        //还不知道原因和解决办法
    ],
    onError: function(self,m){//错误回调函数
        this.capture("error.png");
        console.log("onError===========================FATAL:" + m);
        self.exit();
    },
    onAlert: function (msg) {//alert的回调函数
        console.log("onAlert===========================msg:" + msg);
    }
}); 
//phantom.outputEncoding="gbk"; 
casper.options.viewportSize = {width: 1680, height: 924};

CasperJS的参数列表见API文档。
切入了jquery的使用方法可以参考:http://stackoverflow.com/questions/17860928/how-do-i-use-jquery-in-casperjs
var nameCount = this.evaluate(function() {
    var names = $('span.author-name')
    return names.length;
});
this.echo(nameCount);

需要在this.evaluate()方法内才能使用


CasperJS,基于PhantomJS的工具包 http://www.cnblogs.com/ziyunfei/archive/2012/09/27/2706254.html
CasperJS有什么作用呢?
我太懒了,所以直接引用CasperJS网站的介绍:
CasperJS是一个开源的,用JavaScript编写的,基于PhantomJS的导航脚本和测试工具 ,它简化了定义一个完成的导航操作所需的步骤,还提供了很有用的函数封装,方法,和语法糖,它可以完成下面这些常见任务:
定义 & 排序浏览器导航步骤
填充 & 提交表单
点击 & 跟踪链接
捕获网页截图 (还可以截取某一区域)
在远程DOM上进行断言测试
记录事件
下载资源,包括二进制文件
编写功能测试套件,结果保存为JUnit XML文件
抓取网页内容
进行导航操作


如果你在PhantomJS脚本中使用链式回调来进行导航操作,那是相当痛苦的.这种代码无论写起来,读起来,理解起来还是维护起来,都是噩梦:
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使用更方便的API解决了这种异步操作的问题:
var casper = require('casper').create();           //新建一个页面

casper.start(url1);                                //添加第一个URL
casper.thenOpen(url2);                             //添加第二个URL,依次类推
casper.thenOpen(url3);
casper.thenOpen(url4);
casper.run();                                      //开始导航



想要模拟用户通过点击链接来进行导航操作吗?没问题:
var casper = require("casper").create()                       //新建一个页面
casper.start('http://my.blog.tld/');                          //添加第一个URL
casper.thenClick('nav#menu a.blog');                          //在页面加载完成后,点击选择器指定的链接,进入一个新页面
casper.thenClick('.posts li a');                              //在新页面加载完成后,再次点击一个选择器指定的链接
casper.then(function() {                                      //在第二个新页面加载完成后,输出一些信息到控制台中
    this.echo('Page url is ' + this.getCurrentUrl());
    this.echo('Page title is ' + this.getTitle());
});

casper.run();                                                 //开始导航


注意:当click的选择匹配多个的时候,它只会点击第一个元素.

clickLabel()功能更强大
Signature: clickLabel(String label[, String tag])
New in version 0.6.1.
Clicks on the first DOM element found containing label text. Optionaly ensures that the element node name is tag:
// <a href="...">My link is beautiful</a>
casper.then(function() {
    this.clickLabel('My link is beautiful', 'a');
});

// <button type="submit">But my button is sexier</button>
casper.then(function() {
    this.clickLabel('But my button is sexier', 'button');
});



You have evaluate the jQuery code in the browser context using casper.evaluate 所有浏览器指定代码应该在casper.evaluate里面执行,比如下面使用jquery
使用jquery
You have evaluate the jQuery code in the browser context using casper.evaluate
execute code as if you were using the browser console.
var nameCount = this.evaluate(function() {
    var names = $('span.author-name')
    return names.length;
});
this.echo(nameCount);



你还可以使用coffeescript来编写如上功能的脚本:
var casper = require("casper").create()
casper.start "http://my.blog.tld/"
casper.thenClick "nav#menu a.blog"
casper.thenClick ".posts li a"
casper.then ->
    @echo "Page url is #{@getCurrentUrl()}"
    @echo "Page title is #{@getTitle()}"
casper.run()




填充和提交表单也并不难:
casper.start('http://admin.domain.tld/login/', function() {               //打开页面,并指定一个回调函数
    this.fill('form[id="login-form"]', {                                  //定位到一个form中
        'username': 'chuck',                                              //给name为username的表单控件填充值'chuck'
        'password': 'n0rr1s'                                              //给name为password的表单控件填充值'n0rr1s'
    }, true);                                                             //参数true,表示填充完毕后,立刻提交表单
});

casper.then(function() {
    this.echo(this.getTitle());                                           //新页面加载完成后,在控制台输出页面标题
});

casper.run();                                                             //开始导航

注意: 假如是ajax提交的话,一定要做wait,否在很难看到是否真的成功。最要截图来看是否成功。


页面截图: 给页面上指定的区域截图非常简单:
casper.start('http://domain.tld/page.html', function() {
    this.captureSelector('capture.png', '.article-content');                   //给页面中'.article-content'选择器匹配的元素截图,输出图片文件名为cpature.png,目录为当前目录
});
casper.run();

设定图片大小
this.capture("/mnt/D/work_documents/workspace_ide/phantomjs_casperjs/images/test.png",{
            top: 100,
            left: 100,
            width: 800,
            height: 400
        });



异步渲染页面: 有时(好吧,其实是经常),很多页面内容是通过Ajax或者其他的手段异步加载的.你可以等到某个元素出现时再执行想要的操作:
casper.start('https://twitter.com/casperjs_org', function() {
    this.waitForSelector('.tweet-row', function() {                  //等到'.tweet-row'选择器匹配的元素出现时再执行回调函数
        this.captureSelector('twitter.png', 'html');                 //成功时调用的函数,给整个页面截图
    }, function() {
        this.die('Timeout reached. Fail whale?').exit();             //失败时调用的函数,输出一个消息,并退出
    }, 2000);                                                        //超时时间,两秒钟后指定的选择器还没出现,就算失败 
});

测试

除了上面讲的这些功能之外,CasperJS真正的威力是它提供了功能测试的能力.例如,测试谷歌的搜索操作可以这样完成:
casper.start('http://www.google.fr/', function() {                                          //打开谷歌主页,添加页面加载完成时的回调函数
    this.test.assertTitle('Google', 'google homepage title is the one expected');           //检测页面标题是否是'Google',如果是,输出第二个参数指定的字符串
    this.test.assertExists('form[action="/search"]', 'main form is found');                 //检测页面中是否存在选择器指定的元素,如果存在输出第二个参数指定的字符串
    this.fill('form[action="/search"]', {                                                   //填充表单并提交,执行搜索操作
        q: 'foo'
    }, true);
});

casper.then(function() {
    this.test.assertTitle('foo - Recherche Google', 'google title is ok');                  //检测搜索结果页的页面标题是否正确    
    this.test.assertUrlMatch(/q=foo/, 'search term has been submitted');                    //检测搜索结果页的网址是否匹配指定的正则表达式
    this.test.assertEval(function() {
        return __utils__.findAll('h3.r').length >= 10;                                      //自定义一个检测函数
    }, 'google search for "foo" retrieves 10 or more results');
});

casper.run(function() {
    this.test.renderResults(true);                                                          //输出检测结果
});

运行上面的脚本会产生这样的结果:

输出结果还能导出为XUnit XML文件,可以用在持续集成服务器中,比如Jenkins.
对于记录来说,整个CasperJS测试套件使用自己的API写成,结果在visible on Travis-CI.


网上找到一个自动签到的例子:
var casper = require('casper').create({  
    verbose: true,
    logLevel: 'debug',
    pageSettings: {
         loadImages:  false,    
         loadPlugins: true,   
         userAgent: 'Mozilla/5.0 (Windows NT 6.1; rv:17.0) Gecko/20100101 Firefox/17.0'
    }
});
//phantom.outputEncoding="gbk";
casper.options.viewportSize = {width: 1680, height: 924};
casper.start('http://bulo.hujiang.com/app/login?source=nbulo&returnurl=/home/');
casper.waitForSelector("form#myform input[name='txtUsername']",
    function success() {
        this.test.assertExists("form input[name='txtUsername']");
        this.fill("form",{
            'txtUsername':'shixiaobao17',
            'txtPassword':'×××××your password*****'
        },false);
        this.click("input#btnLogin");
    },
    function fail() {
        this.test.assertExists("form input[name='txtUsername']");
});
casper.waitFor(function check() {
    return this.getCurrentUrl().indexOf("bulo.hujiang.com/home")>-1;
}, function then() {
    console.log("登录成功!!!!!!!!!!!!");
}).then(function(){
    console.log("执行登录后的其它操作!!!!!!!!!!!!");
    if(this.exists("#btn_card_do")){
        this.click("#btn_card_do");
        this.waitForSelector("#my_hb_btn",function success(){
            console.log("打卡成功!");
        },function fail(){
            console.log("打卡失败!");
        });
    }else{
        console.log("今天已经打过卡啦!");
    }
});
casper.run(function() {this.test.renderResults(true);});




ajax:
var data, wsurl = 'http://api.site.com/search.json';

casper.start('http://my.site.com/', function() {
    data = this.evaluate(function(wsurl) {//主意在这里的function传入了url,才能使用的。
        return JSON.parse(__utils__.sendAJAX(wsurl, 'GET', null, false));
    }, {wsurl: wsurl});
});

casper.then(function() {
    require('utils').dump(data);
});



工具类:__utils__
必须再casper.evaluate()里面才能使用,并且替换当前实例
casper.start(url1,function(){
    var text = this.evaluate(function(url1) {
        __utils__.echo('plop');//注意这里的用法
        return 'abc';
    });
    this.echo("text="+text);//主意这里的用法
});



casperjs小结 http://blog.csdn.net/xiarendeniao/article/details/7740375
官网http://casperjs.org/
分享http://download.csdn.net/detail/xiarendeniao/5781445

环境:
[dongsong@localhost ~]$ casperjs --version 
1.0.0-RC2 
[dongsong@localhost ~]$ phantomjs --version 
1.7.0 

1.casperjs按照start()、then*()、wait*()、open()等流程往下做导航(注意,如果有语法错误,比如少个分号神马的,可能运行时就无任何提示的卡那里了)
  run()方法触发该流程,run()方法可以指定一个onComplete()方法供导航完成时回调
  exit()/die()退出

2.打印html源码可以用debugHTML();打印web页面用debugPage();获取网页快照用capture('xx.png')

3.填写表单或者输入框用fill()做(要求文本框有name属性)或者在evaluate()内部做(evaluate相当于casperjs和网页之间的gate)
  提交表单可以通过fill直接完成(有些提交是js控制的,这种方法就行不通了)或者用click()点击提交按钮
  下面给一个在evaluate内部做的例子(跟普通页面上的js编程类似)
// Querying for "Chuck Norris" on Google  
casper.start('http://google.fr/').thenEvaluate(function(term) {  
    document.querySelector('input[name="q"]').setAttribute('value', term);  
    document.querySelector('form[name="f"]').submit();  
}, {  
    term: 'Chuck Norris'  
});  
  
casper.run(); 

再给个填没有name属性的input框框的例子:
casper.waitForSelector('#xx', function then() {  
            recognizedCode = 'xxx';  
            this.evaluate(function(rtCode){  
                document.querySelector('input[class="xxx"]').value = rtCode; //1>可以用这种方式填input框框(without name attribute)  
                //document.querySelector('a[action-type="submit"]')[0].click(); //2>可以在这里提交哦!  
                //__utils__.findOne('input[class="WB_iptxt oauth_form_input oauth_form_code"]').value = rtCode; //1>也可以这样填input框框(without name attribute)  
            }, {rtCode:recognizedCode});  
            this.click('a[action-type="submit"]'); //2>也可以在这里提交!  
        },  
        function onTimeout() {  
            this.log('wait selector timeout', 'error')  
        },  
        timeout = 1000  
    );
 
参考:https://groups.google.com/forum/#!msg/casperjs/iybL4kdLqVg/Exw8v2pcUXAJ

4.日志console.log();或者由casperjs对象调用log()/echo()方法
  创建capserjs对象时可以指定日志级别、日志详尽程度、定制事件回调函数(OnError/onResourceReceived/httpStatusHandlers/...):
var casper = require("casper").create({  
        verbose: true,  
        logLevel: "debug",  
        onError: function(self,m){  
                this.capture("error.png");  
                console.log("FATAL:" + m);  
                self.exit();  
            }  
    }); 

还可以指定casper对象整个导航过程总的运行时间timeout,这样可以防止在程序因为网络原因或程序bug导致一直不退出,对应timeout的处理函数是onTimeout
与整个导航过程timeout和onTimeout相对应的是单步超时时间stepTimeout和单步超时处理函数onStepTimeout
   
5.调试时免不了要打印变量或者对象的信息
require("utils").dump(xxx);
 

 
6.命令行参数由cli模块操作,详见http://casperjs.org/cli.html
casperObj.cli.has(0)  
casperObj.cli.get(3)  
casperObj.cli.get('usename')  


 
7.selector,跟css选择器规则一样(详见谷歌/度娘)
  #xx                 选取以xx为id的html标签
  xx[attr='value']    选取属性attr值为value的xx类型html标签
  .xx                选取以xx为class的html标签
 
8.事件,用casperObj.on()来注册事件的回调函数,详见http://casperjs.org/events-filters.html
  要清除对某时间的监控和回调可以把回调函数设为空
  下面给个小例子
casper.on('resource.received', function(resource){  
        if (resource.redirectURL) {  
            var rUrl = resource.redirectURL;  
            if (rUrl.match(/\?code=(\w+)/g)) {  
                code = rUrl.substr(rUrl.search(/\?code=(\w+)/g)+6);  
                this.log("code=" + code,'info');  
                this.clear();  
                this.on('resource.received', function(resource) {});  
            } else  
                return;  
        } else  
            return;  
    });
 

9.单纯的把timeout设置得很长,而不设置对应的超时函数,那么设置的这个值是不起作用的,可能是casperjs的bug,实践多次证明过这个问题
   比如,使用casperjsObj.waitForSelector()等待某个选择器时想定制timeout那么要提供onTimeout函数才行

10.实践证明一个casperjs程序内部无法创建多个casperjs对象
     那么如果需要同时访问多个站点、且需要用一个站点的某些数据填写另一个站点某个form、且form页面在重新请求时不一样(比如验证码,用back()倒回去验证码就不一样了),怎么办呢?
     鸟人的解决方案是自己提供一个页面内嵌两个frame分别装载目标站点;这里又有个问题是casperjs对象不能直接获取iframe内部的标签,需要用casperjsObj.page.switchToChildFrame(0/1/2)进入iframe、用casperjsObj.page.switchToParentFrame()回到上级iframe或者全局的位置(需要注意的是这两个函数和其他普通js程序一样、只有在导航过程中的某个function内部才有效,否则会被casperjs忽视,casperjs只认start/open/thenOpen/run/then/wait*/each/..等导航,普通js程序需要在这些导航对应的function内部填充)

11.capserjsObj.captureSelector()会有误差,可以根据casperjsObj.getElementBounds()获取要拍照的选择器的边界然后人工修订后把边界值传入casperjsObj.capture()拍照

12.控制导航跳向某一步、查看有多少步,可以用label/goto/dumpSteps函数,函数实现和用法详见https://github.com/yotsumoto/casperjs-goto

13.循环控制,我们不可避免的需要重复做一些动作,把所有的URL放到一个数组里面用each函数来处理当然不错,但是很有局限性,如果要重复的动作不是打开页面而是点击某个按钮呢?
鸟人想到的解决办法是用waitFor()+Wait()来实现循环,结果不太理想,要嘛卡死直到timeout,要嘛飞快的死循环
//loop:隔一段时间就刷新一次简历(用waitFor和wait实现循环,貌似不靠谱儿!)  
        /*  
        casper.waitFor(function check(){  
                        this.wait(10000,  
                                function() {  
                                        this.click('a[title="简历刷新"]');  
                                        this.log('refreshed my resume');  
                                }  
                        );  
                        return false;  
                },  
                function then() {},  
                function onTimeout() {this.log('timeout: refresh loop competed.', 'error');},  
                timeout = 120000  
        );*/ 


网上寻来的解决方案是递归调用,casperjsObj调用run函数时可以传入一个结束时执行的函数,在这个函数里面可以加入【我们的循环体】和【递归的run调用】
参考:https://github.com/n1k0/casperjs/blob/master/samples/dynamic.js
function refresh()  
{  
        this.wait(10000,  
                function() {  
                        this.click('a[title="简历刷新"]');  
                        this.log('refreshed my resume');  
                }  
        );  
        this.run(refresh);  
}  
casper.run(refresh); 




解析HTML内容,选择器只支持css3和xpath方式.
1. 尝试解析链接
casper.then(function () {
    var links = this.evaluate(function () {
        var elements= __utils__.findAll(".rh_ui_icon a");
        var list=[];
        alert("elements.length="+elements.length);
        for(var i=0; i<elements.length; i++){
            alert(elements[i].innerHTML);
            list[list.length]=elements[i].innerHTML;
        }
        /*return Array.prototype.forEach.call(elements, function(e) {
            return e.getAttribute('href');
        });*/

        return list;
    });
    this.echo(JSON.stringify(links));
});


2. 返回一个数组
You dont need jQuery here:
casper.evaluate(function() {
    return [].map.call(__utils__.findAll('#id a'), function(node) {
        return node.getAttribute('href');
    });
});


3.遍历一个数组:回调函数的第二个参数,才是这个数组里面的元素
this.each(links, function (self, link) {
        console.log(self+":"+link);
    })


4. 关于each方法:回调函数的第一个参数是casper对象,第二个参数是数组的当前元素
var links = [
    'http://google.com/',
    'http://yahoo.com/',
    'http://bing.com/'
];

casper.start().each(links, function(self, link) {
    self.thenOpen(link, function() {
        this.echo(this.getTitle());
    });
});


自动投票
test.js
var casper = require('casper').create({  
    verbose: true,
    //logLevel: 'debug',
    pageSettings: {
         loadImages:  false,    
         loadPlugins: true,   
         userAgent: 'Mozilla/5.0 (Windows NT 6.1; rv:17.0) Gecko/20100101 Firefox/17.0'
    }
});
//phantom.outputEncoding="gbk";
casper.options.viewportSize = {width: 1680, height: 924};
//方法一, 投票是直接使用url的方式来记票,这种处理方式太他#妈^的闹*残不负责了.
casper.start('http://www.xxxxxx.com/toupiao?id=42',function(){  
    this.capture("c:/test.png",{  
        top: 00,  
        left: 0,  
        width: 1024,  
        height: 768  
    });
	var date = new Date();
    this.echo(date.getTime()+"====================>capture over");  
});
casper.run();


第一个调用js的toupiao.bat
casperjs c:\test.js


然后再增加一个循环来call这个callTouPiao.bat, 设定重复投1w次票
set /a i=1
:c
call c:\toupiao.bat
if %i% lss 10000 set /a i+=1&goto :c
pause
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics