Heritrix源码分析——isUrlVisited和politeness

一、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的那些行。然后把T’根据顺序值排序,把U’里所有T’标记了的url加到frontier中。

FPMergeUriUniqFilter有两个重要的属性pendingSetTreeSet<PendingItem>quickCacheArrayLongFPCachependingSet就相当于上面的T’,存放着等待mergefingerprint和相应的urlquickCache缓存着最近见过的FP

FPMergeUriUniqFiltermerge的时候调用了三个抽象函数:beginFpMergeaddNewFpfinishFpMergeemFPMergeUriUniqFilterDiskFPMergeUriUniqFilter的区别就在于用不同的方法实现了这三个函数,其实质就是fingerprint存在内存中还是硬盘上。拿最简单的addNewFp函数来看一下:

emFPMergeUriUniqFilter

protected void addNewFp(long currFp) {

        newFps.add(currFp);     // newFps LongArrayList类型

}

DiskFPMergeUriUniqFilter

protected void addNewFp(long fp) {

        try {

            newFps.writeLong(fp);// newFpsDataOutputStream类型

            newCount++;

        } catch (IOException e) {

            throw new RuntimeException(e);

        }

}

politeness

    1.  one connection at a time

Heritrix的礼貌性主要在Frontier中实现:一次对一个服务器只开一个链接,并且保证uri按一定速率处理,从而不会给被爬取的服务器造成负担。

数据结构:

爬虫采用宽度优先遍历,使用FIFO的队列来存储待爬取的URL。因为网页的局部性,队列中相邻的URL很可能是相同主机名的,这样爬取会给服务器造成很大负担。如果用很多队列来存放URL,每个队列中URL的主机名相同,同一时间里,只允许队列中一个URL被爬取,就能避免上述问题了。

heritrix中主机名相同的URL队列是用WorkQueue来实现的,一个WorkQueue就是一个具有相同主机名的队列。frontier中用Map类型的allQueues存储着主机名和相应的队列;snoozedClassQueues存储着所有休眠的url队列的key,它们都按唤醒时间排序;readyClassQueues存储着已经准备好被爬取的队列的keyinactiveQueues存储着所有非活动状态的url队列的keyretiredQueues存储着不再激活的url队列的key

算法:

线程返回readyClassQueuessnoozedClassQueues中已经到唤醒时间的队列中第一个url,下载相应的文档,完成之后从队列中移除该url

每爬取到一个url都需要判断应该加入哪个队列中。 首先根据url的主机名判断是否存在该主机名的队列,如果不存在就新建一个队列。然后判断该队列是否在生命周期内,如果不在就设置为在生命周期内。如果队列需要保持不激活状态或者活动队列的数量超过设定的阈值,就把该队列放入inactiveQueues中,否则放在readyClassQueues中。

另外,heritrix还设定了很多参数来限制对服务器的访问频率。如最长等待时间max-delay-ms,默认30秒;重连同一服务器至少等待时间min-delay-ms,默认是3秒,重连同一服务器要等待上次连接至今时间间隔的几倍delay-factor,默认是5。当然这些参数用户也可以在配置爬虫的时候自己设定。

       2.  robots.txt

robots.txt称为机器人协议,放在网站的根目录下。在这个文件中声明该网站中不想被robot 访问的部分,或者指定搜索引擎只收录指定的内容。这是一个君子协定,爬虫可以不遵守,但是出于礼貌最好遵守。

heritrix在预处理阶段处理robots.txt。它把针对每个user-agentallowdisallow封装为一个RobotsDirectives类,整个robots.txt用一个Robotstxt对象来存储。

heritrix处理robots.txt有五种方法,都封装在RobotsHonoringPolicy中。这五种方法分别是:

Classic:遵守robots.txt对当前user-agent的第一部分指令。

Ignore:忽略robots.txt

Custom:遵守robots.txt中特定操作的指令。

Most-favored:遵守最宽松的指令。

Most-favored-set:给定一些user-agent格式的集合,遵守最宽松的限制。

当策略是Most-favoredMost-favored-set时,可以选择是否伪装成另一个user agent

RobotsExlusionPolicy类中包含heritrix最终处理robots.txt的方法,disallows用来判断userAgent能否访问某个url。它完全依据用户在新建一个爬虫任务时设置的处理robots.txt的策略来实现。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值