菜鸟也想玩搜索引擎——爬虫部分技术要点浅析
本来打算昨晚发的,结果园子又迁移......
网络爬虫(Spider或Crawler),顾名思义,就是在互联网上爬行的虫子,那么这只虫子为什么要在网上爬行呢?很简单:收集信息。在互联网时代,谁掌握了信息谁就把握了主动权。曾经我一直觉得做搜索的公司都是慈善家,他们自己花钱为大众服务,真是太高尚了,直到我知道谷歌每年大半的盈利来自广告,我才明白那句名言——互联网上最昂贵的东西就是免费,因为它能让你轻易的接受,却无法舍弃。(我想多数人离开了搜索引擎,将在网络上寸步难行)
好吧,扯多了,我们先看下下图。我们可以很容易的看出,网络爬虫的根本任务就是从互联网抓取数据,存入数据库或本地文件系统以供使用。
从图中看来似乎一个网络爬虫的功能很简单,但是可不要小看这一个方框所包含的内容,它里边的很多步骤展开之后都包含着一方面技术(当然,菜鸟我就不怎么展开说了,水平不足,怕出丑),下图是一个普通爬虫的内部实现图:
如图所示,往往一个完整的怕成包含两大部分:调度部分和作业部分。其中调度部分可以看做一个总控制器(准确的说通常总控线程包含调度线程),它完成爬虫的启动初始化(配置线程数,Url集合大小,加载Url种子、当前抓取进度、已抓取集合,以及其他配置信息)、运行时调度(为作业线程分配资源)、善后处理(当url集合为空时采取的策略等),有时候还要负责与外部系统交互(如分布式的情况下,一个爬虫将作为一个作业,而调度线程则需要和总控服务器交互信息)。而作业线程就是苦逼干活的了,完成的任务如图所示。
从图中我们可以提出以下几个问题(都是粗浅的,但是对于实现一个爬虫却是基本的):
1、如何从互联网请求数据
2、作业线程的怎样实现?启动多少个合适?也调度线程如何交互?
3、如何处理抽取出来的Url(爬取策略)?
4、Url如何滤重?
下边我们就一一来讨论:
1、如何从互联网请求数据?
在讨论这个问题之前我们先来思考一个问题:为什么我们输入url之后一按回车,浏览器就能将对应的页面显示出来?多数时候浏览器根据url向服务器发起请求(一般情况下使用Http协议,不懂的建议看看帅气的肖佳大大的系列文章:http://www.cnblogs.com/TankXiao/category/415412.html 。),将对应页面下载到本地,然后浏览器的解释器将页面源码解释为我们看到的图文元素,如下图。
从上边的过程我们可以想到,我们只需要模拟浏览器想服务器发起请求即可。对于Java而言,这在多数情况下很容易实现的,Java本身提供了实现请求的对象,如HttpURLConnection,而且使用Apache的子项目HttpClient也能方便实现这些功能(当然熟悉Socket更好,一些情况下直接使用socket来实现不仅更灵活,而且能够在性能上有很大突破)。至于请求的具体实现,网上已有大量资料,但是提醒一点的是,针对对当前爬虫泛滥的情况,不少网站都在反爬虫上做了功夫(往往程序猿们爱爬的网站不想被别人爬,而且程序猿们的小爬虫通常不会遵守啥Robots.txt协议且极具破坏力,曾经就爬瘫过小网站,在此深感抱歉,希望贵网站内换个给力点的服务器以供菜鸟练手......),所以熟悉http等请求协议会使你事半功倍,至少搞定一些普通的请求没问题,我们要坚信互联网一条原则:所见必可得。
最后建议想研究这方面的菜鸟同胞们,学会使用chrome/firefix的Debugger和一些抓包工具(如fiddler2、Sniffer)是很有必要的。
2、作业线程的怎样实现?启动多少个合适?也调度线程如何交互?
想要实现一个良好的爬虫(甚至说一个可用的),必然要采用到多线程技术,这也是用java实现一个优秀系统必然设计的(很多时候你不知不觉就已经用到了)。至于实现的细节在此不再赘述。对于一个小型爬虫而言,我觉得应该至少实现三部分:调度、工作和日志(之所以强调日志是因为其对于我们掌控爬虫的状态是很有用的,一个不可控的程序听着就让人不爽)。其中调度和日志一般一个线程计可,而工作线程一般需要配置多个,这里建议小型爬虫使用java的线程池机制即可(为啥JobSearch里没有?额.....因为当时我还不会),可以节省很大功夫。
那么一般我们应该启动多少个工作线程呢?我也不知道。这是个很难回答的问题,我们知道对于软件系统的性能最终会汇集到一个词:IO(磁盘IO和网络IO),而这有和硬件密切相关的。对已一个稳健性良好的程序,在PC和机房的小型机上跑,性能差距往往很明显。那么这个问题应该如何解决呢?这里就要提到优秀爬虫应该做到的一项原则:可配置度应该高。也就是说我们应该将想要经常改变的内容变为可配置的,这点在很多开源软件中很常见。如此一来我们可以将工作线程池的大小在配置文件中定义,在部署之后根据服务器的运行情况来调整即可。
至于工作线程与调度线程如何交互,其实就是通过待抓取Url集合(通常是队列)和已抓取Url集合(用以滤重,有其他更好的替代方案,之后会讲到)。调度线程一般维护全局的Url抓取队列,而工作线程每次从中取出一条或几条进行抓取。这里需特别注意的就是线程间的资源竞争(Url),所以Url队列需特别保护(例如同步变量或者使用ThreadLocal)。
好吧,写的速度太慢了,俩小时才写了这么多。今天就先讲到这了,后两个问题改天补上吧。
最后共享一篇收藏的文章,里边讲的比我这菜鸟级的强多了。另外最经看了hadoop的一些初级的基本原理,感觉其很多实现思想对我深有启发,大家没事也可以了解下。
共享文档(版权属其原作者所有):搜索引擎系统学习与开发总结