Nutch中Injector的过程

Injector过程主要分成两个过程,而且这两个过程是两个独立的Map/Reduce任务,前一个任务只有Map,后一个任务是一个完整的Map/Reduce过程。

在详细介绍之前,先看inject()的主调用代码:
代码1:
public void inject(Path crawlDb, Path urlDir) throws IOException {
    if (LOG.isInfoEnabled()) {
      LOG.info("Injector: starting");
      LOG.info("Injector: crawlDb: " + crawlDb);
      LOG.info("Injector: urlDir: " + urlDir);
    }
    Path tempDir =
      new Path(getConf().get("mapred.temp.dir", ".") +
               "/inject-temp-"+
               Integer.toString(new Random().nextInt(Integer.MAX_VALUE)));

    // map text input file to a <url,CrawlDatum> file
    if (LOG.isInfoEnabled()) {
      LOG.info("Injector: Converting injected urls to crawl db entries.");
}

/* 第一个任务 */
    JobConf sortJob = new NutchJob(getConf());
    sortJob.setJobName("inject " + urlDir);
    FileInputFormat.addInputPath(sortJob, urlDir); //设置第一个任务的输入路径
    sortJob.setMapperClass(InjectMapper.class); //设置第一个任务的Map类

    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); //执行第一个任务
   
    // merge with existing crawl db
    if (LOG.isInfoEnabled()) {
      LOG.info("Injector: Merging injected urls into crawl db.");
}
/* 第二个任务 */
    JobConf mergeJob = CrawlDb.createJob(getConf(), crawlDb); //初始化第二个任务,包括Map类和Reduce类,以及输入,输出路径,注意输出路径也是在这个函数里设置的
    FileInputFormat.addInputPath(mergeJob, tempDir); //为第二个任务添加一个输入路径
mergeJob.setReducerClass(InjectReducer.class); //重置Reducer类,即替换之前在mergeJob初始化时定义的Reducer类
JobClient.runJob(mergeJob);  //执行第二个任务
    CrawlDb.install(mergeJob, crawlDb);

    // clean up
    FileSystem fs = FileSystem.get(getConf());
    fs.delete(tempDir, true);
    if (LOG.isInfoEnabled()) { LOG.info("Injector: done"); }
  }
这个函数初一看,诈似一个Map/Reduce过程,实际上是两个Map/Reduce过程。

第一个任务(其实就一个Map过程,主要是为了从urlDir中读种子url,并对这些url进行初始化处理,并把处理结果保存到临时文件中):
主要过程介绍:在Map之前,先要将把urlDir(保存种子url的文件路径)中的url(一行一个url)一个一个地读出,把然后把url保存为value(value为Text类型对象),key为WritableComparable类型,值为0。然后将<key, value>作为Map过程的输入。
Map过程(Injector.InjectMapper类中定义):这个过程主要和urlDir有关,该过程主要是从的任务是判断输入的<key, value>中,value的值是否是应该爬取的页面的url,如果是,则将url重新加载到value中,然后整合配置信息中的fetch信息到一个新建的CrawlDatum对象datum中,然后将<value, datum>作为键值通过output.collect(value, datum)保存第一个任务的结果临时保存文件tempDir中。
其代码为:
代码2:
public void map(WritableComparable key, Text value,
                    OutputCollector<Text, CrawlDatum> output, Reporter reporter)
      throws IOException {
      String url = value.toString();              // value is line of text
      try {
        url = urlNormalizers.normalize(url, URLNormalizers.SCOPE_INJECT); //url规整化处理
        url = filters.filter(url);             // 对url进行过滤处理
      } catch (Exception e) {
        if (LOG.isWarnEnabled()) { LOG.warn("Skipping " +url+":"+e); }
        url = null;
      }
      if (url != null) {                          // if it passes
        value.set(url);                           //对value重置处理过的url
        CrawlDatum datum = new CrawlDatum(CrawlDatum.STATUS_INJECTED, interval);//添加fetch间隔时间到datum中
        datum.setFetchTime(curTime); //添加当前时间到datum中
        datum.setScore(scoreInjected); //添加分值到datum中
        try {
          scfilters.injectedScore(value, datum); //根据分值进行一些操作
        } catch (ScoringFilterException e) {
          if (LOG.isWarnEnabled()) {
            LOG.warn("Cannot filter injected score for url " + url +
                     ", using default (" + e.getMessage() + ")");
          }
          datum.setScore(scoreInjected); //出现异常时重置分值到datum中
        }
        output.collect(value, datum); //写处理结果到tempDir指定路径的临时文件中
      }
    }
  }
主要的处理过程:
1、对url进行规整化处理;
2、对url进行过滤操作(即判断该url是否是应该爬取的页面的url);
3、分别将爬取间隔时间、当前爬取时间、分值等代入一个CrawlDatum对象中,构建一个datum对象中;4、写这个<value, datum>键值对到tempDir指定路径的文件中。

第二个任务(主要是对一个url的多个crawlDB对象进行过滤操作):
主要过程介绍:这个过程主要和crawlDB有关,其任务主要是从第一个任务产生的tempDir文件和crawlDB中读<url, datum>键值对,并将这些键值对进行url规整化和过滤处理,并按照同一个url为一组的原则组成一个<value,datums>(datums不是datum,datums是CrawlDatum的一个迭代器,其中包含了所有属于url 的datum),然后将从这些对应同一个url的众多CrawlDatum对象中选择一个CrawlDatum对象作为合适的<value, datum>作为输出,输出到crawlDB中。
先看(代码1)中的代码:
JobConf mergeJob = CrawlDb.createJob(getConf(), crawlDb);
该函数初始化了一个完整的Map/Reduce任务mergeJob,包括Map类和reduce类的设置,同时设置输入路径(输入路径为CrawlDB的路径,后面还要添加第一个任务处理后保存的临时文件路径tempDir)和输出路径。只不过这时的mergeJob不符合第二个任务的需要,后面需要做一些修改:
代码3:
public static JobConf createJob(Configuration config, Path crawlDb)
    throws IOException {
    Path newCrawlDb =
      new Path(crawlDb,
               Integer.toString(new Random().nextInt(Integer.MAX_VALUE)));

    JobConf job = new NutchJob(config); //设置配置信息
    job.setJobName("crawldb " + crawlDb);

    Path current = new Path(crawlDb, CURRENT_NAME);  //根据CrawlDB的路径crawlDb处理输入路径
    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;
  }
前面提到输入路径需要添加第一个任务处理后保存的临时文件路径tempDir,因为事先不知道种子url是否在CrawlDB中已经存在了,而且如果种子url太多的话,就不能保证它们之中没有重复的url,所以需要结合第一个任务导入的种子url和CrawlDB中包含的url,来对url进行去重。(这里就有一个感觉nutch设计不好的地方,因为每次注入种子url时,其实只要根据种子url和CrawlDB中可能存在的相同的url进行去重就可以了,根本就不需要把整个CrawlDB都去重一次)。所以需要添加临时文件路径tempDir,来一起进行去重。添加路径代码见(代码1)的:
FileInputFormat.addInputPath(mergeJob, tempDir);
又因为CrawlDb.createJob(getConf(), crawlDb);中设置的Reduce类不是inject需要的类,所以需要对其进行重置,重置的代码见(代码1)的:
mergeJob.setReducerClass(InjectReducer.class); //重置ReducerClass
现在再分别介绍Map过程和Reduce过程:
Map过程(CrawlDbFilter类中定义):
先看其map函数的代码,
代码4:
public void map(Text key, CrawlDatum value,
      OutputCollector<Text, CrawlDatum> output,
      Reporter reporter) throws IOException {
  //该函数的作用实际上就是写crawlDB
    String url = key.toString();
    if (urlNormalizers) { //key(url)规整化
      try {
        url = normalizers.normalize(url, scope); // normalize the url  // normalize the url, 为什么这里还要进行url规整化操作
      } catch (Exception e) {
        LOG.warn("Skipping " + url + ":" + e);
        url = null;
      }
    }
    if (url != null && urlFiltering) {//key(url)过滤
      try {
        url = filters.filter(url); // filter the url
      } catch (Exception e) {
        LOG.warn("Skipping " + url + ":" + e);
        url = null;
      }
    }
    if (url != null) { // if it passes
      newKey.set(url); // collect it
      output.collect(newKey, value); //写文件
    }
  }
通过这个函数的代码发现,其实第二个任务的Map过程其实就是一个对url进行规整化和过滤的过程,只不过它不仅对CrawlDB中的url进行规整化和过滤操作,而且它还对tempDir指定路径的文件中的url进行规整化和过滤操作。然后把得到的<url, datum>(这个url是Text类型,Map过程没有对datum做任何处理)通过output.collect(newKey, value);临时保存着,等待紧接着的Reduce过程的处理。
Reduce过程(Injector.InjectReducer类中定义):
这个过程可以先看代码,
代码5:
private CrawlDatum old = new CrawlDatum(); //表示是CrawlDB中的url
private CrawlDatum injected = new CrawlDatum(); //表示是注入的种子url
public void reduce(Text key, Iterator<CrawlDatum> values,
                       OutputCollector<Text, CrawlDatum> output, Reporter reporter)
      throws IOException {
      boolean oldSet = false;
      while (values.hasNext()) { //通过迭代器循环获得所有该url下对应的CrawlDatum对象val
        CrawlDatum val = values.next();
        if (val.getStatus() == CrawlDatum.STATUS_INJECTED) { //如果是先前第一个任务中注入到临时文件tempDir中的种子url的话
          injected.set(val); //保存到injected中
          injected.setStatus(CrawlDatum.STATUS_DB_UNFETCHED); //初始化未爬取的状态
        } else {  //如果是CrawlDB中的url的话
          old.set(val);  //保存到old中
          oldSet = true; // oldSet为true表示crawlDB存在于该url对应的CrawlDatum信息
        }
      }
      CrawlDatum res = null;

      //通过下面这个if-else语句,可以看出返回的结果是优先保存CrawlDB中存在的old,也就是说如果CrawlDB中存在当前url(key)对应的CrawlDatum对象,在爬取时,则优先根据当前url在CrawlDB中存在的CrawlDatum对象信息来对该url进行相关处理(是爬取还是不爬取),即使种子url中也包括当前url。
      if (oldSet) res = old; // don't overwrite existing value
      else res = injected;

      output.collect(key, res);
}

你可能感兴趣的:(UP,bbs)