博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

Nutch1.4相关

Posted on 2012-05-22 17:10  Hadoop-scutmstcSIG  阅读(418)  评论(1编辑  收藏  举报

Written by Hao C., Hadoop Special Interest Group.


      这段时间看了下Nutch1.4的源码,并尝试着结合solr和tomcat搭个搜索引擎,目前还没完成,但是也有些东西可以一记,希望对自己对别人都有所帮助。由于时间关系,而且很多细节问题还不是很理解,以下内容并没有经过精心组织,准备等搜索引擎搭好后再重构一下。

      网上有一些文章是讲Nutch的每一步是做什么的,基本上还是很好理解的,所以在这里就先不赘述了。我这里主要写一下Nutch1.4中几个主要的类是做什么的,建议先了解一下Nutch的基本组成和MapReduce的原理。在本文的最下面还有一个FAQ,关于使用nutch的过程中碰到的问题,希望有所帮助~


 Injector:

Injector执行两个MapReduce任务,第一个任务从指定的url种子文件中提取url,最终得到<url, crawldatum>。其中crawdatum包含了特定url的基本信息,如metadata(其中有两个系统保留元数据nutch.score和nutch.fetchInterval)、Fetch Time、初始score等。metadata直接跟在url的后边,之间用“\t”分隔开来。结果以SequenceFileOutputFormat存储,并保存在临时目录<mapred.temp.dir>/inject-temp-<random_number>下。

第二个任务将上面的结果和crawldb所在文件夹(如果存在)作为输入,合并新注入的url和之前的crawldb(InjectReducer:如果新注入的url已经存在,则直接返回旧的url数据)。结果以MapFileOutputFormat存储,并存储在临时目录<crawldb>/<random_number>[1]下。

两个任务完成后,Injector通过CrawlDb的install方法将当前的<crawldb>/current目录重名为<crawldb>/old(如果已经存在old目录,先将其删除),然后将上面生成临时目录重名为current,最后再删除old目录。完成后,Injector将删除第一个任务的中间结果。


Generator:

Generator包含三个MapReduce任务,第一个任务从<crawdb>/current目录下读取数据,根据URLFilters过滤url,并根据FetchScheduleFactory过滤掉还没到下次抓取时间的url。然后计算其余url的评分,并过滤掉低于参数generate.min.score的url,生成中间结果<score, SelectorEntry>,其中SelectorEntry包含url,datum和segnum(段编号)。每个url所在segnum是在Reduce阶段计算得出的,每个Reduce任务的每个段内的url数限制在generate.TopN/<num_of_reducers>,最多maxNumSegments个段。还可以通过参数generate.max.count限制每个段内相同类型url的个数,相同类型可以通过partition.url.mode来制定,包括byHost,byDomain,byIP。不满足上述条件的url都将被过滤掉。第一个任务最后的结果保存在目录<mapred.temp.dir>/generate-temp-<time>/fechlist-<segnum>下。

第二个任务为每个fechlist-<segnum>目录在<segments>/<time>/crawl_generate下生成相应的以时间为目录名的fetchlist。MapReduce过程为<score, SelectorEntry> => <url, SelectorEntry> => <url, crawldatum>。在这个过程中,希望在同一个fetchlist中相同类型(如byHost)的url能够尽量分开一些,所以采用了定制的HashComparator对key值比较并排序,由于采用哈希值作为比较依据,有可能两个url得到的哈希值是一样的,而系统只会保留最开始的那一个,导致一些url丢失,这也是为什么map阶段的输出是SelectorEntry,而不是一步到位提取出crawldatum。

第三个任务是更新crawdb,在前面两个任务完成之后,在未调用updatedb命令的情况下,Generator会根据参数generate.update.crawdb的值确定是否将fechlist更新到crawdb中,默认为false,即不自动更新。


Fetcher: 

Fetcher采用了一个生产者多个消费者的模型,QueueFeeder负责往FetchItemQueues中添加url,FetcherThread则从中获取url并抓取页面。FetchItemQueues中包含多个抓取队列FetchItemQueue,每一个队列对应一组相同类型(如byHost)的url,并根据协议类型和partition.url.mode进行唯一标识。

如果只是为Generator生成的每个fetchlist创建map任务,这样的并行度是远远不够的,所以Fetcher没有实现以往的Map和Reduce的相关接口,而是实现了MapRunner。Fetcher通过MapRunner提供的run方法,利用多线程并行处理fetchlist中的记录。

同时,出于礼貌规则,Fetcher创建的MapReduce任务关闭了Speculative Execution[2],并通过定制的InputFormat类保证每个fetchlist不会再被分成多个splits,防止多个线程对同一个站点过度访问。该任务从<segments>/<time>/crawl_generate目录下读取数据,由QueueFeeder线程负责将<url, crawldatum>队列中,FetcherTread负责从队列中获取url,并抓取页面。

Fetcher使用FecherOutputFormat,根据输出的数据类型,对5个目录进行写操作:<segments>/crawl_fetch(CrawlDatum类型)、<segments>/content(Content类型)、<segments>/parse_text、<segments>/parse_data、<segments>/crawl_parse(Parse类型)。

现在再来看一下<segments>下的每个目录是干什么的:

1. crawl_generate:需要抓取的url列表(fetchlist),由<url, crawldatum>组成的sequence文件。

2. crawl_fetch:每个抓取页面的状态报告,比如是否抓取是否成功,reponse code是多少,由<url, crawldatum>组成的map文件。

3. content:包含下载下来的原数据(raw data),由<url, content>组成的map文件。

4. parse_text:页面的解析文本,用于建立索引,由<url, ParseText>组成的map文件。

5. parse_data:包含页面解析后的元数据和outlinks。

6. crawl_parse:每个被成功抓取和解析的页面的outlinks列表,用于更新crawldb。


注:
[1] 目录中的尖括号部分,表示根据实际情况取值,如<mapred.temp.dir>需要根据mapred-site.xml中的相应参数字段确定。
[2] 由于各种原因(负载或资源分布不均、程序bug等),可能会出现一个Job的多个任务的进度不协调,如其它任务都已完成,而某个任务才完成10%。当某个任务满足一定条件时,Hadoop将为该任务启动Speculative task,并和该任务一起执行,哪个任务先完成就使用哪个的结果。


FAQ:

问题1:根据网上的一些教程(拷贝schema.xml、添加requestHandler,但通常不是针对3.6的教程)配置好solr3.6后,在启动solr的过程中会抛出org.apache.solr.common.SolrException: undefined field text的异常,同时在http://localhost:8983/solr/admin/下的Query String中除了输入*:*会返回结果外,其余的都返回Problem accessing /solr/select. Reason: undefined field text。

解决方法:通过查看从nutch拷贝过来的schema.xml可以看出,该schema是针对solr3.1所写的。所以推测可能是从3.1到3.6的过程中有什么改变,最直接的方法就是在solr根目录中查看CHANGE.TXT,并查找有没有关于schema.xml的记录。结果真的找到了,编号为SOLR-2724的记录中写着:

Specifying <defaultSearchField> and <solrQueryParser defaultOperator="..."/> in schema.xml is now considered deprecated. Instead you are encouraged to specify these via the "df" and "q.op" parameters in your request handler definition. (David Smiley)

从上面可以看出,defaultSearchField已被弃用,schema.xml中的defaultSearchField为content,而实际上根据上面的异常看出默认是对text进行检索,根据SOLR-2724默认检索字段应该是在solrconfig.xml中定义的,查看该文件并找到“df”,得到<str name="df">text</str>。接下里就只要把text改成content就ok啦,试试吧!