Nutch1.2中的数据流介绍(其他版本可能不同)

来源:互联网 发布:数据库防护方案 编辑:程序博客网 时间:2024/05/21 17:26

Injector:插入操作

(1)Injector获得纯文本的url种子文件的路径,即rootUrlDir

(2)inject第一个job,将纯文本url文件读取解析,然后经过一系列过滤加工处理,返回键值对<Text,CrawlDatum>(备注:Text是url封装类,CrawlDatum是url状态类)存储到临时目录tempDir中。

(3)inject第二个job,输入为临时目录tempDir和url种子库CrawlDb中的键值对,然后进行了去重和过滤操作,获得了一些列完整的没有重复的键值对<Text,CrawlDatum>,并将结果存储到url链接库CrawlDb中(备注:最终地址  %结果存放路径%\crawldb\current\)。

 

Generator:生成任务链表操作

(1)Generator获得的是链接库CrawlDb中的键值对<Text,CrawlDatum>

(2)generate的第一个job,将链接库中的键值对通过Selector的mp操作,通过上次爬去时间,现在url的状态和url的得分情况进行过滤,获得键值对<score,SelectorEntry>,reduce获得map的键值对,根据ip和domain进行分块处理,将键值对<score,SelectorEntry>输出到临时目录中。

(3)generate的第二个job,将临时目录中的键值对<score,SelectorEntry>进行处理,生成待下载任务键值对<Text,CrawlDatum>,并将结果存储到Generator-segment目录中,等待下载。(备注最终存放地址:%结果存放路径%\segments\20120724093522\crawl_generate\)

 

Fetcher:爬去模块

(1)爬去模块从上一步Generator的结果获得<Text,CrawlDatum>键值对。

(2)QueueFeeder将键值对作为参数生成FetchItem加入FetchItemQueues待下载队列中。

(3)FetcherThread获得队列中的FetchItem,使用FetchItem最为参数获得网页内容Content。

(4)将获得的结果使用output()函数进行输出,如果Content内容不为空,则调用parseResult = this.parseUtil.parse(content);解析内容,解析获得ParseResult结果。

(5)经过解析,ParseResult获得的解析内容有,

         ParseData:存储了网页内容中的外部链接信息和网页状态信息。

         ParseText:网页的文本内容。

         Metadata:存储元信息。

(6)输出结果的最终目录中的  %结果存放路径%\segments\20120724093522\路径下。

    第一个输出:output.collect(key, new NutchWritable(datum));

                      输出链接对应的CrawlDatum到 %结果路径%\segments\20120724093938\crawl_fetch中

    第二个输出: output.collect(key, new NutchWritable(content));

                      输出结果网页内容 Content到 %结果路径%\segments\20120724093938\content中

    第三个输出:output.collect(url, new NutchWritable(new ParseImpl(new ParseText(parse.getText()), parse.getData(), parse.isCanonical())));

                     数据解析结果,解析结果分为三部分:

                     第一部分ParseText解析的文本内容存储到 %结果路径%\segments\20120724093938\parse_text

                     第二部分ParseData解析的该网页的外部链接信息和状态到  %结果路径%\segments\20120724093938\parse_data

                     第三部将存储网页以及他的外部链接的<Text,CrawlDatum>键值对(备注:该部分和第一个输出将作为链接数据库CrawlDb更新的输入)到 %结果路径%\segments\20120724093938\crawl_parse

 

CrawlDb:链接数据库更新模块

(1)CrawlDb将原来的数据链接库,以及Fetcher模块最终的得到的结果中crawl_fetch和crawl_parse中的<Text,CrawlDatum>键值对作为输入。

(2)CrawlDbFilter对输入的<Text,CrawlDatum>键值对,进行规范化和正则过滤操作,将结果输出到临时目录中。

(3)CrawlDbReducer对于输入的<Text,Iterator<CrawlDatum>>进行循环遍历,找出对应该url的最新的CrawlDatum状态类,根据该状态类设置改url的状态,然后输出的新的链接数据库中。

(4)等所有操作完成后,将新的链接数据库设置为当前连接数据库,删除软来老的链接数据库。

 

LinkDb:外部链接管理模块(它分为两个job)

(1) JobConf job = LinkDb.createJob(getConf(), linkDb, normalize, filter),首先得到第一个job配置信息类,他将segments中所有文件夹下的parse_data作为输入,parse_data是<key,ParseData>的键值对(解析:其中key是爬取的网页url,ParseData是对该网页的解析信息,其中包括解析内容链接等信息),然后经过mp处理,最终在newlinkDb中获得<Text,Inlinks>键值对,mp操作主要完成的工作是,将ParseResult中的链接逐个取出,然后再用当前的key构造Inlinks,这样实现了主机url到外部链接的翻转工作,所以主函数名叫做invert就是这个原因。

(2)如果这是LinkDb文件已经存在(说明前面已经爬取过,并获得了链接信息),则要进行第二个合并job,他将以上一个job中的输出newLinkDb和当前的LinkDb作为输入,最终获得<Text,lnlinks>做为最终结果存放在他自己的临时结果目录newlinkDb中。

(3)通过上面两步后,LinkDb.install(job, linkDb)操作,将上一个job的临时结果替换掉当前的LinkDb结果,然后删除老的LinkDb结果,该模块工作完成。

 

Indexer:建立索引模块

        Indexer indexer = new Indexer(conf);        indexer.index(indexes, crawlDb, linkDb,Arrays.asList(HadoopFSUtil.getPaths(fstats)));

(1)Indexer中的job,以CrawlDb,LinkDb,segments/crawl_fetch,segments/parse_text,segments/parse_data,segments/crawl_parse中的文件作为输入,具体CrawlDb中的<Text,CrawlDatum>,LinkDb中的<Text,Inlinks>,segments/parse_text中的<Text,ParseText>,segments/parse_data中的<Text,ParseData>,segments/crawl_fetch和segments/crawl_parse中的<Text,CrawlDatum>作为输入。

(2)Indexer通过IndexerMapReduce.initMRJob(crawlDb, linkDb, segments, job),初始化了一个以IndexerMapReduce作为mapper和reducer的job,然后将所有的输入进行处理过滤,最终生成了<Text,NutchDocument>的类通过IndexerOutputFormat输出。

(3)IndexerOutputFormat通过getRecordWriter()函数获得RecordWriter<Text, NutchDocument>(),该类调用LuceneWriter对输入的<Text,NutchDocument>键值对进行处理,然后write()到硬盘。

(4)LuceneWriter类的的write()函数,先调用内部方法createLuceneDoc(doc)将NutchDocument转换成标准的Lucene的Document,然后获得分词器,最后调用addDocument()将建立索引。

 public void write(NutchDocument doc) throws IOException {    final Document luceneDoc = createLuceneDoc(doc);    final NutchAnalyzer analyzer = analyzerFactory.get(luceneDoc.get("lang"));    if (Indexer.LOG.isDebugEnabled()) {      Indexer.LOG.debug("Indexing [" + luceneDoc.get("url")          + "] with analyzer " + analyzer + " (" + luceneDoc.get("lang")          + ")");    }    writer.addDocument(luceneDoc, analyzer);  }


 

(5)createLuceneDoc()方法负责将NutchDocument中的左右的NutchField读出,然后转换成Lucene中的Field,添加到Document中。

  private Document createLuceneDoc(NutchDocument doc) {    final Document out = new Document();    out.setBoost(doc.getWeight());    final Metadata documentMeta = doc.getDocumentMeta();    for (final Entry<String, NutchField> entry : doc) {      final String fieldName = entry.getKey();      Field.Store store = fieldStore.get(fieldName);      Field.Index index = fieldIndex.get(fieldName);      Field.TermVector vector = fieldVector.get(fieldName);      // default values      if (store == null) {        store = Field.Store.NO;      }      if (index == null) {        index = Field.Index.NO;      }      if (vector == null) {        vector = Field.TermVector.NO;      }      // read document-level field information      final String[] fieldMetas =        documentMeta.getValues(LuceneConstants.FIELD_PREFIX + fieldName);      if (fieldMetas.length != 0) {        for (final String val : fieldMetas) {          if (LuceneConstants.STORE_YES.equals(val)) {            store = Field.Store.YES;          } else if (LuceneConstants.STORE_NO.equals(val)) {            store = Field.Store.NO;          } else if (LuceneConstants.INDEX_TOKENIZED.equals(val)) {            index = Field.Index.ANALYZED;          } else if (LuceneConstants.INDEX_NO.equals(val)) {            index = Field.Index.NO;          } else if (LuceneConstants.INDEX_UNTOKENIZED.equals(val)) {            index = Field.Index.NOT_ANALYZED;          } else if (LuceneConstants.INDEX_NO_NORMS.equals(val)) {            index = Field.Index.ANALYZED_NO_NORMS;          } else if (LuceneConstants.VECTOR_NO.equals(val)) {            vector = Field.TermVector.NO;          } else if (LuceneConstants.VECTOR_YES.equals(val)) {            vector = Field.TermVector.YES;          } else if (LuceneConstants.VECTOR_POS.equals(val)) {            vector = Field.TermVector.WITH_POSITIONS;          } else if (LuceneConstants.VECTOR_POS_OFFSET.equals(val)) {            vector = Field.TermVector.WITH_POSITIONS_OFFSETS;          } else if (LuceneConstants.VECTOR_OFFSET.equals(val)) {            vector = Field.TermVector.WITH_OFFSETS;          }        }      }      for (final Object fieldValue : entry.getValue().getValues()) {        Field f = new Field(fieldName, fieldValue.toString(), store, index, vector);        f.setBoost(entry.getValue().getWeight());        out.add(f);      }    }    return out;  }


 (6)最终的输出结果在以lucene索引的形式存储在\crawl\indexes目录下。

 

DeleteDuplicates:该类主要作用是删除索引中重复的Document,该类的dedup()函数有三个job

(1)第一个job负责处理同一个url的Document,将最新的Document的keep属性标志位true(表示保留),其他的Document标志位false(表示删除)。该job的输入是上一步Indexer建立的索引,通过InputFormat将索引转换成<Text,IndexDoc>键值对,然后通过UrlsReducer类,将同一个url的比较老的IndexDoc的keep标志为false,最后将所有的IndexDoc以<MD5Hash, IndexDoc>键值对形式输出到outDir1临时结果中。

(2)第二个job负责将第一个job中的结果进行进一步过滤,这一次的标准是IndexDoc的得分(如果不按照得分则按IndexDoc中的url长度)进行过滤。该job的输入是上一个job的输出<MD5Hash,IndexDoc>键值对,最终HashReducer对键值对进行处理,将上一个job删除的和本次删除的IndexDoc,以<Text, IndexDoc>键值对的方式输出到outDir2中。

(3)第三个job负责真正的删除工作,他的输入时第二个job的输出(outDir2中的<Text,IndexDoc>键值对),然后他的Map函数负责将输入<Text,IndexDoc>键值对转换成<indexDoc.index, new IntWritable(indexDoc.doc)>键值对,然后由reduce函数具体实现对Document的删除工作。

备注:注意第三个job的输入不是全部的IndexDoc,而是第二个job输出的要删除的IndexDoc,所以如果第二个job没有输出,则第三个job没有工作要做。

 

 


 

 

 

 

 

原创粉丝点击