`
阅读更多
通过简单的抓取演示,有必要对Heritrix框架的架构进行一些了解。通过搜索相关资料并整合如下。其他相关内容网址:
http://blog.sina.com.cn/s/blog_5484ad0d01008gox.html
http://guoyunsky.iteye.com/category/82971
http://blog.csdn.net/gris0509/archive/2009/11/15/4812641.aspx

Heritrix的架构图如下:


在以上两个博客里,很详细的介绍了Heritrix的结构,可以帮助理解Heritrix的工作原理。个人觉得在理解中最重要的有如下几点:

1. Frontier链接制造工厂
首先,Frontier是用来向线程提供链接的,因此,在上面的代码中(在我所列出的第一个博客中的Heritrix的架构 -2 中,有代码),使用了两个ArrayList来保存链接。其中,第一个pendingURIs保存的是等待处理的链接,第二个 prerequisites中保存的也是链接,只不过它里面的每个链接的优先级都要高于pendingURIs里的链接。通常,在 prerequisites中保存的都是如DNS之类的链接,只有当这些链接被首先解析后,其后续的链接才能够被解析。
其次,除了这两个ArrayList外,在上面的 Frontier还有一个名称为alreadyIncluded的HashMap。它用于记录那些已经被处理过的链接。每当调用Frontier的 schedule()方法来加入一个新的链接时,Frontier总要先检查这个正要加入到队列中的链接是不是已经被处理过了。
很显然,在分析网页的时候,会出现大量相同的链 接,如果没有这种检查,很有可能造成抓取任务永远无法完成的情况。同时,在schedule()方法中还加入了一些逻辑,用于判断当前要进入队列的链接是 否属于需要优先处理的,如果是,则置入prerequisites队列中,否则,就简单的加入pendingURIs中即可。
注意:Frontier中还有两个关键的方法,next()和 finished(),这两个方法都是要交由抓取的线程来完成的。Next()方法的主要功能是:从等待队列中取出一个链接并返回,然后抓取线程会在它自 己的run()方法中完成对这个链接的处理。而finished()方法则是在线程完成对链接的抓取和后续的一切动作后(如将链接传递经过处理器链)要执 行的。它把整个处理过程中解析出的新的链接加入队列中,并且在处理完当前链接后,将之加入alreadyIncluded这个HashMap中去。

但是在Frontier实现的过程中,要面临很多问题,其中就有“大数据量,多并发”的存在。所以,在Frontier实现时,考虑了Berkeley Database,即bdb(缩写)。

简单的说,Berkeley DB就是一个HashTable,它能够按“key/value”方式来保存数据。它是由美国Sleepycat公司开发的一套开放源代码的嵌入式数据库,它为应用程序提供可伸缩的、高性能的、有事务保护功能的数据管理服务。
那么,为什么不使用一个传统的关系型数据库呢? 这是因为当使用BerkeleyDB时,数据库和应用程序在相同的地址空间中运行,所以数据库操作不需要进程间的通讯。然而,当使用传统关系型数据库时, 就需要在一台机器的不同进程间或在网络中不同机器间进行进程通讯,这样所花费的开销,要远远大于函数调用的开销。
另外,Berkeley DB中的所有操作都使用一组API接口。因此,不需要对某种查询语言(比如SQL)进行解析,也不用生成执行计划,这就大大提高了运行效率。
当然,做为一个数据库,最重要的功能就是事务的 支持,Berkeley DB中的事务子系统就是用来为其提供事务支持的。它允许把一组对数据库的修改看作一个原子单位,这组操作要么全做,要么全不做。在默认的情况下,系统将提 供严格的ACID事务属性,但是应用程序可以选择不使用系统所作的隔离保证。该子系统使用两段锁技术和先写日志策略来保证数据的正确性和一致性。这种事务 的支持就要比简单的HashTable中的Synchronize要更加强大。
注意:在Heritrix中,使用的是Berkeley DB的Java版本,这种版本专门为Java语言做了优化,提供了Java的API接口以供开发者使用。

为什么Heritrix中要用到Berkeley DB呢?这就需要再回过头来看一下Frontier了。
在上一小节中,当一个链接被处理后,也即经过处 理器链后,会生成很多新的链接,这些新的链接需要被Frontier的一个schedule方法加入到队列中继续处理。但是,在将这些新链接加入到队列之 前,要首先做一个检查,即在alreadyIncluded这个HashMap中,查看当前要加入到队列中的链接是否在先前已经被处理过了。
当使用HashMap来存储那些已经被处理过的链接时,HashMap中的key为url,而value则为一个对url封装后的对象。很显然的,这里有几个问题。
l  对这个HashMap的读取是多线程的,因为每个线程都需要访问这个HashMap,以决定当前要加入链接是否已经存在过了。
l  对这个HashMap的写入是多线程的,每个线程在处理完毕后,都会访问这个HashMap,以写入最新处理的链接。
l  这个HashMap的容量可能很大,可以试想,一次在广域网范围上的网页抓取,可能会涉及到上十亿个URL地址,这种地址包括网页、图片、文件、多媒体对象等,所以,不可能将这么大一张表完全的置放于内存中。
综合考虑以上3点,仅用一个HashMap来保 存所有的链接,显然已经不能满足“大数据量,多并发”这样的要求。因此,需要寻找一个替代的工具来解决问题。Heritrix中的BdbFrontier 就采用了Berkeley DB,来解决这种URL存放的问题。事实上,BdbFrontier就是Berkeley DB Frontier的简称。
为了在BdbFrontier中使用Berkeley DB,Heritrix本身构造了一系列的类来帮助实现这个功能。这些类如下:
l  BdbFrontier
l  BdbMultipleWorkQueues
l  BdbWorkQueue
l  BdbUriUniqFilter
上述的4个类,都以Bdb3个字母开头,这表明它们都是使用到了Berkeley DB的功能。其中:
(1)BdbMultipleWorkQueues代表了一组链接队列,这些队列有各自不同的key。这样,由Key和链接队列可以形成一个“Key/Value”对,也就成为了Berkeley DB里的一条记录(DatabaseEntry)
可以说,这就是一张Berkeley DB的数据库表。其中,数据库的一条记录包含两个部分,左边是一个由右边的所有URL链接计算出来的公共键值,右边则是一个URL的队列。
(2)BdbWorkQueue代表了一个基于 Berkeley DB的队列,与BdbMutipleWorkQueues所不同的是,该队列中的所有的链接都具有相同的键值。事实上,BdbWorkQueue只是对 BdbMultipleWorkQueues的封装,在构造一个BdbWorkQueue时,需传入一个健值,以此做为该Queue在数据库中的标识。事 实上,在工作线程从Frontier中取出链接时,Heritrix总是先取出整个BdbWorkQueue,再从中取出第一个链接,然后将当前这个 BdbWorkQueue置入一个线程安全的同步容器内,等待线程处理完毕后才将该Queue释放,以便该Queue内的其他URI可以继续被处理。
(3)BdbUriUniqFilter是一个 过滤器,从名称上就能知道,它是专门用来过滤当前要进入等待队列的链接对象是否已经被抓取过。很显然,在BdbUriUniqFilter内部嵌入了一个 Berkeley DB数据库用于存储所有的被抓取过的链接。它对外提供了
public void add(String key, CandidateURI value)
这样的接口,以供Frontier调用。当然,若是参数的CandidateURI已经存在于数据库中了,则该方法会禁止它加入到等待队列中去。
(4)BdbFrontier就是 Heritrix中使用了Berkeley DB的链接制造工厂。它主要使用BdbUriUniqFilter,做为其判断当前要进入等待队列的链接对象是否已经被抓取过。同时,它还使用了 BdbMultipleWorkQueues来做为所有等待处理的URI的容器。这些URI根据各自的内容会生成一个Hash值成为它们所在队列的键值。
在Heritrix1.10的版本中,可以说BdbFrontier是惟一一个具有实用意义的链接制造工厂了。虽然Heritrix还提供了另外两个Frontier:
org.archive.crawler.frontier.DomainSensitiveFrontier
org.archive.crawler.frontier.AdaptiveRevisitFrontier
但是, DomainSensitiveFrontier已经被废弃不再推荐使用了。而AdaptiveRevisitFrontier的算法是不管遇到什么新链 接,都义无反顾的再次抓取,这显然是一种很落后的算法。因此,了解BdbFrontier的实现原理,对于更好的了解Heritrix对链接的处理有实际 意义。
BdbFrontier的代码相对比较复杂,笔者在这里也只能简单将其轮廓进行介绍,读者仍须将代码仔细研读,方能把文中的点点知识串联起来,进而更好的理解Heritrix作者们的巧妙匠心。

2. Heritrix的多线程ToeThread和ToePool
想要更有效更快速的抓取网页内容,则必须采用多线程。Heritrix中提供了一个标准的线程池ToePool,它用于管理所有的抓取线程。
ToePool和ToeThread都位于 org.archive.crawler.framework包中。前面已经说过,ToePool的初始化,是在CrawlController的 initialize()方法中完成的。(本条内容参考我列出的博客中第一条中的Heritrix的架构 -3)

3. 处理链和Processor(非常重要)
处理器链包括以下几种:
l  PreProcessor
l  Fetcher
l  Extractor
l  Writer
l  PostProcessor
为了很好的表示整个处理器链的逻辑结构,以及它们之间的链式调用关系,Heritrix设计了几个API来表示这种逻辑结构。
org.archive.crawler.framework.Processor
org.archive.crawler.framework.ProcessorChain
org.archive.crawler.framework.ProcessorChainList

下面进行详细讲解。
(1) Processor类
该类代表着单个的处理器,所有的处理器都是它的子类。在Processor类中有一个process()方法,它被标识为final类型的,也就是说,它不可以被它的子类所覆盖。代码如下。
代码10.8
public final void process(CrawlURI curi) throws InterruptedException
{
    // 设置下一个处理器
    curi.setNextProcessor(getDefaultNextProcessor(curi));
    try
    {
       // 判断当前这个处理器是否为enabled
       if (!((Boolean) getAttribute(ATTR_ENABLED, curi)).booleanValue()) {
        return;
       }
    } catch (AttributeNotFoundException e) {
       logger.severe(e.getMessage());
    }
    // 如果当前的链接能够通过过滤器调用innerProcess(curi)方法来进行处理
    if(filtersAccept(curi)) {
       innerProcess(curi);
    }
    // 如果不能通过过滤器检查,则调用innerRejectProcess(curi)来处理
    else
    {
       innerRejectProcess(curi);
    }
}
方法的含义很简单。即首先检查是否允许这个处理器处理该链接,如果允许,则检查当前处理器所自带的过滤器是否能够接受这个链接。当过滤器的检查也通过后,则调用innerProcess(curi)方 法来处理,如果过滤器的检查没有通过,就使用innerRejectProcess(curi)方法处理。
其中innerProcess(curi)和 innerRejectProcess(curi)方法都是protected类型的,且本身没有实现任何内容。很明显它们是留在子类中,实现具体的处理逻辑。不过大部分的子类都不会重写innerRejectProcess(curi)方法了,这是因为反正一个链接已经被当前处理器拒绝处理了,就不用再有什么逻辑了,直接跳到下一个处理器继续处理就行了。
(2) ProcessorChain类
该类表示一个队列,里面包括了同种类型的几个Processor。例如,可以将一组的Extractor加入到同一个ProcessorChain中去。
在一个ProcessorChain中,有3个private类型的类变量:
private final MapType processorMap;
private ProcessorChain nextChain;
private Processor firstProcessor;
其中,processorMap中存放的是当前 这个ProcessorChain中所有的Processor。nextChain的类型是ProcessorChain,它表示指向下一个处理器链的指 针。而firstProcessor则是指向当前队列中的第一个处理器的指针。
3.ProcessorChainList
从名称上看,它保存了Heritrix一次抓取 任务中所设定的所有处理器链,将之做为一个列表。正常情况下,一个ProcessorChainList中,应该包括有5个 ProcessorChain,分别为PreProcessor链、Fetcher链、Extractor链、Writer链和 PostProcessor链,而每个链中又包含有多个的Processor。这样,就将整个处理器结构合理的表示了出来。
那么,在ToeThread的processCrawlUri()方法中,又是如何来将一个链接循环经过这样一组结构的呢?请看下面的代码(在org.archive.crawler.framework.ToeThread.java中):
代码10.9
private void processCrawlUri() throws InterruptedException {
   // 设定当前线程的编号
   currentCuri.setThreadNumber(this.serialNumber);
   // 为当前处理的URI设定下一个ProcessorChain
   currentCuri.setNextProcessorChain(controller.getFirstProcessorChain());
  
   // 设定开始时间
   lastStartTime = System.currentTimeMillis();
   try {
  
      // 如果还有一个处理链没处理完
      while (currentCuri.nextProcessorChain() != null)
      {
         setStep(STEP_ABOUT_TO_BEGIN_CHAIN);
        
         // 将下个处理链中的第一个处理器设定为
         // 下一个处理当前链接的处理器
         currentCuri.setNextProcessor(currentCuri
                                      .nextProcessorChain().getFirstProcessor());
         // 将再下一个处理器链设定为当前链接的
         // 下一个处理器链,因为此时已经相当于
         // 把下一个处理器链置为当前处理器链了
         currentCuri.setNextProcessorChain(currentCuri
                                      .nextProcessorChain().getNextProcessorChain());
                                     
         // 开始循环处理当前处理器链中的每一个Processor
         while (currentCuri.nextProcessor() != null)
         {
            setStep(STEP_ABOUT_TO_BEGIN_PROCESSOR);
            Processor currentProcessor = getProcessor(currentCuri.nextProcessor());
            currentProcessorName = currentProcessor.getName();
            continueCheck();
            // 调用Process方法
            currentProcessor.process(currentCuri);
         }
      }
      setStep(STEP_DONE_WITH_PROCESSORS);
      currentProcessorName = "";
   }
   catch (RuntimeExceptionWrapper e) {
       // 如果是Berkeley DB的异常
       if(e.getCause() == null) {
           e.initCause(e.getDetail());
       }
       recoverableProblem(e);
   } catch (AssertionError ae) {
       recoverableProblem(ae);
   } catch (RuntimeException e) {
       recoverableProblem(e);
   } catch (StackOverflowError err) {
       recoverableProblem(err);
   } catch (Error err) {
       seriousError(err);
   }
}

代码使用了双重循环来遍历整个处理器链的结构,第一重循环首先遍历所有的处理器链,第二重循环则在链内部遍历每个Processor,然后调用它的process()方法来执行处理逻辑。
在controller中有5个chain,当处理的时候,按照顺序分别调用各个chain,在调用每个chain的时候,使用chain中的processor(如currentCuri.setNextProcessor(currentCuri.nextProcessorChain().getFirstProcessor());),这样一步步的使用controller中的processChain,使用processChain中的processor。

4.网络爬虫的整体结构可以参考:http://hanyuanbo.iteye.com/blog/779350
  • 大小: 68.8 KB
分享到:
评论
1 楼 不要叫我杨过 2016-03-28  
受教了,高手

相关推荐

    网络爬虫Heritrix1.14.4可直接用

    2. **Heritrix架构**:Heritrix采用模块化设计,包括种子管理器、URI调度器、爬取策略、处理器链、存储模块等。每个模块都有其特定功能,如种子管理器负责管理起始抓取URL,调度器负责控制爬取速率和优先级。 3. **...

    Heritrix源码分析

    11. `org.archive.crawler.framework`:Heritrix的框架包,定义了核心类如CrawlController(爬虫控制器)和Frontier(调度器),是整个系统架构的基础。 12. `org.archive.crawler.framework.exceptions`:框架异常...

    heritrix爬虫安装部署

    Heritrix的设计初衷是为了满足大规模网页归档的需求,但因其灵活的架构和丰富的API,也被广泛应用于数据挖掘、搜索引擎优化等领域。 #### 二、Heritrix下载、安装与配置 ##### 2.1 下载 - **下载地址**: 通常可以从...

    heritrix-3.1.0 最新jar包

    - **模块化架构**:Heritrix的组件可以通过配置文件进行添加、删除或修改,如爬行策略、解析器、存儲策略等,提供了极大的灵活性。 - **爬行策略**:Heritrix支持多种爬行策略,如深度优先、广度优先,甚至可以...

    heritrix正确完整的配置heritrix正确完整的配置

    这可能涉及Java编程,需对Heritrix的架构有深入理解。 9. **异常处理与恢复**: 配置如何处理网络错误、服务器拒绝等问题,以及在中断后如何恢复爬取。 10. **性能优化**: 考虑并发数、重试策略、DNS缓存等,以提高...

    heritrix源码

    2. **模块化架构**:Heritrix的核心组件包括启动器、管道(Pipeline)、处理器(Processor)和发射器(Emitter)。启动器负责启动爬虫,管道连接各种处理器,处理器执行实际的抓取任务,如解析HTML、处理链接等,...

    heritrix抓取的操作和扩展

    Heritrix是一个强大的开源网络爬虫工具,专为...然而,由于其丰富的配置选项和复杂的架构,对于新手来说,学习和掌握Heritrix可能需要一定的时间。因此,深入理解Heritrix的工作原理和配置机制是充分发挥其潜力的关键。

    heritrix的学习-源码分析 1-10

    1. **基础理解**:首先了解Heritrix的基本概念和架构设计。 2. **核心包分析**:深入研究上述提到的核心包,理解其内部工作原理和交互机制。 3. **实践应用**:基于Heritrix构建简单的爬虫任务,逐步增加复杂度。 4....

    heritrix 3.1

    文件"heritrix 3.1.dia"可能是用Dia工具绘制的Heritrix 3.1的类图或架构图,它可以帮助我们直观地理解各组件间的相互关系。而"heritrix 3.1.png"可能是一些关键类的截图或者配置示例,用于辅助理解。 总的来说,...

    heritrix-1.14.4爬虫框架及源码

    源码分析对于理解Heritrix的工作原理至关重要。通过对源码的阅读,我们可以了解其内部架构,包括线程模型、数据流控制和模块间的通信机制。例如,Heritrix采用多线程设计,每个组件如fetcher、parser和archiver都在...

    Heritrix3-可扩展web级别的Java爬虫项目

    1. **模块化架构**:Heritrix3采用组件化的架构,将爬虫的各个功能拆分为独立的模块,如爬取策略、解析器、存储器等。这种设计使得开发者可以轻松替换或扩展任何模块,以适应不同的抓取需求。 2. **线程模型**:...

    heritrix-3.2.0

    - **模块化设计**:Heritrix 的架构基于模块化,各个组件可以独立工作,易于扩展和维护。 - **插件支持**:通过编写插件,用户可以添加新的处理步骤,如爬取策略、数据解析和存储方法。 - **日志记录**:详细的...

    heritrix-1.12.1

    这种灵活性使得Heritrix能够适应各种复杂和特定的爬网任务,无论是学术研究、市场分析还是数据挖掘。 关于标签,"crawler"代表了Heritrix的核心功能,即网络爬虫,它能够自动化地遍历互联网并下载页面。"heritrix...

    heritrix3.2

    在实际应用中,Heritrix 3.2 可用于多种场景,如构建互联网档案库、学术研究、市场分析、竞争情报收集等。对于IT专业人士来说,掌握Heritrix 的使用和配置能极大地提升其在网络数据获取和处理方面的能力。通过深入...

    heritrix3.2源码

    Heritrix 3.2 源码的分析和理解有助于开发者深入掌握爬虫技术,定制自己的爬虫解决方案。 首先,让我们了解一下Heritrix 3.2 的核心特性: 1. **模块化设计**:Heritrix 3.2 采用组件化的架构,使得不同的爬取功能...

    heritrix

    Heritrix提供了丰富的配置选项和模块化架构,使得用户能够定制自己的抓取流程。 在Heritrix的使用过程中,首先需要从官方指定的下载页面获取最新版本,目前是1.12.1。下载完成后,解压缩到本地目录,通常会包含`lib...

    Lucene+Heritrix 源码

    **Lucene 和 Heritrix 源码分析** 在IT领域,搜索引擎的开发是一项复杂而重要的任务,它涉及到大量的文本处理、索引构建以及高效的查询算法。Lucene 和 Heritrix 是两个开源工具,分别专注于搜索的核心算法和网页...

    Lucene+Heritrix(搜索引擎开发)

    值得注意的是,在使用Lucene和Heritrix开发搜索引擎时,我们还需要考虑系统架构、数据处理能力、存储解决方案以及系统的可扩展性与维护性。在设计之初,就要规划如何高效地处理和存储索引数据,以及如何应对搜索引擎...

    开发自己的搜索引擎——Lucene+Heritrix

    3. **搜索引擎架构**: 构建搜索引擎通常包括以下几个步骤:网页抓取(由Heritrix完成)、预处理(包括HTML解析、链接提取、分词等)、索引创建(Lucene的主要任务)和查询处理。索引是搜索引擎的核心,它将网页内容...

Global site tag (gtag.js) - Google Analytics