Nutch主流程代码阅读笔记整理

 Nutch 的Crawler和Searcher两部分被尽是分开,其主要目的是为了使两个部分可以布地配置在硬件平台上,例如Crawler和Searcher分别被放置在两个主机上,这样可以极大的提高灵活性和性能。
 
一、总体流程介绍
Nutch 的Crawler和Searcher两部分被尽是分开,其主要目的是为了使两个部分可以布地配置在硬件平台上,例如Crawler和Searcher分别被放置在两个主机上,这样可以极大的提高灵活性和性能。

1、先注入种子urls到crawldb
2、循环:
   * generate 从crawldb中生成一个url的子集用于抓取
   * fetch 抓取上一小的url生成一个个segment
   * parse 分析已抓取segment的内容
   * update 把已抓取的数据更新到原先的crawldb
3、从已抓取的segments中分析出link地图
4、索引segment文本及inlink锚文本
 
Nutch用入口地址,地址正则表达式,搜索深度三种形式来限制
因为使用了HadoopNutch的代码都按照Hadoop的模式来编写以获得分布式的能力,因此要先了解一下Hadoop,明白它Mapper,Rerducer,InputFormat, OutputFormat类的作用才能更好的阅读。
二、相关的数据结构和目录结构分析
 
爬虫Crawler:
Crawler的工作流程包括了整个nutch的所有步骤--injector,generator,fetcher,parseSegment,updateCrawleDB,Invertlinks, Index ,DeleteDuplicates, IndexMerger
Crawler涉及的数据文件和格式和含义,和以上的各个步骤相关的文件分别被存放在物理设备上的以下几个文件夹里,crawldb,segments,indexes,linkdb,index五个文件夹里。
那么各个步骤和流程是怎么,各个文件夹里又是放着什么呢?
观察Crawler类可以知道它的流程
./nutchcrawl urls -dir ~/crawl -depth 4 -threads 10 -topN 2000
 
Crawl目录结构分析,参考自《Lucene+Nutch搜索引擎开发》
一、crawldb下载的url,以及下载日期,用来进行页面更新
二、segements 存放抓取页面和分析结果
1、crawl_generate:待下载url
2、crawl_fetch:每个下载url的状态
3、content:每个下载页面的内容
4、parse_text:包含每个解析过的url文本内容
5、parse_data:每个url解析出的外部链接和元数据
6、crawl_parse:用来更新crawl的外部链接库
三、linkdb 存放url的互联关系
四、indexes:存放每次下载的独立索引目录
五、index:符合lucene格式的索引目录,是indexes里所有index合并后的完整索引
 
数据结构:
Crawl DB
● CrawlDb是一个包含如下结构数据的文件:
<URL,CrawlDatum>
&#9679;CrawlDatum:
<status,date, interval, failures, linkCount, ...>
&#9679; Status:
{db_unfetched,db_fetched, db_gone,linked,
fetch_success,fetch_fail, fetch_gone

三、主要类和方法分析
 

  1. org.apache.nutch.crawl.Injector:

1,注入url.txt
2,url标准化
3,拦截url,进行正则校验(regex-urlfilter.txt)
4,对符URL标准的url进行map对构造,在构造过程中给CrawlDatum初始化得分,分数可影响urlhost的搜索排序,和采集优先级!
5,reduce只做一件事,判断url是不是在crawldb中已经存在,如果存在则直接读取原来CrawlDatum,如果是新host,则把相应状态存储到里边(STATUS_DB_UNFETCHED(状态意思为没有采集过))
 
Injector injector = new Injector(conf); 
Usage: Injector <crawldb> <url_dir>
首先是建立起始url集,每个url都经过URLNormalizers、filter和scoreFilter三个过程并标记状态。首先经过normalizerplugin,把url进行标准化,比如basic nomalizer的作用有把大写的url标准化为小写,把空格去除等等。然后再经过的plugin是filter,可以根据你写的正则表达式把想要的url留下来。经过两个步骤后,然后就是把这个url进行状态标记,每个url都对应着一个CrawlDatum,这个类对应着每个url在所有生命周期内的一切状态。细节上还有这个url处理的时间和初始时的分值。
同时,在这个步骤里,会在文件系统里生成如下文件crawlDB/current/part-00000
这个文件夹里还有.data.crc , .index.crc, data, index四个文件
 
&#9679; MapReduce1: 把输入的文件转换成DB格式
In: 包含urls的文本文件
Map(line) → <url, CrawlDatum>; status=db_unfetched
Reduce() is identity;
Output: 临时的输出文件夹
&#9679; MapReduce2: 合并到现有的DB
Input: 第一步的输出和已存在的DB文件
Map() is identity.
Reduce: 合并CrawlDatum成一个实体(entry)
Out: 一个新的DB
 
 

  1. org.apache.nutch.crawl.Generator:

1,过滤不及格url(使用url过滤插件)
2,检测URL是否在有效更新时间里
3,获取URLmetaData,metaData记录了url上次更新时间
4,对url进行打分
5,将url载入相应任务组(以host为分组)
6,计算urlhash值
7,收集url,直至到达 topN 指定量
 
Generator generator = newGenerator(conf); //Generates a subset of a crawl db to fetch
 
Usage: Generator <crawldb> <segments_dir> [-force][-topN N] [-numFetchers numFetchers] [-adddays numDays] [-noFilter]
在这个步骤里,Generator一共做了四件事情,
1、给前面injector完成的输出结果里按分值选出前topN个url,作为一个fetch的子集。
2、根据第一步的结果检查是否已经选取出一些url,CrawlDatum的实体集。
3、再次转化,此次要以url的host来分组,并以url的hash来排序。
4、根据以上的步骤的结果来更新crawldb(injector产生)。
 
&#9679; MapReduce1: 根据要求选取一些要抓取的url
In: Crawl DB 文件
Map() → if date≥now, invert to <CrawlDatum, url>
Partition 以随机的hash值来分组
Reduce:
compare() 以 CrawlDatum.linkCount的降序排列
output only top-N most-linked entries
&#9679; MapReduce2: 为下一步抓取准备
Map() is invert; Partition() by host, Reduce() is identity.
Out: 包含<url,CrawlDatum> 要并行抓取的文件
 
 

  1. org.apache.nutch.crawl.Fetcher:

1,从segment中读取,将它放入相应的队列中,队列以queueId为分类,而queueId是由协议://ip 组成,在放入队列过程中,如果不存在队列则创建(比如javaeye的所有地址都属于这个队列:http://221.130.184.141)&ndash;>queues.addFetchItem(url, datum);
2,检查机器人协议是否允许该url被爬行(robots.txt)&ndash;> protocol.getRobotRules(fit.url, fit.datum);
3,检查url是否在有效的更新时间里&ndash;> if (rules.getCrawlDelay() > 0)
4,针对不同协议采用不同的协议采用不同机器人,可以是http、ftp、file,这地方已经将内容保存下来(Content)。&ndash;> protocol.getProtocolOutput(fit.url, fit.datum);
5,成功取回Content后,在次对HTTP状态进行识别(如200、404)。&ndash;>case ProtocolStatus.SUCCESS:
6,内容成功保存,进入ProtocolStatus.SUCCESS区域,在这区域里,系统对输出内容进行构造。&ndash;> output(fit.url, fit.datum, content, status,CrawlDatum.STATUS_FETCH_SUCCESS);
7,在内容构造过程中,调取内容解析器插件(parseUtil),如mp3/html/pdf/word/zip/jsp/swf……。&ndash;> this.parseUtil.parse(content); &ndash;> parsers.getParse(content);
8,现在研究html解析,所以只简略说明HtmlParser,HtmlParser中,会解析出text,title,outlinks, metadata。
text:过滤所有HTML元素;title:网页标题;outlinks:url下的所有链接;metadata:这东西分别做那么几件事情首先检测url头部的meta name=”robots” 看看是否允许蜘蛛爬行,其次通过对meta http-equivrefresh等属性进行识别记录,看页面是否需要转向。
 
Fetcher fetcher = newFetcher(conf); //The fetcher. Most of the work is done by plugins
Usage: Fetcher <segment> [-threads n] [-noParsing]
这个步骤里,Fetcher所做的事情主要就是抓取了,同时也完成一些其它的工作。首先,这是一个多线程的步骤,默认以10个线程去抓取。根据抓取回来后的结果状态来进行不同的标记,存储,再处理等等行为。输入是上一步骤Generator产生的segment文件夹,这个步骤里,考虑到先前已经按照ip或host来patition了,所以在此就不再把input文件进行分割了。程序继承了SequenceFileInputFormat重写了inputFormat来达到这点。这个类的各种形为都是插件来具体完成的,它只是一个骨架一样为各种插件提供一个平台。它先根据url来取出具体的protocol,得到protocolOutput,进而得到状态status及内容content。然后,根据抓取的状态status来继续再处理。再处理时,首先会将这次抓取的内容content、状态status及它的状态标记进行存储。这个存储的过程中,还会记下抓取的时间,再把segment存过metadata,同时在分析parsing前经过scoreFilter,再用parseUtil(一系列的parse插件)进行分析,分析后再经过一次score插件的处理。经过这一系列处理后,最后进行输出(url,fetcherOutput)。
之前讲到根据抓取回来的各种状态,进行再处理,这些状态一共包括12种,比如当抓取成功时,会像上刚讲的那样先存储结果,再判断是否是链接跳转,跳转的次数等等处理。
 
&#9679; MapReduce:抓取
In: <url,CrawlDatum>, 以host分区, 以hash值排序
Map(url,CrawlDatum) → <url, FetcherOutput>
多线程的, 同步的map实现
调用已有的协议protocol插件
FetcherOutput: <CrawlDatum, Content>
Reduce is identity
Out: 两个文件: <url,CrawlDatum>, <url,Content>
 

  1. org.apache.nutch.parse.ParseSegment

1,这个类逻辑就相对简单很多,它对我们也是很有价值的,它只做一件事情,就是对爬行下来的Content(原始HTML)进行解析,具体解析通过插件来实现。比如我们要做的数据分析、数据统计都可以在这进行实现。
2,执行完成后,输出三个Map对解析内容、包含所有链接的分析后的结果、outlinks
 
ParseSegment parseSegment = newParseSegment(conf); //Parse content in a segment
Usage: ParseSegment segment
对抓取后上一步骤存储在segment里的content进行分析parse。同样,这个步骤的具体工作也是由插件来完成的。
 
MapReduce: 分析内容
In: <url, Content> 抓取来的内容
Map(url, Content) → <url, Parse>
调用分析插件parser plugins
Reduce is identity.
Parse: <ParseText, ParseData>
Out: 分割成三个文件: <url,ParseText>, <url,ParseData>和<url,CrawlDatum> 为了outlinks.
 

  1. org.apache.nutch.crawl.CrawlDb

主要根据crawld_fatch输出更新crawldb。
1,map对crawld_fatch、crawldb地址进行标准化(nomalizer)和拦截操作(filte);
2,reduce在对两crawld_fatch和crawldb进行合并更新。
 
CrawlDb crawlDbTool = newCrawlDb(conf); //takes the output of the fetcher and updates thecrawldb accordingly.
Usage: CrawlDb <crawldb> (-dir <segments> |<seg1> <seg2> ...) [-force] [-normalize] [-filter] [-noAdditions]
 
MapReduce:合并抓取的和分析后的输出到crawldb里
In: <url,CrawlDatum>现有的db加上抓取后的和分析后的输出
Map() is identity
Reduce() 合并所有实体(entry)成一个,以抓取后的状态覆盖原先的db状态信息,统计出分析后的链接数
Out: 新的crawl db
 

  1. org.apache.nutch.crawl.LinkDb

这个类的作用是管理新转化进来的链接映射,并列出每个url的外部链接(incominglinks)。
1,先是对每一个url取出它的outLinks,作map操作把这个url作为每个outLinks的incominglink,
2,在reduce里把根据每个key来把一个url的所有incominglink都加到inlinks里。
3,这样就把每个url的外部链接统计出来了,注意,系统对只对外部链接进行统计,什么叫外部链接呢,就是只对不同host进行统计,记住javaeye.com和biaowen.javaeye.com是两个不同的host哦。&ndash;> boolean ignoreInternalLinks = true;
4,然后一步是对这些新加进来的链接进行合并。
 
&#9679; MapReduce: 统计每个链接的外部链接
In: <url,ParseData>, 包含所有链接的分析后的结果
Map(srcUrl, ParseData> → <destUrl, Inlinks>
为每个链出链接收集一个入链。
Inlinks: <srcUrl, anchorText>*
Reduce()加上外部入链数量
Out: <url, Inlinks>, 一个相关完整的链接地图
 

  1. org.apache.nutch.crawl.Indexer

这个类的任务是另一方面的工作了,它是基于hadoop和lucene的分布式索引。它就是为前面爬虫抓取回来的数据进行索引好让用户可以搜索到这些数据。这里的输入就比较多了,有segments下的fetch_dir,parseData和parseText,还有crawldb下的current_dir和linkdb下的current_dir。
Indexer应用hadoop遍历所有Segments目录,将parseData文件序列化成ParseData类,从中获得各种资料然后调用插件进行索引,最后仍然由ouputFormat类完成写入索引的工作。 
注意,如果你仅想使用Nutch的爬虫,而不是其索引功能,可以仿照Indexer重写自己的实现,比如把segments内容直接搬进数据库。
 
1,在这个类里,map将所有输入都装载到一个容器里边,
2,在到reduce进行分类处理,
3,实现拦截&ndash;> this.filters.filter(doc, parse, key, fetchDatum, inlinks);
4,打分&ndash;> this.scfilters.indexerScore(key, doc, dbDatum,fetchDatum, parse, inlinks,boost);
5,当然要把这些数据体组合成一个lucene的document让它索引了。
6,在reduce里组装好后收集时是,最后在输出的OutputFormat类里进行真正的索引。
doc里有如下几个field
content(正文)
site(所属主地址)
title(标题)
host(host)
segement(属于哪个segement)
digest(MD5码,去重时候用到)
tstamp(暂时不知道什么东西)
url(当前URL地址)
 
 
&#9679; MapReduce: 生成lucene的索引文件
In: 外个文件, values 以 <Class, Object>包装
<url, ParseData> from parse, 有title, metadata, 等等信息.
<url, ParseText> from parse, 文本 text
<url, Inlinks> from invert, 锚文本anchors
<url, CrawlDatum> from fetch,用于抓取
Map() is identity
Reduce() 生成Lucene Document
调用index插件
Out: 建立Lucene 索引; 最后存储到文件系统上
 

  1. org.apache.nutch.crawl.DeleteDuplicates

这个类的作用就是这它的名字所写的意思--去重。
前面索引后(当然不是一次时的情况)会有重复,所以要去重。为什么呢,在一次索引时是不重复的,可是多次抓取后就会有重复了。就是这个原因才要去重。当然去重的规则有两种一个是以时间为标准,一种是以内容的md5值为标准。
 
 

  1. org.apache.nutch.indexer.IndexMerger

这个类就相对简单了,目的将多个indexes合并为一个index,直接调用lucene方法实现!把所有的小索引合并成一个索引。在这一步没有用到map-reduce。
 
在这九大步骤中generator,fetcher,parseSegment,crawlDbTool会根据抓取的层数循环运行,当抓取的层数大于1时会运行linkInvert,index,dedup,和merge。

四、Nutch 每条索引记录的字段说明

url: 作为唯一标标识值,由BasicIndexingFilter类产生。 

segment: Indexer类产生。Nutch抓回来的页面内容放在segments目录,lucene只会索引,不会store原文内容,因此在查询时要以 segmenturl作为外键,由FetchedSegments类根据hitsDetailsegments目录获得content。 

boost:优先级,由Indexer类调用插件计算产生。 

title:显示标题,在BasicIndexingFilter插件中被索引和存储。

content: 主要的被搜索项,在BasicIndexingFilter插件中被索引。
 
 
五、Nutch搜索过程实例

Nutch提供了一个FascadeNutchBean类供我们使用,一段典型的代码如下
 
    NutchBean bean = new NutchBean();
    Query query = Query.parse(args[0]);
    Hits hits = bean.search(query, NUM_HITS,"title",true);
 
    for (int i = 0; i < hits.getLength(); i++) {
      Hit hit = hits.getHit(i);
      HitDetails details = bean.getDetails(hit);
 
      String title = details.getValue("title");
      String url = details.getValue("url");
      String summary =bean.getSummary(details, query);
    }
这里NutchBean为我们做了几样事情:
一是按Title field来排序
二是支持分布式查询,如果有配置servers,就会使用hadoopIPC系统,调用所有server上的nutchBeans,最后规约出总的结果。
三是每个站点只显示分数最高的一页,如果用户还想看同站的其他结果,就需要访问MoreHitsExculde[]
四是生成Summary,读取segments目录,按segmentsurl获得content, 并按一定算法抽取出包含关键字的文档片断。

你可能感兴趣的:(Nutch主流程代码阅读笔记整理)