熟悉我的人都知道,我还算是一个比较务实的人,所以我很少用类似本文这种有些哗众取宠的标题的。但,偶尔用用还是挺爽滴~

场景描述

基本上当今做web开发的,不可能没有自己写过或接触过 采集目标web页面 这个领域的相关工具。总之,你可以在搜索引擎中找到大量的关于“采集”主题的技术分享。采集,也叫“偷”,被采集的数据往往对采集者来说是有价值的,而无偿使用这些数据,我个人感觉和偷没有啥本质区别。我们这里从纯技术角度来讨论一下这种偷的技巧。我还是很支持知识付费的,只不过一些老一点的平台往往不提供接口供第三方获取数据,这个时候,就只能用技术手段来采集数据了。

如果你采集淘宝这种网站的商品信息,其实还是比较容易的,因为不需要必须登录系统嘛。所以,你可以简单的模拟首页请求,然后解析html,根据得到的目标链接进行迭代请求(这里没有特别强调目标系统的防御措施)。当然,至于是深度还是广度遍历优先,需要根据你自己的需要来选择了,当然还有一些其它的技术细节,如并发数控制,请求失败处理,连接池,甚至是代理IP库等等。这些都可以在网上找到对应的功能库或资料文献。

如果你采集的系统并不是公开的网站,而是管理系统类型的站点,那就意味着你要先想办法模拟登录到目标系统中。这里我们假设系统不要求输入任何类型的验证码(验证码识别并非我的强项,一般遇到这种情况我都是直接放弃,谁知道好的验证码识别方案,不妨留言给我哟~),而且我们又拥有对应的合法帐号。

接下来就可以开始捋起袖子开始采集了。

所需环境

正如我们标题所说,这里我们使用casperjs,之所以没有使用简单的模拟请求方案,是因为被采集的WEB系统往往存在很多前端javascript的逻辑。如果你直接发起一个http请求,拿到的很可能是一些无用的html数据而已(目前大量的使用了前端框架的网站都这样)。所以,我们需要一个支持编程接口的虚拟浏览器,由于之前我做过前端端到端测试的尝试,所以自然就想到了casperjs

实战

这篇文章并不打算事无巨细的讲解casperjs要怎么用,毕竟它的官方文档非常的简单,而且我之前的文章也有比较详细的介绍过这些内容。这篇文章的目标主要是讨论几个关键问题点,并给出我的一个解决方案。

模拟点击

其实这个方面,之前文章也有提到过。在模拟访问某些系统的时候,casperjs提供的click方法并不总是有效。我不能确定具体原因,但貌似像一些div, span等这样的元素上进行模拟点击会存在失败的可能。

这个时候,我会尝试使用casper提供的mouse库,不要问我为什么,总之是会管用的。

模拟滚动

针对上面的模拟点击话题,有这么一种情况:如果当前页面中该区域并不处于可视区内,那么对它的模拟点击可能是无效的。这是什么意思呢?举个例子,下拉菜单里往往会有很多项,一般情况下如果选择项太多,菜单展开后都会有滚动条,对吧?你如果直接尝试模拟选择菜单中最下面的项目(需要滚动才能肉眼看到的选项),你可能会失败。这个时候就需要模拟滚动了。

了解到这一点后,剩下的就是如何模拟滚动了。casper提供的scroll***方法都是滚动整个页面区域,而我们想滚动特定区域的话,就得自己想办法了。当然,前面提到的mouse模块值得一试,我在之前的文章中也是这么做的,只需要模拟鼠标按下(down),移动(move),松开鼠标(up)行为即可。不过,还有更简单的方案可以考虑,就是直接使用casper提供的[evaluate方法(http://docs.casperjs.org/en/latest/modules/casper.html#evaluate)。

该方法本身并不是用来做这件事儿的,不过该方法允许我们注入我们的逻辑到目标页面中去,所以,我们可以借助其它js库的帮助来完成滚动行为,例如jquery.scrollTop。那如果目标网站自身并没有使用jquery怎么办?我们一样可以将目标库注入到页面中

模拟下载文件

本文的重点到了,如果我们想在目标网站上下载特定文件,该怎么办?简单点的模拟下载,只需要直接使用casper.download方法即可。

不过,如果下载链接无法直接获得的话,就需要我们耍点手段了。既然目标页面中附件的下载链接不是静态写在html中的,那么必然使用了js处理。如果这些js代码可读性比较好,我们依然可以通过分析来理清楚这些js的业务逻辑,最终自行手动拿到附件的目标链接地址。

但是,往往目标网站的js代码都是压缩混淆过的,基本上没有可读性。再复杂点的话,这些js逻辑还需要借助ajax请求来计算数据。如果我们拿不到附件的链接,我们就无法使用casper.download方法。而直接模拟点击下载链接的话。casper是不会帮你将附件下载到你的目标位置的,这里我不能确定是否下载到里某个casper的默认位置,知道的童鞋请告诉我哟~

接下来我们该怎么办?我相信很多人又遇到过这个问题,解决方案多种多样,我这里分享一个我的思路。我遇到的实际情况是,前端js压缩混淆了,而且我经过分析发现,它会发送一个ajax去获取目标文件的id用做生成下载链接。

而我们需要的,就是模拟这个ajax请求去拿到对应的返回结果。不过这个ajax请求还携带了相当多的参数,这就增加了模拟请求的难度。拿到这些参数,我们可以使用casper提供的resource.requested事件HOOK

按说我们可以拿到请求携带的参数,也应该同时可以拿到请求的响应结果才对,不过奇葩的是,casper并不提供获取响应结果的接口。我实在搞不明白这是因为什么理由,毕竟你已经提供了大量的接口来让用户模拟浏览器操作,而且提供的事件也围绕着请求的各个环节,也确实发送了请求。但怎么就不老老实实的把响应数据也提供出来呢?实在让人无语啊。

既然无法通过casper提供的现有方法来拿响应结果,我们只能再耍一些手段了。通过获取到的请求参数,我们再次使用evaluate方法,在页面中注入jQuery.ajax逻辑,并将ajax的返回结果写入到自己创建的Dom节点中。

最后通过casper.waitForSelector方法拿到我们想要的响应结果。
到这一步,基本上就搞定了下载文件的需求了。

选择器

casper提供的很多方法都接受“选择器”参数,用起来非常方便。不过有的网站并没有提供可供使用的id或class属性,甚至我还碰到过整个html中所有id和class都是随机生成的网站。

这个时候,我们就需要用一种麻烦的方法来搞定它了。使用evaluate方法注入jquery逻辑来定位目标Dom,并获取它的动态id或class属性值,并返回给casper。可能有更聪明的方法,等待高手赐教。

结束语

这篇文章有点标题党了,不过还是有点价值的,不是么?