Larbin是一个用C++开发的开源网络爬虫,有一定的定制选项和较高的网页抓取速度。
下图表示了一般爬虫抓取网页的基本过程。
抓取以/Larbin.conf中的startUrl做为种子URLs开始。
下面先来看用于处理url的类:
上面的类图只显示了url类可见的接口。除了基本的构造函数和私有变量的get函数,url类比较重要的函数是hashCode( ),其实现为:
/* return a hashcode for this url */
uinturl::hashCode () {
unsigned int h=port;
unsigned int i=0;
while (host[i] != 0) {
h = 31*h + host[i];
i++;
}
i=0;
while (file[i] != 0) {
h = 31*h + file[i];
i++;
}
return h % hashSize;
}
在全局变量Globle.h中,hashTable *seen用来表示抓取中出现过的URLs。按照Larbin的默认值hashSize为64,000,000,*seen是一个大小为8,000,000的char数组的hashTable结构,该char数组*table的被置为1的bit位可以代表一个出现过的URL。
设定*table数组bit位的函数实现如下:
/* add a newurl in the hashtable
* return true if it has been added
* return false if it has already been seen */
boolhashTable::testSet (url *U) {
unsigned int code = U->hashCode();
unsigned int pos = code / 8; //该URL的hashCode在table中的索引
unsigned int bits = 1 << (code % 8); //该hashCode在字节中的bit位
int res = table[pos] & bits; //判断对应bit位是否为1,为1时res为正数
table[pos] |= bits; //将对应bit位标记为1
return !res;
}
对于网页内容的简单去重使用的是hashDup *hDuplicate,其相关实现为:
/*set a page in the hashtable
* return false if it was already there
* return true if it was not (ie it is new)*/
bool hashDup::testSet(char *doc) {
unsigned int code = 0;
char c;
for (uint i=0; (c=doc[i])!=0; i++) {
if (c>'A' && c<'z')
code = (code*23 + c) % size;
}
unsigned int pos = code / 8;
unsigned int bits = 1 << (code % 8);
int res = table[pos] & bits;
table[pos] |= bits;
return !res;
}
显然这里的网页内容去重过于简单,只能区分指向页面内容完全相同的不同链接。
为了分析和处理抓取回的网页和robot文件,Larbin中设计了file类, html类和robots类继承了file类。下面的类图显示了这几类的关系和相关函数。
html类中的inputHeaders()函数用于处理获得的网页文件的头部,包括链接、状态等。endInput函数调用其他私有函数进行一系列关于网页内容的分析和链接的提取管理操作。endInput先调用global::hDuplicate->testSet( )检测网页是否重复,再调用parseHtml()进行网页内容的分析。parseHtml( )中比较重要的是调用parseTag( )分析html标签,然后调用parseContent(action)针对标签内容进行URL的处理。parseContent(action)中调用了manageUrl( )函数,manageUrl( )调用/fetch/checker.cc中的check( )函数将URL的加入前端队列。
这些函数之间的调用关系可以在阅读源码时看到。此外,处理网页的模块还会用到一些文本操作函数,它们定义在/utils/text.h中,当然也需要url类中的相关处理函数。
接下来考虑larbin的URL队列管理。*global::URLsPriority和*global::URLsPriorityWait是用于特定类型文件下载时所需的队列,这里暂不考虑。Larbin用于一般爬取时主要用到的队列包括:
PersistentFifo*URLsDisk; //作为URL集合的前段队列,加入新的URL
PersistentFifo*URLsDiskWait; //提供对一定数量的前段URL的非阻塞访问,
//即当队列为空时不阻塞,而直接返回NULL
NamedSite *namedSiteList; //使用URL的hashCode为索引的已访问站点的大表,//维护各个站点的dns属性、对应站点待抓取URL队列
Fifo
Fifo
NamedSite*namedSiteList是维护URL队列的重要数据结构。在larbin的默认设置中,它可以维护20000个站点的待抓取队列。NamedSite类的类图如下:
可以看出,NamedSite对每个站点维护着一个fifo队列。根据不同站点的dns状态,它使用newQuery()通过adns库建立新的dns异步解析服务(DNS服务模块使用了开源的adns库,http://www.chiark.greenend.org.uk/~ian/adns),同时可以通过putUrl()和putUrlWait( )来管理URL后端队列global::*okSites的URL添加。
至此,本文最开始的第一张爬虫结构图中的主要模块及实现均做了介绍。整合在一起如下图所示:
Larbin的运行过程可以描述如下:种子URL文件最初初始化*URLsDisk,读取到namedSiteList中,通过adns库调用,逐渐往Fifo
Larbin更详细的实现和定制细节可参考源码注释。
参考资料:
1, 搜索引擎Larbin设计原理,http://blog.chinaunix.net/u/21158/showart_133214.html
2, Larbin中的队列结构,http://blog.chinaunix.net/u/21158/showart_1106639.html
3, Larbin项目,http://larbin.sourceforge.net/index-eng.html