Nutch 1.3 學習筆記3 – Inject

來源:互聯網
上載者:User

Nutch 1.3 學習筆記 - Inject
----------------------------
1. Inject是幹嘛的?

在Nutch中Inject是用來把文字格式設定的url列表注入到抓取資料庫中,一般是用來引導系統的初始化。
這裡的文字格式設定如下:

http://www.nutch.org/ \t nutch.score=10 \t nutch.fetchInterval=2592000 \t userType=open_source

這裡的url與其中繼資料之間用Tab隔開,這裡有兩個保留的中繼資料,如下
nutch.score : 表示特定url的分數
nutch.fetchInterval : 表示特定url的抓取間隔,單位為毫秒
Inject注入後產生的資料庫為二進位結構,是Hadoop的MapSequenceFileOutputFormat格式

2. Inject運行命令

bin/nutch inject <url_dir> <crawl_db>

在本地運行後的輸出結果如下:

Injector: starting at 2011-08-23 10:14:10Injector: crawlDb: db/crawldbInjector: urlDir: urlsInjector: Converting injected urls to crawl db entries.Injector: Merging injected urls into crawl db.Injector: finished at 2011-08-23 10:14:12, elapsed: 00:00:02

你可以用如下命令來查看其資料庫內容

bin/nutch readdb <crawl_db> -stats -sort

在原生輸出如下:

rawlDb statistics start: db/crawldbStatistics for CrawlDb: db/crawldbTOTAL urls:1retry 0:1min score:1.0avg score:1.0max score:1.0status 1 (db_unfetched):1   www.baidu.com :1CrawlDb statistics: done

3. Inject原始碼分析

   我們知道Injector.java在Nutch原始碼中的位置為org.apache.nutch.crawl.Injector.java
   其中有一個main入口函數,使用Hadoop的工具類ToolRunner來運行其執行個體
   但其是終入口函數還是void inject(Path crawlDb, Path urlDir)
   其中有兩個MP任務,第一個主要是把檔案格式的輸入轉換成<url,CrawlDatum>格式的輸出,這裡的
   CrawlDatum是Nutch對於單個抓取url對象的一個抽象,其中有很多url的相關資訊
   第二個MP主要是把上面新產生的輸出與舊的CrawlDb資料進行合并,產生一個新的CrawlDb

3.1 對於Inject中第一個MP任務的分析

   第一個MP任務主要代碼如下:
    

JobConf sortJob = new NutchJob(getConf()); // 產生一個Nutch的配置抽象    sortJob.setJobName("inject " + urlDir);    FileInputFormat.addInputPath(sortJob, urlDir); // 設定InputFormat,這裡為FileInputFormat,這裡要注意的是可以調用多次addInputPath這個方法,效果是會有多個輸入源    sortJob.setMapperClass(InjectMapper.class);    // 這裡設定了Mapper方法,主要是用於解析、過濾和規格化url文本,把其轉換成<url,CrawlDatum>格式    FileOutputFormat.setOutputPath(sortJob, tempDir); // 這裡定義了一個輸出路徑,這裡的tempDir=mapred.temp.dir/inject-temp-Random()    sortJob.setOutputFormat(SequenceFileOutputFormat.class); // 這裡配置了輸出格式,這裡為SequenceFileOutputFormat,這是MP的一種二進位輸出結構    sortJob.setOutputKeyClass(Text.class);         // 這裡配置了MP的輸出<key,value>的類型,這裡為<Text,CrawlDatum>    sortJob.setOutputValueClass(CrawlDatum.class);    sortJob.setLong("injector.current.time", System.currentTimeMillis());    JobClient.runJob(sortJob);                     // 這裡用於提交任務到JobTracker,讓其運行任務

這裡對InjectMapper中的主要代碼進行分析:
這個類主要用於對url進行解析、過濾和規格化

 public void map(WritableComparable key, Text value,                    OutputCollector<Text, CrawlDatum> output, Reporter reporter)      throws IOException {      String url = value.toString();              // value is line of text      if (url != null && url.trim().startsWith("#")) {   // 這裡以#號開頭的文本就過濾          /* Ignore line that start with # */          return;      }      // if tabs : metadata that could be stored      // must be name=value and separated by \t      float customScore = -1f;      int customInterval = interval;      Map<String,String> metadata = new TreeMap<String,String>();  // 設定屬性的一個容器      if (url.indexOf("\t")!=-1){      String[] splits = url.split("\t");    // 對一行文本進行切分      url = splits[0];      for (int s=1;s<splits.length;s++){      // find separation between name 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);   // 如果這個中繼資料不是保留的中繼資料,就放到容器中      }      }      try {        url = urlNormalizers.normalize(url, URLNormalizers.SCOPE_INJECT);   // 對url進行規格化,這裡調用的是plugins中的外掛程式        url = filters.filter(url);             // filter the url            // 以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對象,設定一些url的初始化資料        CrawlDatum datum = new CrawlDatum(CrawlDatum.STATUS_INJECTED, customInterval);        datum.setFetchTime(curTime);    // 設定當前url的抓取時間        // now add the metadata        Iterator<String> keysIter = metadata.keySet().iterator();        while (keysIter.hasNext()){     // 配置其中繼資料        String keymd = keysIter.next();        String valuemd = metadata.get(keymd);        datum.getMetaData().put(new Text(keymd), new Text(valuemd));        }// 設定初始化分數        if (customScore != -1) datum.setScore(customScore);        else datum.setScore(scoreInjected);        try {// 這裡對url的分數進行初始化        scfilters.injectedScore(value, datum);        } catch (ScoringFilterException e) {        if (LOG.isWarnEnabled()) {        LOG.warn("Cannot filter injected score for url " + url        + ", using default (" + e.getMessage() + ")");        }        }// Map 收集相應的資料,類型為<Text,CrawlDatum>        output.collect(value, datum);      }    }  }

   3.2 第二個MP任務的分析

第二個MP任務主要是對crawlDb進行合并,原始碼如下:

// merge with existing crawl db    JobConf mergeJob = CrawlDb.createJob(getConf(), crawlDb); // 這裡對Job進行相應的配置    FileInputFormat.addInputPath(mergeJob, tempDir);          // 這裡配置了輸入的文本資料,就是上面第一個MP任務的輸出    mergeJob.setReducerClass(InjectReducer.class);            // 這裡配置了Reduce的抽象類別,這裡會覆蓋上面createJob設定的Reduce類    JobClient.runJob(mergeJob);                               // 提交運行任務    CrawlDb.install(mergeJob, crawlDb);                       // 把上面新產生的目錄重新命名為crawlDb的標準檔案夾名,然後再刪除老的目錄    // clean up    FileSystem fs = FileSystem.get(getConf());    fs.delete(tempDir, true);                                 // 把第一個MP任務的輸出目錄刪除

下面是createJob的原始碼說明:

public static JobConf createJob(Configuration config, Path crawlDb) throws IOException {// 產生新的CrawlDb檔案名稱    Path newCrawlDb =  new Path(crawlDb,Integer.toString(new Random().nextInt(Integer.MAX_VALUE)));    JobConf job = new NutchJob(config);   // 產生相應的Job配置抽象    job.setJobName("crawldb " + crawlDb);    Path current = new Path(crawlDb, CURRENT_NAME);    if (FileSystem.get(job).exists(current)) {   // 如果存在老的CrawlDb目錄,將其加入InputPath路徑中,和上面的tempDir一起進行合并      FileInputFormat.addInputPath(job, current);      }// NOTE:有沒有注意到這裡如果有老的CrawlDb目錄的話,那它的檔案格式是MapFileOutputFormat,而下面對其讀取用了SequenceFileInputFormat來讀,因為這兩個類底層都是調用了SequenceFile的Reader與Writer來讀寫的,所以可以通用。    job.setInputFormat(SequenceFileInputFormat.class);  // 設定CrawlDb目錄檔案的格式為SequenceFileInputFormat    job.setMapperClass(CrawlDbFilter.class);     // 設定相應的Map操作,主要是過濾和規格化url    job.setReducerClass(CrawlDbReducer.class);   // 設定相應的Reduce操作,主要是對url進行彙總    FileOutputFormat.setOutputPath(job, newCrawlDb);  // 設定新的輸出路徑    job.setOutputFormat(MapFileOutputFormat.class);   // 設定輸出的格式,這裡是MapFileOutputFormat// 這裡設定了輸出的類型<Text,CrawlDatum>    job.setOutputKeyClass(Text.class);    job.setOutputValueClass(CrawlDatum.class);    return job;  }


下面來看看覆蓋的InjectReducer都幹了些什麼,部分原始碼如下:

 public void reduce(Text key, Iterator<CrawlDatum> values,OutputCollector<Text, CrawlDatum> output, Reporter reporter)      throws IOException {      boolean oldSet = false;// 把相同url彙總後的結果進行處理,這個迴圈主要是新注入的url與老的url有沒有相同的,// 如果有相同的話就不設定其狀態,支援collect出去了      while (values.hasNext()) {        CrawlDatum val = values.next();        if (val.getStatus() == CrawlDatum.STATUS_INJECTED) {          injected.set(val);          injected.setStatus(CrawlDatum.STATUS_DB_UNFETCHED);        } else {          old.set(val);          oldSet = true;        }      }      CrawlDatum res = null;      if (oldSet) res = old; // don't overwrite existing value      else res = injected;      output.collect(key, res);    }  }

最後來看一下CrawlDb.install方法都幹了些什麼,其原始碼如下:

public static void install(JobConf job, Path crawlDb) throws IOException {    Path newCrawlDb = FileOutputFormat.getOutputPath(job);  // 得到第二個MP任務的輸出目錄    FileSystem fs = new JobClient(job).getFs();    Path old = new Path(crawlDb, "old");    Path current = new Path(crawlDb, CURRENT_NAME);         // 得到CrawlDb的正規目錄名,也就是有沒有老的CrawlDb    if (fs.exists(current)) {// 如果有老的CrawlDb目錄,把老的目錄名生命名為old這個名字      if (fs.exists(old)) fs.delete(old, true);   // 這裡判斷old這個目錄是不是已經存在,如果存在就刪除之      fs.rename(current, old);    }    fs.mkdirs(crawlDb);    fs.rename(newCrawlDb, current);                 // 這裡是把第二個MP任務的輸出目錄重新命名為current目錄,也就是正規目錄名    if (fs.exists(old)) fs.delete(old, true);       // 刪除重使名後的老的CrawlDb目錄    Path lock = new Path(crawlDb, LOCK_NAME);    LockUtil.removeLockFile(fs, lock);              // 目錄解鎖  }

4. 總結

Inject主要是從文字檔中注入新的url,使其與老的crawlDb中的url進行合并,然後把老的CrawlDb目錄刪除,現把新產生的CrawlDb臨時目錄重新命名為CrawlDb目錄名。
流程如下:
url_dir -> MapReduce1(inject new urls) -> MapReduece2(merge new urls with old crawlDb) -> install new CrawlDb -> clean up

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.