Node.js实现的简易网页抓取功能示例


Posted in Javascript onDecember 05, 2014

现今,网页抓取已经是一种人所共知的技术了,然而依然存在着诸多复杂性, 简单的网页爬虫依然难以胜任Ajax轮训、XMLHttpRequest,WebSockets,Flash Sockets等各种复杂技术所开发出来的现代化网站。

我们以我们在Hubdoc这个项目上的基础需求为例,在这个项目中,我们从银行,公共事业和信用卡公司的网站上抓取帐单金额,到期日期,账户号码,以及最重要的:近期账单的pdf。对于这个项目,我一开始采用了很简单的方案(暂时并没有使用我们正在评估的昂贵的商业化产品)——我以前在MessageLab/Symantec使用Perl做过的一个简单的爬虫项目。但是结果很不顺利,垃圾邮件发送者所制作的网站要比银行和公共事业公司的网站简单的多得多。

那么如何解决这个问题呢?我们主要从使用Mikea开发的优秀 request库开始。在浏览器中发出请求,并在Network窗口中查看到底发送出去了什么请求头,然后把这些请求头拷贝到代码里。这个过程很简单。仅仅是跟踪从登陆开始,到下载Pdf文件结束的这个过程,然后模拟这个过程的所有的请求而已。为了使类似的事情处理起来变得容易,并且能让网络开发者们更加合理地写爬虫程序,我把从HTML上取到结果的方把导出到jQuery中(使用轻量级 cheerio库),这使得相似的工作变得简单,也使利用CSS选择子选取一个页面中的元素变得较为简单。整个过程被包装进一个框架,而这个框架也可以做额外的工作,例如从数据库中拾取证书,加载个体机器人,和UI通过socket.io沟通。

对于一些web站点来说这个是有效的,但这仅仅是JS脚本,而不是我那个被这些公司放在他们站点上的node.js的code。他们对遗留下来的问题,针对复杂性就行分层,使得你非常难去弄明白该做什么来得到登录的信息点。对于一些站点我尝试了几天通过与request()库结合来获取,但仍是徒然。

在几近崩溃后,我发现了node-phantomjs,这个库可以让我从node中控制phantomjs headless webkit浏览器(译者注:这个我没想到一个对应的名词,headless这里的意思是渲染页面在后台完成,无需显示设备)。这看起来是一种简单的解决方案,但是还有一些phantomjs无法回避的问题需要解决:

1.PhantomJS只能告诉你页面是否完成了加载,但是你无法确定这个过程中是否存在通过JavaScript或者meta标签实现的重定向(redirect)。特别是JavaScript使用setTimeout()来延迟调用的时候。

2.PhantomJS为你提供了一个页面加载开始(pageLoadStarted)的钩子,允许你处理上面提到的问题,但是这个机能只能在你确定要加载的页面数,在每个页面加载完成时减少这个数字,并且为可能的超时提供处理(因为这种事情并不总是会发生),这样当你的数字减少为0,就可以调用你的回调函数了。这种方式可以工作,但是总让人觉得有点像是黑客手段。

3.PhantomJS每抓取一个页面需要一个完整独立的进程,因为如果不这样,无法分离每个页面之间的cookies。如果你是用同一个phantomjs进程,已经登录的页面中的session会被发送到另一个页面中。

4.无法使用PhantomJS下载资源 - 你只能将页面保存为png或者pdf。这很有用,但是这意味着我们需要求助于request()来下载pdf。

5.由于上述的原因,我必须找到一个方法来将cookie从PhantomJS的session中分发到request()的session库中去。只需要将document.cookie的字符串分发过去,解析它,然后将其注入到request()的cookie jar中去。

6.将变量注入到浏览器session中并不是件容易的事情。要这么做我需要创建一个字符串来建立一个Javascript函数。

Robot.prototype.add_page_data = function (page, name, data) {

 page.evaluate(

 "function () { var " + name + " = window." + name + " = " + JSON.stringify(data) + "}"

 );

}

7.一些网站总是充斥着console.log()之类的代码,也需要将他们重新定义,输出到我们希望的位置。为了完成这个,我这么做:
if (!console.log) {

    var iframe = document.createElement("iframe");

    document.body.appendChild(iframe);

    console = window.frames[0].console;

}

8.一些网站总是充斥着console.log()之类的代码,也需要将他们重新定义,输出到我们希望的位置。为了完成这个,我这么做:

if (!console.log) {

    var iframe = document.createElement("iframe");

    document.body.appendChild(iframe);

    console = window.frames[0].console;

}

9.告诉浏览器我点击了a标签也是件很不容易的事情,为了完成这些事情,我加入了以下的代码:
var clickElement = window.clickElement = function (id){

    var a = document.getElementById(id);

    var e = document.createEvent("MouseEvents");

    e.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);

    a.dispatchEvent(e);

 };

10.我还需要限制浏览器session的最大并发量,从而保障我们不会爆掉服务器。虽然这么说,可是这个限制要比昂贵的商业解决方案所能提供的高很多。(译者注:即商业解决方案的并发量比这个解决方案大)

所有的工作结束后,我就有一个比较体面的 PhantomJS + request 的爬虫解决方案。必须使用 PhantomJS 登录后才可以返回去 request() 请求,它将使用在 PhantomJS 中设置的 Cookie 来验证登录的会话。这是一个巨大的胜利,因为我们可以使用 request() 的流来下载 pdf文件。

整个的计划就是为了让 Web 开发者相对容易的理解如何使用 jQuery 和 CSS 选择器来创建不同 Web 网站的爬虫,我还没有成功证明这个思路可行,但相信很快会了。

Javascript 相关文章推荐
Javascript miscellanea -display data real time, using window.status
Jan 09 Javascript
使用js如何实现全选与全不选
Dec 30 Javascript
JavaScript实现按照指定长度为数字前面补零输出的方法
Mar 19 Javascript
谈谈PHP中相对路径的问题与绝对路径的使用
Aug 16 Javascript
javascript实现简单的on事件绑定
Aug 23 Javascript
JavaScript浮点数及运算精度调整详解
Oct 21 Javascript
实例浅析js的this
Dec 11 Javascript
微信小程序中时间戳和日期的相互转换问题
Jul 09 Javascript
Vue使用虚拟dom进行渲染view的方法
Dec 26 Javascript
使用PreloadJS加载图片资源的基础方法详解
Feb 03 Javascript
vue tab滚动到一定高度,固定在顶部,点击tab切换不同的内容操作
Jul 22 Javascript
解决vue自定义指令导致的内存泄漏问题
Aug 04 Javascript
浅谈js的setInterval事件
Dec 05 #Javascript
浅谈javascript中createElement事件
Dec 05 #Javascript
javascript的push使用指南
Dec 05 #Javascript
javascript结合ajax读取txt文件内容
Dec 05 #Javascript
javascript实现切换td中的值
Dec 05 #Javascript
使用Javascript简单实现图片无缝滚动
Dec 05 #Javascript
深入分析js的冒泡事件
Dec 05 #Javascript
You might like
PHP的单引号和双引号 字符串效率
2009/05/27 PHP
深入解析Laravel5.5中的包自动发现Package Auto Discovery
2017/09/13 PHP
PHP5.5基于mysqli连接MySQL数据库和读取数据操作实例详解
2019/02/16 PHP
js常见表单应用技巧
2008/01/09 Javascript
ExtJS4 动态生成的grid导出为excel示例
2014/05/02 Javascript
JS实现新浪微博效果带遮罩层的弹出框代码
2015/10/12 Javascript
Jquery easyui 实现动态树
2015/11/17 Javascript
javascript如何写热点图
2015/12/08 Javascript
弹出遮罩层后禁止滚动效果【实现代码】
2016/04/29 Javascript
Bootstrap Fileinput文件上传组件用法详解
2016/05/10 Javascript
JavaScript进阶练习及简单实例分析
2016/06/03 Javascript
AngularJS实现用户登录状态判断的方法(Model添加拦截过滤器,路由增加限制)
2016/12/12 Javascript
input获取焦点时底部菜单被顶上来问题的解决办法
2017/01/24 Javascript
JS简单实现数组去重的方法分析
2017/10/14 Javascript
集成vue到jquery/bootstrap项目的方法
2018/02/10 jQuery
vue源码解析之事件机制原理
2018/04/21 Javascript
Linux Centos7.2下安装nodejs&npm配置全局路径的教程
2018/05/15 NodeJs
vue中的inject学习教程
2019/04/24 Javascript
JavaScript函数式编程(Functional Programming)声明式与命令式实例分析
2019/05/21 Javascript
echarts统计x轴区间的数值实例代码详解
2019/07/07 Javascript
Vue实现多标签选择器
2019/11/28 Javascript
微信小程序之导航滑块视图容器功能的实现代码(简单两步)
2020/06/19 Javascript
python创建关联数组(字典)的方法
2015/05/04 Python
Python中常见的数据类型小结
2015/08/29 Python
Python中easy_install 和 pip 的安装及使用
2017/06/05 Python
Python Unittest根据不同测试环境跳过用例的方法
2018/12/16 Python
在Pandas中DataFrame数据合并,连接(concat,merge,join)的实例
2019/01/29 Python
keras 指定程序在某块卡上训练实例
2020/06/22 Python
html5 乒乓球(碰撞检测)实例二
2013/07/25 HTML / CSS
canvas学习总结三之绘制路径-线段
2019/01/31 HTML / CSS
h5页面唤起app如果没安装就跳转下载(iOS和Android)
2020/06/03 HTML / CSS
META-INF文件夹中的MANIFEST.MF的作用
2016/06/21 面试题
外企求职信范文分享
2013/12/31 职场文书
廉政承诺书
2015/01/19 职场文书
matplotlib画混淆矩阵与正确率曲线的实例代码
2021/06/01 Python
python实现一个简单的贪吃蛇游戏附代码
2022/06/28 Python