现在的位置: 首页 > 综合 > 正文

Heritrix源码分析——isUrlVisited和politeness

2013年04月12日 ⁄ 综合 ⁄ 共 2886字 ⁄ 字号 评论关闭

一、isUrlVisited

这主要是在Frontier里实现的,当一个链接要进入等待队列时需要先判断是否已经被抓取过,如果已经抓取过则不进入,否则进入。

其中最重要的部分就是存储已抓取url的结构。为了提高效率,Heritrix在内部使用了Berkeley DBBdbFrontier是唯一个具有实际意义的链接工厂

Heritrix中涉及存储url的主要的类有UriUniqFilterFPMergeUriUniqFilteSetBasedUriUniqFilterBdbUriUniqFilter BloomUriUniqFilter emFPMergeUriUniqFilterDiskFPMergeUriUniqFilter

用户可以在创建一个爬取任务时选择BdbUriUniqFilter, BloomUriUniqFilter, emFPMergeUriUniqFilterDiskFPMergeUriUniqFilter中的一种。默认是BdbUriUniqFilter

下面将分别介绍这四种UriUniqFilter

1BdbUriUniqFilter

数据结构

这里存储已经处理过的url的数据结构是Berkeley Database,叫做alreadySeen。声明代码如下:

protected transient Database alreadySeen = null;

Berkeley DB,它是一套开放源代码的嵌入式数据库。简单的说,Berkeley DB就是一个Hash Table,它能够按“key/value”方式来保存数据。使用Berkeley DB时,数据库和应用程序在相同的地址空间中运行,所以数据库操作不需要进程间的通讯。另外,Berkeley DB中的所有操作都使用一组API接口。因此,不需要对某种查询语言(比如SQL)进行解析,也不用生成执行计划,这就大大提高了运行效率。

算法

为了节省存储空间,alreadySeenUrl中存储的并不是url,而是urlfingerprint。为了不破坏url的局部性,分别对url的主机名和整个url计算fingerprint,然后把24位的主机名fingerprint40位的urlfingerprint连接起来得到最后的64位的fingerprint

计算fingerprint是在createKey函数中实现。关键代码如下如下:

CharSequence hostPlusScheme = (index == -1)? url: url.subSequence(0, index);

long tmp = FPGenerator.std24.fp(hostPlusScheme);

return tmp | (FPGenerator.std40.fp(url) >>> 24);

setAdd函数把uri加入到数据库中,如果已经存在,则返回false,否则返回true。关键代码如下:

status = alreadySeen.putNoOverwrite(null, key, ZERO_LENGTH_ENTRY);

setRemove函数把uri从数据库中删除,如果成功则返回true,否则返回false。关键代码如下:

status = alreadySeen.delete(null, key);

2BloomUriUniqFilter

数据结构

这里采用的数据结构是BloomFilter,实现版本有很多种,默认采用BloomFilter32bitSplitBloom Filter是一种空间效率很高的随机数据结构,它利用位数组很简洁地表示一个集合,并能判断一个元素是否属于这个集合。Bloom Filter的这种高效是有一定代价的:在判断一个元素是否属于某个集合时,有可能会把不属于这个集合的元素误认为属于这个集合(false positive)。

算法

初始状态时,Bloom Filter是一个包含m位的位数组,每一位都置为0。为了表达S={x1, x2,…,xn}这样一个n个元素的集合,Bloom Filter使用d个相互独立的哈希函数(Hash Function),它们分别将集合中的每个元素映射到{1,…,m}的范围中。对任意一个元素x,第i个哈希函数映射的位置hi(x)就会被置为11≤i≤k)。如果一个位置多次被置为1,那么只有第一次会起作用,后面几次将没有任何效果。

add函数实现了把url加入到Bloom Filter中的功能,关键代码如下:

boolean result = false;   //插入是否成功

int i = d, l = s.length();//i表示第几个哈希函数,lurl的长度

long h;                      //存放哈希值

while( i-- != 0 ) {

      h = hash( s, l, i );//用第i个哈希函数计算s的哈希值

      if ( ! setGetBit( h ) ) result = true;//h插入位数组中,并返回插入之前的值

}

return result;            //返回插入是否成功

在判断y是否属于这个集合时,我们对y应用k次哈希函数,如果所有hi(y)的位置都是11≤i≤k),那么我们就认为y是集合中的元素,否则就认为y不是集合中的元素。这在contains函数中实现,关键代码如下:

int i = d, l = s.length();

while( i-- != 0 )

if ( ! getBit( hash( s, l, i ) ) ) return false;

//只要有一个哈希函数的值对应的位数组值是0,就返回false

return true;

这种方式的缺陷是不能删除元素,这是由filters的工作方式决定的。

3emFPMergeUriUniqFilterDiskFPMergeUriUniqFilter

二者都继承自FPMergeUriUniqFilter,区别就在于fingerprint存放在内存中还是磁盘上。下面先来介绍一下FPMergeUriUniqFilter,再分析emFPMergeUriUniqFilterDiskFPMergeUriUniqFilter的区别。

数据结构:

数据主要有urlurlfingerprint,分别存储在不同文件中。为了便于之后的说明,采用下列符号。

UU’:存储URL的磁盘文件,一行是一个URL

TT’:存储URLfingerprint和该URLU中的顺序。

F:存储URLfingerprint

算法

首先,把urlfingerprint与缓存中的流行url和哈希表T进行比较,如果已经在其中任何一个里面,不需要进行任何操作。否则,把url存在磁盘文件U中,把fingerprint和对应的urlU中的顺序存在T中。

一旦T的大小超过了预先设置的值,把T的内容复制到T’中,重命名UU’。在其他爬虫继续工作时,T’U’的内容被分别添加到Ffrontier中,也就是merge的过程。先把T’ 根据fingerprint排序,线性合并T’F,并标记加入到F

抱歉!评论已关闭.