Nutch之Injector

本文详细介绍Apache Nutch的Injector模块工作原理。Injector负责将初始URL列表导入CrawlDb中,涉及两个MapReduce作业,用于规整化和过滤URL,以及合并更新CrawlDb。

1.       主要功能

将抓取起始URLs写入crawlDb中。

2.       涉及到的主要的类

org.apache.nutch.crawl.Injector

org.apache.nutch.crawl.CrawlDbFilter

org.apache.nutch.crawl.CrawlDbReducer

3.       具体介绍

(1)   Injector功能流程图

该流程图的前两步为Map/Reduce实现。

(2)   具体分析

1)      org.apache.nutch.crawl.Injector介绍

主要的方法:

inject(Path  crawlDb, Path  urlDir).其中urlDir为种子文件,也就是我们自己提供的url.txt文本。

该方法实现上述Injector功能流程图中的所有功能。具体看代码分析:

public void inject(Path crawlDb, PathurlDir) throws IOException {

 

   //首先生成一个以随机数结尾的临时目录来存放中间结果

   Path tempDir =

     new Path(getConf().get("mapred.temp.dir", ".") +

               "/inject-temp-"+

               Integer.toString(newRandom().nextInt(Integer.MAX_VALUE)));

 

   /** 第一个Map/Reduce */

   JobConf sortJob = new NutchJob(getConf());   //创建排序任务

   sortJob.setJobName("inject " + urlDir);      //设置Job

   //设置相关属性

   FileInputFormat.addInputPath(sortJob, urlDir);

   sortJob.setMapperClass(InjectMapper.class); 

   FileOutputFormat.setOutputPath(sortJob, tempDir); 

   sortJob.setOutputFormat(SequenceFileOutputFormat.class); 

   sortJob.setOutputKeyClass(Text.class);

   sortJob.setOutputValueClass(CrawlDatum.class);

   sortJob.setLong("injector.current.time", System.currentTimeMillis());

JobClient.runJob(sortJob);  //运行任务

 

   /** 第二个Map/Reduce */

JobConfmergeJob = CrawlDb.createJob(getConf(), crawlDb);   //创建合并任务

//设置属性

FileInputFormat.addInputPath(mergeJob,tempDir);

mergeJob.setReducerClass(InjectReducer.class);

JobClient.runJob(mergeJob);   //运行任务

 

//替换

CrawlDb.install(mergeJob,crawlDb);

 

//删除临时目录

FileSystem fs =FileSystem.get(getConf());

fs.delete(tempDir,true);

 

}

其中在第一个Map/Reduce中的Mapper.class主要是(org.apache.nutch.crawl.Injector的内部类)InjectMapper

第二个Map/Reduce中的Reduce.class主要是(org.apache.nutch.crawl.Injector的内部类)InjectReducer .

 

下边我们分别来看这四个步骤:

2)      第一个Map/Reduce----InjectMapper

InjectMapper implementsMapper<WritableComparable, Text, Text, CrawlDatum>

主要的方法:

public void map(WritableComparable key, Text value,

                    OutputCollector<Text,CrawlDatum> output, Reporter reporter)

    该方法实现的主要功能是:规整化和过滤注入的url,并将结果保存到临时文件中。

 

 

下边是具体的代码实现:

public voidmap(WritableComparable key, Text value,

                    OutputCollector<Text,CrawlDatum> output, Reporter reporter)

      throws IOException {

 

   /** 获得一行url地址 */

String url =value.toString();  

   

   /** 规范化和过滤URL */

try {   

    url = urlNormalizers.normalize(url,URLNormalizers.SCOPE_INJECT);

    url = filters.filter(url);        

} catch(Exception e) {

    if(LOG.isWarnEnabled()) { LOG.warn("Skipping " +url+":"+e); }

    url = null;

}

 

/**

 * 元数据一定是以key=value的形式被存储的,并且被制表符(“\t”)分割

 */

          float customScore = -1f;

intcustomInterval = interval;

Map<String,String>metadata = new TreeMap<String,String>();

if (url.indexOf("\t")!=-1){//判断读取的url是不是元数据,即判断是否是第一轮爬取

    String[] splits =url.split("\t");

    url =splits[0];

    for (int s=1;s<splits.length;s++){

    // find separation betweenname and value

    int indexEquals =splits[s].indexOf("=");

    if (indexEquals==-1) {

        // skip anything without a =

        continue;              

    }

    String metaname =splits[s].substring(0, indexEquals);

    String metavalue =splits[s].substring(indexEquals+1);

if (metaname.equals(nutchScoreMDName)) { 

try {

        customScore= Float.parseFloat(metavalue);

}catch (NumberFormatException nfe){}

}else if(metaname.equals(nutchFetchIntervalMDName)) {

    try {

                  customInterval =Integer.parseInt(metavalue);

} catch (NumberFormatException nfe){}

}else metadata.put(metaname,metavalue);

}

}  

             

/**

* <Text,  CrawlDatum>的形式保存结果

 * 其中Text指的是注入的urlCrawlDatum指封装了分值score、爬取的时间间隔*  customInterval、当前爬取时间curTime等信息的CrawlDatum对象。

*/

if (url !=null) {

    value.set(url);    //url设置为value(valueText的文本对象)

    CrawlDatum datum=newCrawlDatum(CrawlDatum.STATUS_INJECTED, customInterval);  //将爬取时间封装到CrawlDatum对象中

    datum.setFetchTime(curTime);  //将当前爬取时间封装CrawlDatum对象中

    //将分数封装到CrawlDatum对象中

if(customScore != -1) datum.setScore(customScore);

        else datum.setScore(scoreInjected);

    try {   //将最新的分值添加到CrawlDatum对象中

                  scfilters.injectedScore(value,datum);

    } catch (ScoringFilterException e) {

                  if(LOG.isWarnEnabled()) {

                           LOG.warn("Cannotfilter injected score for url " + url

                                              +", using default (" + e.getMessage() + ")");

                  }

    }

    output.collect(value, datum);    //存储结果

}

}

 

由上述代码实现我们可以看出,InjectMapper类功能实现主要经过了4个步骤:(看图)

 

详细过程如下:

先把urlDir(保存种子url的文件路径)中的url(一行一个url)一个一个读出来,然后根据相应的规则规整化和过滤url,判断是不是元数据。最后将url加载到value中,然后整合配置信息中的fetch信息到一个新建的CrawlDatum对象datum中,然后将<value,datum>作为键值通过output.collect(value,datum)将第一个任务的结果保存到临时文件tempDir中。

 

总结:

这里为什么要判断是不是元数据?

根据Map/Reduce的编程模式,在进入Map之前,应该将数据的数据分解成<keyvalue>的形式,这里如何实现的?

有分析显示,第一个任务的流程如下:

Map之前,先把urlDir(保存种子url的文件路径)中的url(一行一个url)一个一个地读出,然后把url保存为valuevalueText类型对象),keyWritableComparable类型,值为0。然后将<key,value>作为Map过程的输入。
Map
过程(Injector.InjectMapper类中定义):这个过程主要和urlDir有关,该过程主要的任务是判断输入的<key, value>中,value的值是否是应该爬取的页面的url,如果是,则将url重新加载到value中,然后整合配置信息中的fetch信息到一个新建的CrawlDatum对象datum中,然后将<value, datum>作为键值通过output.collect(value, datum)保存第一个任务的结果临时保存文件tempDir中。                

3)      第二个Map/Reduce
A.       第二个Mpa/Reduce主要是执行合并的任务,该合并任务是通过调用CrawlDb.createJob()方法来创建的,我们先来看一下这个方法:

public static JobConfcreateJob(Configuration config, Path crawlDb)

throwsIOException {

 

// 为即将生成的新“CrawlDb”产生一个临时的随机路径

Path newCrawlDb=

      new Path(crawlDb,

               Integer.toString(newRandom().nextInt(Integer.MAX_VALUE)));

 

//创建任务并设置任务名

JobConf job =new NutchJob(config);

job.setJobName("crawldb" + crawlDb);

 

//获得已有的“CrawlDb”路径

Path current =new Path(crawlDb, CURRENT_NAME); 

//如果该文件存在,则把它加入到该任务的输入路径中

if(FileSystem.get(job).exists(current)) {

      FileInputFormat.addInputPath(job,current);

    }

 

/** 设置任务参数 */

job.setInputFormat(SequenceFileInputFormat.class);

job.setMapperClass(CrawlDbFilter.class);    //设置Map

job.setReducerClass(CrawlDbReducer.class);  //设置reduce类,但被后面覆盖

FileOutputFormat.setOutputPath(job,newCrawlDb);  //设置输出路径

job.setOutputFormat(MapFileOutputFormat.class);

job.setOutputKeyClass(Text.class);

job.setOutputValueClass(CrawlDatum.class);

 

   //返回任务

   return job;

 

}

 

根据

job.setOutputKeyClass(Text.class);

job.setOutputValueClass(CrawlDatum.class);

可以看出合并任务的输出是个Map类型的文件,和之前的”CrawlDb”(如果存在)类型一致。该任务的map类是“org.apache.nutch.crawl.CrawlDbFilter ,主要是规整化和过滤url

 

B.       由以上代码可以看出,第二个M/R实现的主要步骤是:(如图)

C.        详细过程如下:

从第一个任务产生的tempDir文件和crawlDB中读<url datum>键值对,并对这些键值

进行urlurl规整化和过滤处理,并按照同一个url为一组的原则组成一个<value,datums>(datums不是datumdatumsCrawlDatum的一个迭代器,其中包含了所有属于urldatum) 然后将从这些对应同一个url的众多CrawlDatum对象中选择一个CrawlDatum对象作为合适的<value, datum>作为输出,输出到crawlDb中。

该合并的实现是由“InjectReducer”(org.apache.nutch.crawl.Injector的静态内部类)具体实施的。

D.       InjectReducer介绍

InjectReducer implements Reducer<Text,CrawlDatum, Text, CrawlDatum>

主要的方法:

    publicvoid reduce(Text key, Iterator<CrawlDatum> values,

                       OutputCollector<Text,CrawlDatum> output, Reporter reporter)

    该方法实现的功能主要是:合并对应同一个urlCrawlDatum对象。

下边是该方法的具体的代码实现:

private CrawlDatum old = newCrawlDatum();         //表示是CrawlDb中的url

private CrawlDatum injected = newCrawlDatum();     //表示是注入的种子url

 

public void reduce(Text key,Iterator<CrawlDatum> values,

                       OutputCollector<Text,CrawlDatum> output, Reporter reporter)

     throws IOException {

 

    booleanoldSet = false;

   while (values.hasNext()) {

        CrawlDatum val = values.next();

       //url,并初始化其未爬取的状态

       if (val.getStatus() == CrawlDatum.STATUS_INJECTED) {          

injected.set(val); 

           injected.setStatus(CrawlDatum.STATUS_DB_UNFETCHED); 

       } else {   //”CrawlDb”中已有的url

         old.set(val);

         oldSet = true;          

}

}

 

/**

 * 优先保存CrawlDB中存在的old

* 即如果CrawlDB中存在当前urlkey)对应的CrawlDatum对象,在爬取时,

* 则优先根据当前urlCrawlDB中存在的CrawlDatum对象信息,对该url进行

* 相关的处理(是爬取还是不爬取)

 */

CrawlDatum res= null;

if (oldSet) res= old; // don't overwrite existing value

      else res = injected;

 

/** <key,CrawlDatum>的形式保存结果 */

output.collect(key,res);

}

4)      第三步:新旧文件替换

两个M/R实现以后,出现一新一旧两个“crawlDb”文件(如果crawlDb存在),现在将两个文件互换。相应的函数为CrawlDb.install(mergeJob, crawlDb)。下面我们看具体的实现代码:

public static void install(JobConf job,Path crawlDb) throws IOException {

   //获取新“crawlDb”路径

   Path newCrawlDb = FileOutputFormat.getOutputPath(job);

   FileSystem fs = new JobClient(job).getFs();

Path old = newPath(crawlDb, "old");

//获取当前(旧)“crawlDb”路径

Path current =new Path(crawlDb, CURRENT_NAME);

//如果当前存在“crawlDb,将其重命名为“old

   if (fs.exists(current)) {

     if (fs.exists(old)) fs.delete(old, true);

     fs.rename(current, old);

}

fs.mkdirs(crawlDb);

//把新的“crawlDb”重命名为当前“crawlDb

fs.rename(newCrawlDb,current);

//删除“old

   if (fs.exists(old)) fs.delete(old, true);

   Path lock = new Path(crawlDb, LOCK_NAME);

LockUtil.removeLockFile(fs,lock);

}

5)      第四步:删除原来的临时文件tempDir

好啦,Injector过程至此结束啦,哈哈哈哈哈哈。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值