casperjs加载“点击加载更多”型的网页

casperjs加载“点击加载更多”型的网页

问题描述

有些网页内容显示不全,通常会采取两种方式提供浏览。

1. 分页

分页是通过一个请求,里面带有页面号,类似于http://xxx/doc?page=1http://xxx/doc/1/,根据页面号会显示对应的页面(参考百度搜索)。这种方式的页面只需要通过拼接URL就可以爬取到。

但特殊情况,也有些页面是用ajax请求,拿到了json数据然后动态生成新的页面。这种情况我们也可以抓取json数据并解析,问题不大。

2. 瀑布流

瀑布流一般是指在同一个页面里,滚动到最下方之后,或点击“加载更多”按钮之后,请求新的数据,并追加显示在页面下方(参考朋友圈,或百度图片)。

请求方式可能也是带有页面号的,但也有可能是直接带上将要显示的条目ID。比如说https://securingtomorrow.mcafee.com/。点击”Load more”按钮之后,请求的参数是这样的:

action:filter
visible[]:64695
visible[]:65271
visible[]:65236
visible[]:65209
visible[]:64205
visible[]:64367
visible[]:64405
visible[]:64399
visible[]:64392
visible[]:64345
visible[]:64128
visible[]:64304
visible[]:64297
visible[]:64291
visible[]:64285
visible[]:64279
visible[]:64266
visible[]:64253
visible[]:64165
visible[]:64242
visible[]:64220
visible[]:64077
visible[]:64190
visible[]:64175
visible[]:64168
search:

在猜不到它生成这些ID的逻辑的情况下,我们拼接不出下一页的URL,于是只能采用模拟点击的方式了。

解决方案

phantomjs是一个不能浏览的浏览器|||-_-。。。它没有界面,但可以通过js代码来控制这个浏览器的行为,模拟拥护的点击、滚动鼠标、输入等操作。

注:phantomjs的浏览器内核是Webkit

不,我们不用phantomjs。我们用更方便的casperjs。

casperjs是一个框架,它在phantomjs的基础上封装了很多好用的操作。其test模块可以很方便地用来做网页的自动化测试。而casper模块本身提供的操作,可供我们方便地操作网页,配合文件系统fs模块,就可以很容易实现爬虫了。

casperjs的安装

casperjs的安装很容易,把它编译好的二进制文件下下来用就行了。在github https://github.com/casperjs/casperjs 里的bin目录里。但好像还没完……

casperjs依赖于phantomjs,所以还需要安装phantomjs。这个的安装也很容易,下载zip包解压出来就可以用了。下载链接:http://phantomjs.org/download.html

另外要让casperjs找到phantomjs,可以把它们放在一个目录里。。。然后配置好PATH环境变量,就可以在任何地方调用它们了。

casperjs的使用

为了避免设置环境变量这么高难度的动作,我们直接在casperjs的目录里新建一个baidu.js,然后用编辑器打开。记得把phantomjs的可执行文件也放进来! 然后写下如下代码:

// 创建casper对象
var casper = require('casper').create();
// 打开百度网站
casper.start('http://www.baidu.com/')
// (casper开始运行之后)等半秒钟然后截图存到baidupage.png里
casper.wait(500,function() {
    this.capture('baidupage.png');
})
// casper开始运行
casper.run();

然后我们在命令行里执行

casperjs baidu.js

啊?你不知道命令行是啥?那我默认你是windows用户,所以可以再在刚才到目录里新建一个baidu.bat,用编辑器打开,把上面的casperjs baidu.js 粘贴进去,保存关闭,然后双击baidu.bat。

然后我们就看到出现了一个baidupage.png,打开一看就赫然看到百度到首页赤裸裸地展现在面前啦!

casper的原理

此小节仅代表个人理解,并非真实的实现,如有误欢迎指出!

最基本的casper采用了类似于“命令模式“的设计。在casper.run()之前,casper提供的函数都是(这个”都是”用词不妥)用来添加命令的,并没有真正执行。在run()被调用时,才去一个一个的执行命令。

所以,上面的casper.wait(500, ...) 被调用时,并没有开始等待,而是添加了一个命令,命令的具体行为是等待。一直到casper.run(),执行到这条命令的时候,才开始真正的等待。

为什么要分析原理?

我们的目的是加载“点击加载更多”型的网页。当你真正开始想怎么去写的时候,才发现好像不知道怎么让它重复点击了。

https://securingtomorrow.mcafee.com/ 来说,它有个Load more按钮,点击之后它的文字会变成Loading。更多内容加载完成之后,它又变回了Load more。

我们可以用casper.click('a.btn.btn-primary') 点击Load more按钮。然后等待它变回Load more,然后再一次点击,然后再等待:

//...
// 用灵活性更高的xpath来拿到"包含Load more字样的按钮"
var xLoadMoreBtn = {type:'xpath',path:'//a[contains(text(), "Load more")]'}; 

// 点击并等待
casper.then(function() {
    this.click(xLoadMoreBtn);
});
casper.waitForSelector(xLoadMoreBtn);

// 点击并等待
casper.then(function() {
    this.click(xLoadMoreBtn);
});
casper.waitForSelector(xLoadMoreBtn);

casper.then() {
    this.capture('clicked-twice.png');
}
casper.run();

可是我们怎么实现循环呢?结束条件是什么呢?怎么让循环结束呢?这样吗(千万不要运行下面的代码,它会导致死循环)

//...
var xLoadMoreBtn = {type:'xpath',path:'//a[contains(text(), "Load more")]'};

var shouldRun = true;
while (shouldRun) {
    casper.then(function() {
        this.click(xLoadMoreBtn);
    });
    casper.waitForSelector(xLoadMoreBtn, null, function onTimeout(){
        // 等待超时之后Load more按钮仍然不出现,就退出循环
        shouldRun = false;
    });
}

casper.then() {
    this.capture('clicked-many-times.png');
}
casper.run();

在不知道命令模式的前提下,你可能觉得这样写这是没毛病的。但是它确实会死循环!跟网页无关!

因为调用then的时候,函数里的内容并没有真正开始执行(并没有去点击按钮);而且waitForSelector被调用时,并没有开始去等待元素。也不会出现等待超时!

then和waitForSelector只是在添加命令,并没有去执行命令。

在casper中,添加的动作被称为step(步骤)而非命令。

这个循环会一直不停地添加命令,一直到内存溢出,程序崩溃。

正确的”循环”

其实在casper.run() 内部已经有一个循环了,只要有命令在,它会一直去执行命令。我们只需要在命令执行完之前,再给它添加新的命令就可以了。

//...
var xLoadMoreBtn = {type:'xpath',path:'//a[contains(text(), "Load more")]'};

function addStep() {
    casper.waitForSelector(xLoadMoreBtn, function() {
        casper.capture('ok.png');
        casper.click(xLoadMoreBtn);
        addStep();
    })
}

addStep();
casper.run();

因为casper中称为step(步骤),而不是命令,我们从这里开始改口为步骤

上面的代码中,addStep会添加一个步骤,此时casper队列中只有一个步骤Step1。

casper.run()被调用时,该步骤Step1会执行,具体的动作是等待Load more按钮出现,出现之后对整个网页截图留念,然后点击按钮,并调用addStep()继续添加步骤Step2。

然后Step1执行完毕,casper.run()内部一看,我〇居然还有步骤,然后拿到步骤Step2继续执行。过程跟上述相同不再赘述。

我们是不是忘了结束循环的事了?
并没有。如果等待不到带有”Load more”字样的按钮,等待会超时,于是不会进入我写的回调函数里,也就不会再新增步骤。
casper.run()执行完当前步骤发现不再有待执行的步骤之后,自然会结束程序。

关于https的坑

casper 访问https的内容很有可能会出现各种错误。比如说卡在那不动了。比如说莫名其妙就结束了。比如说截图一看发现Forbidden了。

对于前两种错误,一般在运行的时候增加一个参数--ignore-ssl-errors=yes 就可以解决了:

casperjs --ignore-ssl-errors=yes https.js

对于Forbidden的错误,我们需要在casper.create的时候指定一个UserAgent(这个UseAgent的内容,每个浏览器不一样,可以随便百度一个都行,我这个是从我的chrome里复制出来的):

var casper = require('casper').create({
    pageSettings: { // 没有userAgent不让访问,真是大坑!
        userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36'
    }
});

关于被墙资源的坑

访问很多网站的时候,经常会有加载google流量分析,google广告等等(被墙)的资源。这些资源有时候对我们来说是不必要的,而加载它们会卡住网页的部分内容,一直到等待超时。这大大降低了我们的抓取速度。

解决这种问题,可以用类似下面的代码,让casper不加载它们:

// 避免加载被墙的资源
casper.on('resource.requested', function(reqData, req) {
    if (/facebook|google|twitter|linkedin/.test(reqData.url)) {
        req.abort();
    }
})

https://securingtomorrow.mcafee.com/ 的循环加载

贴上最后的代码:

// 封装一下xpath对象方便调用
function x(xpath) { return { type: 'xpath', path: xpath }; }
var fs = require('fs'); // 文件系统,用来保存最后的HTML
var casper = require('casper').create({
    pageSettings: { // 没有userAgent不让访问,真是大坑!
        userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36'
    },
    logLevel: 'debug',
    verbose: true
});

// 避免加载被墙的资源
casper.on('resource.requested', function(reqData, req) {
    if (/facebook|google|twitter|linkedin/.test(reqData.url)) {
        req.abort();
    }
})

casper.start('https://securingtomorrow.mcafee.com/');

function addLoadMoreStep() {
    casper.waitForSelector(x('//a[contains(text(), "Load more")]'), function() {
        console.log('button shown');
        casper.capture('ok.png');

        casper.click(x('//a[contains(text(), "Load more")]'));
        addLoadMoreStep();
    }, null, 20*1000);
}

addLoadMoreStep();
casper.run(function() {
    // 执行结束后保存HTML
    fs.write("temp.html", this.getHTML(), 'w');
});

总结

我们不仅学会了用casperjs来模拟点击加载瀑布流页面,还简单地复习了解了命令模式。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值