您的位置:首页 > 理论基础 > 计算机网络

自己动手写爬虫

2016-08-21 11:08 357 查看

一:全面剖析网络爬虫

URI:

web上各种可用的资源,如HTML文档,图像,视频,程序等都由一个通用资源标志符(URI)进行定位。

URI通常三部分组成:

1.访问资源的命令机制

2.存放资源的主机名

3.资源自身的名称,由路径表示

文件的URL:

用URL表示文件时,服务器方式用file表示,后面要有IP地址,文件的存取路径(即目录)和文件名等信息。有时可省略目录和文件名,但“/”符号不能省略

大致步骤:

GET:



POST:



与get方法不同,post方法可以使用NameValuePair来设置参数,因此可以设置“无限”多的参数。而get方法采用把参数写在URL里面的方式,由于URL有长度限制,因此传递参数的长度也会有限制。



宽度优先爬虫:

实际的爬虫项目是从一系列的种子链接开始的。所谓的种子链接,就好比宽度优先遍历中的种子结点一样。但是种子链接可以有多个。

带偏好的爬虫:

判断网页的重要性的因素有很多,主要有链接的欢迎度,链接的重要度和平均链接深度,网站质量,历史权重等主要因素。

链接的欢迎度主要是由反向链接(backlinks,即指向当前URL的链接)的数量和质量决定的,定义为IB(P)。

链接的重要度,是一个关于URL字符串的函数,仅仅考察字符串本身,比如认为“.com”和“home”的URL重要度比“.cc”和”map“高,定义为IL(P)。

平均链接深度,根据上面所分析的宽度优先原则计算出全站的平均链接深度,然后距离种子站点越近的重要性越高,定义为ID(P)。

定义网页的重要性为I(P),那么:

I(P) = X*IB(P)+Y*IL(P)

X,Y表示所占比例大小,ID(P)由宽度优先的遍历规则保证。

如何实现最佳爬虫呢?

可以使用优先队列来实现TODO表,并且把每个URL的重要性作为队列元素的优先级。

在带偏好的爬虫里,队列元素的优先级是由URL的优先级确定的。关于如何确定URL的优先级,有一些专用的链接分析的方法,比如google的PangRank和HITS算法

爬虫队列:

爬虫队列的设计是网络爬虫的关键。爬虫队列是用来保存URL的队列数据结构的。数以亿计的URL地址,使用内存的链表或者队列来存储显然不够。需要找到另一种数据结构,具有以下特点:

1.能够存储海量数据,当数据超出内存限制时,能够固化到硬盘上。

2.存储数据速度非常快.

3.能够支持多线程访问。

3点要求,使得Hash成为存储结构的不二选择。

在进行Hash存储的时候,为了节省空间,通常会对URL进行压缩。常用的压缩算法是MD5算法。

通过布隆过滤器构建Visited表:

在企业级搜索引擎中,常用一个成为布隆过滤器(bloom filter)的算法来实现对已经抓取过的URL的过滤。

设计爬虫架构:

1.分布式:能在多台机器上运行

2.可伸缩性:能过通过额外的机器和带宽来提高抓取速度

3.性能和有效性:有效利用各种系统资源,例如,处理器、存储空间和网络带宽。

4.质量:爬虫应该首先抓取有用的网页

5.新鲜性:在很多应用中,爬虫应该持续运行而不是只遍历一次。

6.更新

7.可扩展性:为了能够支持新的数据格式和新的抓取协议,爬虫架构应该设计成模块化的形式。

实际爬虫逻辑架构参考:

1.URL Frontier包含爬虫当前待抓取的URL(对于持续更新抓取的爬虫,以前已经抓取过的URL可能会回到Frontier重抓)。

2.DNS解析模块根据给定的URL决定从哪个Web服务器获取网页。

3.获取模块使用HTTP协议获取URL代表的页面。

4.解析模块提取文本和网页的链接集合

5.重复消除模块决定一个解析出来的链接是否已经在URL Frontier或者最近下载过。

示意图:



DNS解析是网络爬虫的瓶颈。由于域名服务的分布式特点,DNS可能需要多次请求转发,并在互联网上往返,需要几秒甚至更长的时间解析出IP地址。如果我们的目标是一秒钟抓取数百个文件,这样就达不到性能要求。一个标准的补救措施是引入缓存:最近完成DNS查询的网址可能会在DNS缓存中找到,避免了访问互联网上的DNS服务器。

用DNS解析还有一个难点:在标准库中实现的查找是同步的。这意味着一旦一个请求发送到DNS服务器上,在哪个节点上的其他爬虫线程也被阻塞知道第一个请求完成。为了避免这种情况发生,很多爬虫自己来实现DNS解析。

整个爬虫系统可以由一台抓取机器或多个爬虫节点组成。多机并行抓取的分布式爬虫这需要考虑节点之间的通信和调度。

在一个爬虫节点上实现并行抓取,可以考虑:

1.多线程同步IO

2.单线程异步IO

JAVA多线程:

多线程是一种机制,它允许在程序中并发执行多个指令流,每个指令流都称为一个线程,彼此间相互独立。

线程又称为轻量级进程,它和进程一样拥有独立的执行控制,由操作系统负责调度,区别是线程没有独立的存储空间,而是和所属进程中的其他线程共享存储空间,这使得线程间的通信较进程简单。

多个线程的执行是并发的,即在逻辑上是“同时”的。如果系统只有一个CPU,那么真正的“同时”是不可能的,但是由于CPU切换的速度非常快,用户感觉不到其中的区别。

线程的状态,一个线程具有如下四种状态:

1.新状态:线程已被创建但尚未执行(start()方法尚未被调用)

2.可执行状态:线程可以执行,但不一定正在执行。CPU时间随时可能被分配给该线程,从而使得它执行

3.死亡状态:正常情况下,run()方法返回使得线程死亡。调用Thread类中的stop()或destroy()方法亦有同样效果,但是不推荐使用这两种方法,前者会产生异常,后者是强制终止,不会释放锁。

4.阻塞状态:线程不会被分配CPU时间,无法执行。

在java语言中,每个对象都拥有一把锁,当执行这个对象的synchronized方法时,必须获得该对象的锁方能执行,否则所属线程阻塞。而synchronized方法一旦执行,就独占该锁,直到从synchronized方法返回时才将锁释放,之后被阻塞的线程方能获得该锁,重新进入可执行状态。

这种对象锁机制确保同一时刻对于每一对象,其所有声明为synchronized的方法中至多只有一个处于可执行状态,从而有效地避免了类成员变量的访问冲突。

在java中,不光是对象,每一个类也对应一把锁,因此也可将该类的静态成员函数声明为synchronized,以控制其对类的静态成员变量的访问。

(1)synchronized方法

(2)synchronized块

语法:

synchronize(syncObject){
//允许访问控制的代码
}


块的代码必须获得syncObject对象(可以是类实例或类)的锁才能执行。

二:分布式爬虫

Hadoop是Apache下的一个分布式并行计算框架。Hadoop的核心设计思想是MapReduce和HDFS。

Hadoop中MapReduce的三个步骤:

1.map(主要是分解并行的任务)

2.combine(主要是为了提高reduce的效率)

3.reduce(把处理后的结果再汇总起来)

1.map

由于map是并行地对输入的文件集进行操作,所以它的第一步就是把文件集分割成一些子集。如果单个的文件大到影响查找效率时,它会被分割成一些小的文件。要指出的是,分割这一步是不知道输入文件到额内部逻辑结构的。比如,以行为逻辑分割的文本文件会被以任意的字节界限分割,所以这个具体分割要由用户自己指定。然后每个文件分割体都会对应地有一个新的map任务。

当单个map任务开始时,它会对每个配置过的reduce任务开启一个新的输出流,这个输出流会读取文件分割体。Hadoop中的类InputFormat用于分析输入文件并产生键值(key/value)对。

Hadoop中的Mapper类是一个可以由用户实现的类,经过InputFormat类分析的键值(key/value)对都传给Mapper类,这样用户提供的Mapper类就可以进行真正的map操作。

当map操作的输出被收集后,他们会被Hadoop中Partitioner类以指定的方式区分地写入输出文件里。

2.combine

当map操作输出它的键值(key/value)对时,处于性能和效率的考虑,Hadoop框架提供了一个合成器(combine)。有了这个合成器,map操作所产生的键值(key/value)对就不会马上写入输出文件,他们会被收集在一些list中,一个key值对应一个list,当写入一定数量的键值(key/value)对时,这部分list会被合成器处理。

比如,Hadoop案例中的wordCount程序,它的map操作输出是(word,l)键值对,在map操作的输入中,词的计数可以使用合成器来加速。合成操作会在内存中收集处理list,一个词一个list。当一定数量的键值对输出到内存中时,就调用合成操作的reduce方法,每次都以一个唯一的词为key,values是list的迭代器,然后合成器输出(word,count-in-this-part-of-the-input)键值对。

3.reduce

当一个reduce任务开始时,它的输入分散在各个节点的map的输出文件里。如果在分布式的模式下,需要先把这些文件拷贝到本地文件系统上。

一旦所有的数据都被拷贝到reduce任务所在的机器上时,reduce任务会把这些文件合并到一个文件中。然后这个文件会被合并分类,使得相同的key的键值对可以排在一起。接下来的reduce操作就很简单了,顺序地读入这个文件,将键(key)所对应的值(value)传给reduce方法完成之后再读取一个键(key)。

最后,输出由每个reduce任务的输出文件组成。而他们的格式可以由JobConf.setOutputFormat类指定。

三:爬虫的“方方面面 ##”

设计爬虫时,通常都会回避动态网页,因为动态网页(识别动态网页,看URL中是否出现问号)会将爬虫带入“黑洞”。

一般的爬虫是“见网页就抓”,而主题爬虫只抓取和主题“相关”的页面。

主题爬虫

如何控制抓取的网页和主题相关呢?

解决思路有四种:

1.第一种,通常指一些行业搜索。比如机票搜索等。航空公司和代理人网站的数量有限。因此,抓取的时候可以根据这些网站做定制抓取。也就是根据每个网站的页面内容,分析他们的源代码的结构,获取自己需要的信息,存入数据库中。这种方法适合小型的行业搜索引擎。

2.第二种,根据得到的网页的内容,判断网页的内容和主题是否相关。如果一个网页是和主题相关的,在网页中的标题、正文、超链接中,通常会有一些与主题相关的关键词。在面向主题的搜索中,这种词叫做导向词,给每个导向词一个权重,就能够优先访问和主题相关的URL。

导向词设置权重的方法:

1.管理员的经验手工设置

2.“特征提取”

导向词算法在对一个URL进行评分的时候,总是要先下载URL指向的页面,然后对URL进行评分,放入TODO表中进行处理。当扩展这个URL的时候,还要进行一次下载,这样对网络资源的消耗非常大。这是可以采用一种阈值的方法进行处理。即通过经验设定一个阈值,如果得出的评分大于这个阈值,就立刻进行下一步处理,并且先不对从当前页面中提取出的URL进行评分计算,待到扩展的时候再进行评分,否则,简单丢弃这个页面即可。

3.第三种,针对网页链接进行评分(这种方法只是根据当前爬虫爬取的信息对当前的URL进行评分,而不涉及当前网页的内容)

4.第四种,链接描述文本分析。

主题爬虫处理流程:



限定爬虫

限定爬虫就是对爬虫所爬取的主机范围做一些限制,通常,限定爬虫包含以下几个方面。

1.限定域名。例,只抓取edu.cn结尾的域名

2.限定爬取层数。

3.限定IP。例,只抓取中国范围内的IP

4.限定语言的抓取。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息