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算法题 求任意一个1-9位不重复的N位数在该组合中的大小排列序号
Jul 21 Javascript
SeaJS入门教程系列之SeaJS介绍(一)
Mar 03 Javascript
jQuery的animate函数学习记录
Aug 08 Javascript
js使用onmousemove和onmouseout获取鼠标坐标的方法
Mar 31 Javascript
javascript与jquery中的this关键字用法实例分析
Dec 24 Javascript
JavaScript入门系列之知识点总结
Mar 24 Javascript
jquery常用的12个小功能
Jul 22 Javascript
浅谈javascript:两种注释,声明变量,定义函数
Oct 05 Javascript
bootstrap折叠调用collapse()后data-parent不生效的快速解决办法
Feb 23 Javascript
微信小程序自定义模态对话框实例详解
Aug 16 Javascript
使用ajax的post同步执行(实现方法)
Dec 21 Javascript
React中使用UMEditor的方法示例
Dec 27 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
php2html php生成静态页函数
2008/12/08 PHP
通过PHP CLI实现简单的数据库实时监控调度
2009/07/01 PHP
初次接触php抽象工厂模式(Elgg)
2010/03/21 PHP
php算开始时间到过期时间的相隔的天数
2011/01/12 PHP
php连接数据库代码应用分析
2011/05/29 PHP
学习php设计模式 php实现模板方法模式
2015/12/08 PHP
PHP邮件群发机实现代码
2016/02/16 PHP
学习PHP session的传递方式
2016/06/15 PHP
PHP日志LOG类定义与用法示例
2018/09/06 PHP
PDO::commit讲解
2019/01/27 PHP
jQuery实现动画效果的简单实例
2014/01/27 Javascript
JS使用JSON作为参数实例分析
2016/06/23 Javascript
Jquery Easyui选项卡组件Tab使用详解(10)
2016/12/18 Javascript
Json按某个键的值进行排序
2016/12/22 Javascript
bootstrap fileinput 上传插件的基础使用
2017/02/17 Javascript
AngularJS实现的生成随机数与猜数字大小功能示例
2017/12/25 Javascript
深入理解ES6中let和闭包
2018/02/22 Javascript
跟老齐学Python之用Python计算
2014/09/12 Python
Python使用迭代器捕获Generator返回值的方法
2017/04/05 Python
pandas.dataframe按行索引表达式选取方法
2018/10/30 Python
pytorch使用Variable实现线性回归
2019/05/21 Python
python-django中的APPEND_SLASH实现方法
2019/06/21 Python
python求最大值,不使用内置函数的实现方法
2019/07/09 Python
python 内置函数汇总详解
2019/09/16 Python
Python字符串大小写转换拼接删除空白
2019/09/19 Python
python反转列表的三种方式解析
2019/11/08 Python
解决import tensorflow as tf 出错的原因
2020/04/16 Python
解决python对齐错误的方法
2020/07/16 Python
Python同时迭代多个序列的方法
2020/07/28 Python
Python selenium如何打包静态网页并下载
2020/08/12 Python
Django自带的用户验证系统实现
2020/12/18 Python
如何掌握自荐信格式呢
2013/11/19 职场文书
领导干部群众路线个人对照检查材料思想汇报
2014/09/30 职场文书
给原生html中添加水印遮罩层的实现示例
2021/04/02 Javascript
详解Python函数print用法
2021/06/18 Python
Mysql中的触发器定义及语法介绍
2022/06/25 MySQL