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:  [
        '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');
});




你还可以使用 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());
    });
});

你可能感兴趣的:(浏览器)