SkylineSoft

莽莽苍节兮 群山巍峨 日月光照兮 纷纭错落 丝竹共振兮 执节者歌 行云流水兮 用心无多 求大道以弹兵兮凌万物而超脱 觅知音因难得兮唯天地与作合
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

〔转〕Nutch 1.0源代码分析(1): Crawl初始化与Injector

Posted on 2011-02-12 10:48  Jiangwzh  阅读(699)  评论(0)    收藏  举报

从今天开始陆续对Nutch 1.0的工作过程进行分析。从Crawl为起点,先分析爬虫从爬行到索引的过程,再分析查询检索的过程。如有错误,欢迎批评指正!
      Crawl类是Nutch爬虫中的一个核心类,它的主要方法就是该类中的main方法,该方法包含了爬虫的整个运行阶段,包括Inject(将初始URL加入到网页库CrawlDb中),Generate(产生待爬行队列),Fetch(抓取网页)和Index(索引)。这里通过分析main方法的执行过程分析nutch爬虫从建立网页库到建立索引的全过程。Crawl类位于org.apache.nutch.crawl包中,接下来就对main方法进行分析。
1.初始化阶段
      根据nutch-default.xml,nutch-site.xml,nutch-tool.xml 3个配置文件和用户在命令行输入的参数,配置和初始化爬行深度、线程数、爬行根目录、topN、crawlDb、linkDb、segments、indexes等目录,并初始化injector、generator、fetcher、parser、indexer等。
2.Inject
      Inject阶段将初始URL进行合法性检查(UrlNomalizer)如:统一大小写,检查是否为合法URL等和过滤(UrlFilter),如过滤掉用户指定的不爬行的链接之后注入到网页数据库crawlDb中。调用inject(crawlDb, rootUrlDir)方法,该方法在org.apache.nutch.crawl.Injector类中,下面对其进行分析。
inject方法的两个参数,Path crawlDb代表crawlDb的路径,这里是crawl/crawlDb;Path rootUrlDir 代表初始URL的路径。
  

Java代码 复制代码
  1. // org.apache.nutch.crawl.Injector   
  2. public void inject(Path crawlDb, Path urlDir) throws IOException {      
  3. // crawlDb 网页数据库目录                                                                     // urlDir  种子URL目录   
  4. ... ...   
  5.     Path tempDir =   
  6.       new Path(getConf().get("mapred.temp.dir"".") +               "/inject-temp-"+Integer.toString(new Random().nextInt(Integer.MAX_VALUE)));   
  7.   
  8.     // map text input file to a <url,CrawlDatum> file   
  9.     if (LOG.isInfoEnabled()) {   
  10.       LOG.info("Injector: Converting injected urls to crawl db entries.");   
  11.     }   
  12.     JobConf sortJob = new NutchJob(getConf());   
  13.     sortJob.setJobName("inject " + urlDir);   
  14.     FileInputFormat.addInputPath(sortJob, urlDir);   
  15.     sortJob.setMapperClass(InjectMapper.class);   
  16.   
  17.     FileOutputFormat.setOutputPath(sortJob, tempDir);   
  18.     sortJob.setOutputFormat(SequenceFileOutputFormat.class);   
  19.     sortJob.setOutputKeyClass(Text.class);   
  20.     sortJob.setOutputValueClass(CrawlDatum.class);   
  21.     sortJob.setLong("injector.current.time",System.currentTimeMillis());   
  22.     JobClient.runJob(sortJob);   
  23.     ... ...  
// org.apache.nutch.crawl.Injector
public void inject(Path crawlDb, Path urlDir) throws IOException {   
// crawlDb 网页数据库目录	                                                                   // urlDir  种子URL目录
... ...
    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);

    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成<url, CrawlDatum>的文件。输入文件是用户指定的初始URL,使用的Mapper类是InjectMapper,Reduce阶段不做任何事情,原样输出。输出的目录是刚刚生成的tempDir临时目录,输出的键值对类型是<Text, CrawlDatum>,将injector.current.time属性设置为当前时间。
      这里介绍一下CrawlDatum的作用。它是记录数据库crawlDb中每个链接爬行状态的一个类,每次爬行的时候判断这个链接是否爬行过,目前正处于爬行的哪个阶段等等信息都记录在这个类的标志中。
      那么在Map阶段具体都做了哪些事呢?需要进一步分析InjectMapper类。该类的configure方法对urlNormalizer、interval、scfiliter、scoreInjected等域进行初始化,为后面的map做准备。其中interval是网页抓取的时间间隔,默认情况下是30天,scfilter是当前nutch使用的ScoringFilter,scoreInjected是inject到crawlDb中的链接得分,默认是1.0。接下来是最重要的map方法:

Java代码 复制代码
  1. public void map(WritableComparable key, Text value,   
  2.                     OutputCollector<Text, CrawlDatum> output, Reporter reporter)   
  3.       throws IOException {   
  4.       String url = value.toString();              // value is line of text   
  5.       try {   
  6.         url = urlNormalizers.normalize(url, URLNormalizers.SCOPE_INJECT);   
  7.         url = filters.filter(url);             // filter the url   
  8.       } catch (Exception e) {   
  9.         if (LOG.isWarnEnabled()) { LOG.warn("Skipping " +url+":"+e); }   
  10.         url = null;   
  11.       }   
  12.       if (url != null) {                          // if it passes   
  13.         value.set(url);                           // collect it   
  14.         CrawlDatum datum = new CrawlDatum(CrawlDatum.STATUS_INJECTED, interval);   
  15.         datum.setFetchTime(curTime);   
  16.         datum.setScore(scoreInjected);   
  17.         try {   
  18.           scfilters.injectedScore(value, datum);   
  19.         } catch (ScoringFilterException e) {   
  20.           if (LOG.isWarnEnabled()) {   
  21.             LOG.warn("Cannot filter injected score for url " + url +   
  22.                      ", using default (" + e.getMessage() + ")");   
  23.           }   
  24.           datum.setScore(scoreInjected);   
  25.         }   
  26.         output.collect(value, datum);   // 一个URL对应一个状态信息,key是URL,值是状态信息   
  27.       }   
  28.     }  
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 = filters.filter(url);             // filter the url
      } catch (Exception e) {
        if (LOG.isWarnEnabled()) { LOG.warn("Skipping " +url+":"+e); }
        url = null;
      }
      if (url != null) {                          // if it passes
        value.set(url);                           // collect it
        CrawlDatum datum = new CrawlDatum(CrawlDatum.STATUS_INJECTED, interval);
        datum.setFetchTime(curTime);
        datum.setScore(scoreInjected);
        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);
        }
        output.collect(value, datum);   // 一个URL对应一个状态信息,key是URL,值是状态信息
      }
    }


      这个map方法的主要功能是将用户输入的初始URL从文本转换为<url, CrawlDatum>键值对类型,CrawlDatum是一个链接的爬行状态信息。首先使用nomalizer对输入的URL进行规范化,如果不能通过规范化则返回,否则向crawldatum中打入链接的相关信息:当前的爬行状态、链接得分(如果ScoringFilter中指定了在inject阶段怎样计算注入链接的得分则使用这个得分,否则使用默认的1.0)、抓取时间。最后,收集url和与它对应的爬行状态。
      程序返回到inject方法中继续执行:

Java代码 复制代码
  1. // merge with existing crawl db   
  2.     if (LOG.isInfoEnabled()) {   
  3.       LOG.info("Injector: Merging injected urls into crawl db.");   
  4.     }   
  5.     JobConf mergeJob = CrawlDb.createJob(getConf(), crawlDb);   
  6.     FileInputFormat.addInputPath(mergeJob, tempDir);   
  7.     mergeJob.setReducerClass(InjectReducer.class);   
  8.     JobClient.runJob(mergeJob);   
  9.     CrawlDb.install(mergeJob, crawlDb);   
  10.   
  11.     // clean up   
  12.     FileSystem fs = FileSystem.get(getConf());   
  13.     fs.delete(tempDir, true);   
  14.     if (LOG.isInfoEnabled()) { LOG.info("Injector: done"); }   
  15.   
  16.   } // inject方法结束  
// merge with existing crawl db
    if (LOG.isInfoEnabled()) {
      LOG.info("Injector: Merging injected urls into crawl db.");
    }
    JobConf mergeJob = CrawlDb.createJob(getConf(), crawlDb);
    FileInputFormat.addInputPath(mergeJob, tempDir);
    mergeJob.setReducerClass(InjectReducer.class);
    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"); }

  } // inject方法结束


      这部分首先调用CrawlDb的createJob静态方法对接下来的将injected URL合并到crawlDb的MapReduce任务进行配置。createJob将FileInputPath设置为crawl/crawlDb/current,Mapper类设置为CrawlDbFilter,Reducer类设置为CrawlDbReducer,输出路径(outputPath)设置为crawl/crawlDb/下的一个以随机数命名的目录,输出的键值对类型是<Text, CrawlDatum>,此后creatJob返回。
      在该方法后,inject方法继续对接下来的MapReduce任务进行配置,增加一个输入路径tempDir(即上次Map任务的输出。这样,Map任务实际有两个输入路径),设置Reducer类为InjectReducer(此处待确认:覆盖creatJob中设置的Reducer?????)。
      Map阶段CrawlDbFilter的作用是将crawl/crawldb/current和上一步Map处理(读入初始URL并map)后的<url, crawldatum>键值对经过UrlNormalizer和UrlFilter规范化(如字母大小写等)和过滤(根据用户配置,决定是否保留这个url)后,然后输出到tempDir中。
      Reduce阶段InjectReducer的任务是将已存在于current中的网页数据库和新加入在outputPath中的数据库进行合并。如果新加入的网页已存在于current中,则不修改其状态,否则将新加入链接datum的状态从STATUS_INJECTED改为STATUS_DB_UNFETCHED。这样就完成了新旧数据库的合并
最后,通过CrawlDb.install(mergeJob, crawlDb)方法将tempDir删除。将新的数据库命名为current。
      至此,Inject阶段的任务完成,该阶段数据处理的流程图如下:
 
从今天开始陆续对Nutch 1.0的工作过程进行分析。从Crawl为起点,先分析爬虫从爬行到索引的过程,再分析查询检索的过程。如有错误,欢迎批评指正!
      Crawl类是Nutch爬虫中的一个核心类,它的主要方法就是该类中的main方法,该方法包含了爬虫的整个运行阶段,包括Inject(将初始URL加入到网页库CrawlDb中),Generate(产生待爬行队列),Fetch(抓取网页)和Index(索引)。这里通过分析main方法的执行过程分析nutch爬虫从建立网页库到建立索引的全过程。Crawl类位于org.apache.nutch.crawl包中,接下来就对main方法进行分析。
1.初始化阶段
      根据nutch-default.xml,nutch-site.xml,nutch-tool.xml 3个配置文件和用户在命令行输入的参数,配置和初始化爬行深度、线程数、爬行根目录、topN、crawlDb、linkDb、segments、indexes等目录,并初始化injector、generator、fetcher、parser、indexer等。
2.Inject
      Inject阶段将初始URL进行合法性检查(UrlNomalizer)如:统一大小写,检查是否为合法URL等和过滤(UrlFilter),如过滤掉用户指定的不爬行的链接之后注入到网页数据库crawlDb中。调用inject(crawlDb, rootUrlDir)方法,该方法在org.apache.nutch.crawl.Injector类中,下面对其进行分析。
inject方法的两个参数,Path crawlDb代表crawlDb的路径,这里是crawl/crawlDb;Path rootUrlDir 代表初始URL的路径。
  

Java代码 复制代码
  1. // org.apache.nutch.crawl.Injector   
  2. public void inject(Path crawlDb, Path urlDir) throws IOException {      
  3. // crawlDb 网页数据库目录                                                                     // urlDir  种子URL目录   
  4. ... ...   
  5.     Path tempDir =   
  6.       new Path(getConf().get("mapred.temp.dir"".") +               "/inject-temp-"+Integer.toString(new Random().nextInt(Integer.MAX_VALUE)));   
  7.   
  8.     // map text input file to a <url,CrawlDatum> file   
  9.     if (LOG.isInfoEnabled()) {   
  10.       LOG.info("Injector: Converting injected urls to crawl db entries.");   
  11.     }   
  12.     JobConf sortJob = new NutchJob(getConf());   
  13.     sortJob.setJobName("inject " + urlDir);   
  14.     FileInputFormat.addInputPath(sortJob, urlDir);   
  15.     sortJob.setMapperClass(InjectMapper.class);   
  16.   
  17.     FileOutputFormat.setOutputPath(sortJob, tempDir);   
  18.     sortJob.setOutputFormat(SequenceFileOutputFormat.class);   
  19.     sortJob.setOutputKeyClass(Text.class);   
  20.     sortJob.setOutputValueClass(CrawlDatum.class);   
  21.     sortJob.setLong("injector.current.time",System.currentTimeMillis());   
  22.     JobClient.runJob(sortJob);   
  23.     ... ...  
// org.apache.nutch.crawl.Injector
public void inject(Path crawlDb, Path urlDir) throws IOException {   
// crawlDb 网页数据库目录	                                                                   // urlDir  种子URL目录
... ...
    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);

    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成<url, CrawlDatum>的文件。输入文件是用户指定的初始URL,使用的Mapper类是InjectMapper,Reduce阶段不做任何事情,原样输出。输出的目录是刚刚生成的tempDir临时目录,输出的键值对类型是<Text, CrawlDatum>,将injector.current.time属性设置为当前时间。
      这里介绍一下CrawlDatum的作用。它是记录数据库crawlDb中每个链接爬行状态的一个类,每次爬行的时候判断这个链接是否爬行过,目前正处于爬行的哪个阶段等等信息都记录在这个类的标志中。
      那么在Map阶段具体都做了哪些事呢?需要进一步分析InjectMapper类。该类的configure方法对urlNormalizer、interval、scfiliter、scoreInjected等域进行初始化,为后面的map做准备。其中interval是网页抓取的时间间隔,默认情况下是30天,scfilter是当前nutch使用的ScoringFilter,scoreInjected是inject到crawlDb中的链接得分,默认是1.0。接下来是最重要的map方法:

Java代码 复制代码
  1. public void map(WritableComparable key, Text value,   
  2.                     OutputCollector<Text, CrawlDatum> output, Reporter reporter)   
  3.       throws IOException {   
  4.       String url = value.toString();              // value is line of text   
  5.       try {   
  6.         url = urlNormalizers.normalize(url, URLNormalizers.SCOPE_INJECT);   
  7.         url = filters.filter(url);             // filter the url   
  8.       } catch (Exception e) {   
  9.         if (LOG.isWarnEnabled()) { LOG.warn("Skipping " +url+":"+e); }   
  10.         url = null;   
  11.       }   
  12.       if (url != null) {                          // if it passes   
  13.         value.set(url);                           // collect it   
  14.         CrawlDatum datum = new CrawlDatum(CrawlDatum.STATUS_INJECTED, interval);   
  15.         datum.setFetchTime(curTime);   
  16.         datum.setScore(scoreInjected);   
  17.         try {   
  18.           scfilters.injectedScore(value, datum);   
  19.         } catch (ScoringFilterException e) {   
  20.           if (LOG.isWarnEnabled()) {   
  21.             LOG.warn("Cannot filter injected score for url " + url +   
  22.                      ", using default (" + e.getMessage() + ")");   
  23.           }   
  24.           datum.setScore(scoreInjected);   
  25.         }   
  26.         output.collect(value, datum);   // 一个URL对应一个状态信息,key是URL,值是状态信息   
  27.       }   
  28.     }  
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 = filters.filter(url);             // filter the url
      } catch (Exception e) {
        if (LOG.isWarnEnabled()) { LOG.warn("Skipping " +url+":"+e); }
        url = null;
      }
      if (url != null) {                          // if it passes
        value.set(url);                           // collect it
        CrawlDatum datum = new CrawlDatum(CrawlDatum.STATUS_INJECTED, interval);
        datum.setFetchTime(curTime);
        datum.setScore(scoreInjected);
        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);
        }
        output.collect(value, datum);   // 一个URL对应一个状态信息,key是URL,值是状态信息
      }
    }


      这个map方法的主要功能是将用户输入的初始URL从文本转换为<url, CrawlDatum>键值对类型,CrawlDatum是一个链接的爬行状态信息。首先使用nomalizer对输入的URL进行规范化,如果不能通过规范化则返回,否则向crawldatum中打入链接的相关信息:当前的爬行状态、链接得分(如果ScoringFilter中指定了在inject阶段怎样计算注入链接的得分则使用这个得分,否则使用默认的1.0)、抓取时间。最后,收集url和与它对应的爬行状态。
      程序返回到inject方法中继续执行:

Java代码 复制代码
  1. // merge with existing crawl db   
  2.     if (LOG.isInfoEnabled()) {   
  3.       LOG.info("Injector: Merging injected urls into crawl db.");   
  4.     }   
  5.     JobConf mergeJob = CrawlDb.createJob(getConf(), crawlDb);   
  6.     FileInputFormat.addInputPath(mergeJob, tempDir);   
  7.     mergeJob.setReducerClass(InjectReducer.class);   
  8.     JobClient.runJob(mergeJob);   
  9.     CrawlDb.install(mergeJob, crawlDb);   
  10.   
  11.     // clean up   
  12.     FileSystem fs = FileSystem.get(getConf());   
  13.     fs.delete(tempDir, true);   
  14.     if (LOG.isInfoEnabled()) { LOG.info("Injector: done"); }   
  15.   
  16.   } // inject方法结束  
// merge with existing crawl db
    if (LOG.isInfoEnabled()) {
      LOG.info("Injector: Merging injected urls into crawl db.");
    }
    JobConf mergeJob = CrawlDb.createJob(getConf(), crawlDb);
    FileInputFormat.addInputPath(mergeJob, tempDir);
    mergeJob.setReducerClass(InjectReducer.class);
    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"); }

  } // inject方法结束


      这部分首先调用CrawlDb的createJob静态方法对接下来的将injected URL合并到crawlDb的MapReduce任务进行配置。createJob将FileInputPath设置为crawl/crawlDb/current,Mapper类设置为CrawlDbFilter,Reducer类设置为CrawlDbReducer,输出路径(outputPath)设置为crawl/crawlDb/下的一个以随机数命名的目录,输出的键值对类型是<Text, CrawlDatum>,此后creatJob返回。
      在该方法后,inject方法继续对接下来的MapReduce任务进行配置,增加一个输入路径tempDir(即上次Map任务的输出。这样,Map任务实际有两个输入路径),设置Reducer类为InjectReducer(此处待确认:覆盖creatJob中设置的Reducer?????)。
      Map阶段CrawlDbFilter的作用是将crawl/crawldb/current和上一步Map处理(读入初始URL并map)后的<url, crawldatum>键值对经过UrlNormalizer和UrlFilter规范化(如字母大小写等)和过滤(根据用户配置,决定是否保留这个url)后,然后输出到tempDir中。
      Reduce阶段InjectReducer的任务是将已存在于current中的网页数据库和新加入在outputPath中的数据库进行合并。如果新加入的网页已存在于current中,则不修改其状态,否则将新加入链接datum的状态从STATUS_INJECTED改为STATUS_DB_UNFETCHED。这样就完成了新旧数据库的合并
最后,通过CrawlDb.install(mergeJob, crawlDb)方法将tempDir删除。将新的数据库命名为current。
      至此,Inject阶段的任务完成,该阶段数据处理的流程图如下: