Nutch 1.3 学习笔记 4-1 SegmentReader分析

来源:互联网 发布:mysql 启动失败 日志 编辑:程序博客网 时间:2024/06/11 01:01

分类: Nutch 852人阅读 评论(1) 收藏 举报
Nutch 1.3 学习笔记 4-1 SegmentReader分析
-----------------------------------
前面我们看了一下Generate的流程,它是为Fetch产生相应的fetchlist,这里想介绍一下Segment的查看工具SegmentReader类。


1. 命令介绍

[html] view plaincopy
  1. bin/nutch readseg  
  2.     Usage: SegmentReader (-dump ... | -list ... | -get ...) [general options]  

// 这里是一般的参数说明
[html] view plaincopy
  1. * General options:  
  2.         -nocontent  ignore content directory  
  3.         -nofetch    ignore crawl_fetch directory  
  4.         -nogenerate ignore crawl_generate directory  
  5.         -noparse    ignore crawl_parse directory  
  6.         -noparsedata    ignore parse_data directory  
  7.         -noparsetext    ignore parse_text directory  

// 这里用于下载segment的内容,把其转换成文本格式,后面可以加General options参数,
// 看是不是过滤相应的目录
[html] view plaincopy
  1. * SegmentReader -dump <segment_dir> <output> [general options]  
  2.         Dumps content of a <segment_dir> as a text file to <output>.  
  3.   
  4.   
  5.         <segment_dir> name of the segment directory.  
  6.         <output>  name of the (non-existent) output directory.  

// 列出相应的segment信息
[html] view plaincopy
  1. * SegmentReader -list (<segment_dir1> ... | -dir <segments>) [general options]  
  2.         List a synopsis of segments in specified directories, or all segments in  
  3.         a directory <segments>, and print it on System.out  
  4.   
  5.   
  6.         <segment_dir1> ...    list of segment directories to process  
  7.         -dir <segments>       directory that contains multiple segments  

// 得到相对应的url的信息
[html] view plaincopy
  1. * SegmentReader -get <segment_dir> <keyValue> [general options]  
  2.         Get a specified record from a segment, and print it on System.out.  
  3.   
  4.   
  5.         <segment_dir> name of the segment directory.  
  6.         <keyValue>    value of the key (url).  
  7.             Note: put double-quotes around strings with spaces.  

2. 每一个命令的运行结果

  2.1 bin/nutch readseg -dump 

在本地运行其命令的结果如下:
// 下载一个segment
[html] view plaincopy
  1. lemo@debian:~/Workspace/java/Apache/Nutch/nutch-1.3$ bin/nutch readseg -dump db/segments/20110822105243/ output  
  2.     SegmentReader: dump segment: db/segments/20110822105243  
  3.     SegmentReader: done  

        // 下载目录浏览
[html] view plaincopy
  1. lemo@debian:~/Workspace/java/Apache/Nutch/nutch-1.3$ ls output  
  2.     dump  

// 输出一部分下载信息
[html] view plaincopy
  1. lemo@debian:~/Workspace/java/Apache/Nutch/nutch-1.3$ head output/dump   
  2.     Recno:: 0  
  3.     URL:: http://baike.baidu.com/  
  4.     CrawlDatum::  
  5.     Version: 7  
  6.     Status: 67 (linked)  
  7.     Fetch time: Mon Aug 22 10:58:21 EDT 2011  
  8.     Modified time: Wed Dec 31 19:00:00 EST 1969  
  9.     Retries since fetch: 0  

我们来看一下其源代码是怎么写的,这个shell命令最终是调用org.apache.nutch.segment.SegmentReader中的dump方法,如下是这个方法的主要源代码:

[html] view plaincopy
  1. // 这里生成一个MP任务  
  2. JobConf job = createJobConf();  
  3.    job.setJobName("read " + segment);  
  4.   
  5.   
  6. // 查看General Options的参数,是否过滤相应的目录  
  7.    if (ge) FileInputFormat.addInputPath(job, new Path(segment, CrawlDatum.GENERATE_DIR_NAME));  
  8.    if (fe) FileInputFormat.addInputPath(job, new Path(segment, CrawlDatum.FETCH_DIR_NAME));  
  9.    if (pa) FileInputFormat.addInputPath(job, new Path(segment, CrawlDatum.PARSE_DIR_NAME));  
  10.    if (co) FileInputFormat.addInputPath(job, new Path(segment, Content.DIR_NAME));  
  11.    if (pd) FileInputFormat.addInputPath(job, new Path(segment, ParseData.DIR_NAME));  
  12.    if (pt) FileInputFormat.addInputPath(job, new Path(segment, ParseText.DIR_NAME));  
  13.   
  14.   
  15. // 输入的目录文件格式,这里是SequenceFileInputFormat  
  16.    job.setInputFormat(SequenceFileInputFormat.class);  
  17. // 相应的Map与Reducer操作  
  18.    job.setMapperClass(InputCompatMapper.class);  // 这里主要是把key转成UTF8格式,  
  19.    job.setReducerClass(SegmentReader.class);        // 把相应的value对象反序列化成Text类型  
  20.   
  21.   
  22.    Path tempDir = new Path(job.get("hadoop.tmp.dir", "/tmp") + "/segread-" + new java.util.Random().nextInt());  
  23.    fs.delete(tempDir, true);  
  24.      
  25.    FileOutputFormat.setOutputPath(job, tempDir);   // 输出目录  
  26.    job.setOutputFormat(TextOutputFormat.class);   // output text  
  27.    job.setOutputKeyClass(Text.class);  
  28.    job.setOutputValueClass(NutchWritable.class);  // 输出的value类型,这里要注意一下,因为上面Reducer是SegmentReader,其输出的<key,value>类型为<Text,Text>,而这里的value类型为NutchWritable,这里使用了强制类型转换。不知道这么做是为什么?  
  29.   
  30.   
  31.    JobClient.runJob(job);  
  32.   
  33.   
  34.    // concatenate the output  
  35.    Path dumpFile = new Path(output, job.get("segment.dump.dir", "dump"));  
  36.   
  37.   
  38.    // remove the old file  
  39.    fs.delete(dumpFile, true);  
  40.    FileStatus[] fstats = fs.listStatus(tempDir, HadoopFSUtil.getPassAllFilter());  
  41.    Path[] files = HadoopFSUtil.getPaths(fstats);  
  42.   
  43.   
  44.    PrintWriter writer = null;  
  45.    int currentRecordNumber = 0;  
  46. // 这里主要是合并上面的临时文件到正式的目录文件中output/dump  
  47. // 并且加一些格式信息,使用append方法  
  48.    if (files.length > 0) {  
  49.     // create print writer with format  
  50.      writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(fs.create(dumpFile))));  
  51.      try {  
  52.        for (int i = 0; i < files.length; i++) {   // read tmp files  
  53.          Path partFile = (Path) files[i];  
  54.          try {  
  55.            currentRecordNumber = append(fs, job, partFile, writer, currentRecordNumber);  
  56.          } catch (IOException exception) {  
  57.            if (LOG.isWarnEnabled()) {  
  58.              LOG.warn("Couldn't copy the content of " + partFile.toString() +  
  59.                       " into " + dumpFile.toString());  
  60.              LOG.warn(exception.getMessage());  
  61.            }  
  62.          }  
  63.        }  
  64.      } finally {  
  65.        writer.close();  
  66.      }  
  67.    }  
  68.    fs.delete(tempDir); // 删除临时目录  
  69.    if (LOG.isInfoEnabled()) { LOG.info("SegmentReader: done"); }  

2.2 bin/nutch readseg -list

在本地的运行结果如下:
列出单个Segment的信息
[html] view plaincopy
  1. lemo@debian:~/Workspace/java/Apache/Nutch/nutch-1.3$ bin/nutch readseg -list db/segments/20110822105243/  
  2.     NAME        GENERATED   FETCHER START       FETCHER END     FETCHED PARSED  
  3.     20110822105243  1       2011-08-22T10:56:09 2011-08-22T10:56:09 1   1  

列出多个Segment的信息
[html] view plaincopy
  1. lemo@debian:~/Workspace/java/Apache/Nutch/nutch-1.3$ bin/nutch readseg -list -dir db/segments/  
  2.     NAME        GENERATED   FETCHER START       FETCHER END     FETCHED PARSED  
  3.     20110822105243  1       2011-08-22T10:56:09 2011-08-22T10:56:09 1   1  
  4.     20110825112318  9       ?       ?   ?   ?  
  5.     20110825112320  10      ?       ?   ?   ?  

下面来看一下其源代码,调用的是SegmentReader中的list方法,主要代码如下:
  这个list方法调用了另外一个getStats方法,得到单个Segment的信息

[html] view plaincopy
  1. // 得到一个文件的读取器  
  2. SequenceFile.Reader[] readers = SequenceFileOutputFormat.getReaders(getConf(), new Path(segment, CrawlDatum.GENERATE_DIR_NAME));  
  3.    long cnt = 0L;  
  4.    Text key = new Text();  
  5.   
  6.   
  7. // 计算这个Segment有多少url  
  8.    for (int i = 0; i < readers.length; i++) {  
  9.      while (readers[i].next(key)) cnt++;  
  10.      readers[i].close();  
  11.    }  
  12.    stats.generated = cnt;   // get generated url count(cnt)  
  13.      
  14.    // parse fetch dir  解析fetch目录,计算Fetch的开始与结束时间和fetch网页个数  
  15. // 主要这里的fetch目录的文件格式为MapFileOutputFormat  
  16.    Path fetchDir = new Path(segment, CrawlDatum.FETCH_DIR_NAME);  
  17.    if (fs.exists(fetchDir) && fs.getFileStatus(fetchDir).isDir()) {  
  18.      cnt = 0L;  
  19.      long start = Long.MAX_VALUE;  
  20.      long end = Long.MIN_VALUE;  
  21.      CrawlDatum value = new CrawlDatum();  
  22.      MapFile.Reader[] mreaders = MapFileOutputFormat.getReaders(fs, fetchDir, getConf());  
  23.      for (int i = 0; i < mreaders.length; i++) {  
  24.        while (mreaders[i].next(key, value)) {  
  25.          cnt++;  
  26.          if (value.getFetchTime() < startstart = value.getFetchTime();  
  27.          if (value.getFetchTime() > end) end = value.getFetchTime();  
  28.        }  
  29.        mreaders[i].close();  
  30.      }  
  31.      stats.start = start;  
  32.      stats.end = end;  
  33.      stats.fetched = cnt;  
  34.    }  
  35.      
  36.    // parse parsed dir 解析parsed目录,得到解析成功与出错的网页个数  
  37.    Path parseDir = new Path(segment, ParseData.DIR_NAME);  
  38.    if (fs.exists(fetchDir) && fs.getFileStatus(fetchDir).isDir()) {  
  39.      cnt = 0L;  
  40.      long errors = 0L;  
  41.      ParseData value = new ParseData();  
  42.      MapFile.Reader[] mreaders = MapFileOutputFormat.getReaders(fs, parseDir, getConf());  
  43.      for (int i = 0; i < mreaders.length; i++) {  
  44.        while (mreaders[i].next(key, value)) {  
  45.          cnt++;  
  46.          if (!value.getStatus().isSuccess()) errors++;  
  47.        }  
  48.        mreaders[i].close();  
  49.      }  
  50.      stats.parsed = cnt;  
  51.      stats.parseErrors = errors;  
  52.    }  

2.3 bin/nutch readseg -get 用于得到特定url的信息

本机运行结果
[html] view plaincopy
  1. lemo@debian:~/Workspace/java/Apache/Nutch/nutch-1.3$ bin/nutch readseg -get db/segments/20110822105243/     http://hi.baidu.com/  
  2.     SegmentReader: get 'http://hi.baidu.com/'  
  3.     Crawl Parse::  
  4.     Version: 7  
  5.     Status: 67 (linked)  
  6.     Fetch time: Mon Aug 22 10:58:21 EDT 2011  
  7.     Modified time: Wed Dec 31 19:00:00 EST 1969  
  8.     Retries since fetch: 0  
  9.     Retry interval: 2592000 seconds (30 days)  
  10.     Score: 0.045454547  
  11.     Signature: null  
  12.     Metadata:   

下面我们来看一下它的源代码,它是调用SegmentReader中的get方法,主要代码如下:
[html] view plaincopy
  1. public void get(final Path segment, final Text key, Writer writer,  
  2.           final Map<String, List<Writable>> results) throws Exception {  
  3.     LOG.info("SegmentReader: get '" + key + "'");  
  4.     // 这里使用的inner class来实现对于不同目录的异步读取  
  5.     ArrayList<Thread> threads = new ArrayList<Thread>();  
  6.     if (co) threads.add(new Thread() {  
  7.       public void run() {  
  8.         try {  
  9.             // 从MapFileOutputFormat格式的文件中找到相应的key的value值  
  10.           List<Writable> res = getMapRecords(new Path(segment, Content.DIR_NAME), key);  
  11.           // NOTE:有没有注意到这个results是一个HashMap,而在Java中这个HashMap不是线程安全的  
  12.           // 在极端的情况下,会出现多个线程同时put数据,这里是不是应该把result改成线程安全的  
  13.           // 如 Map result = Collections.synchronizedMap(new HashMap(...));  
  14.           results.put("co", res);  
  15.         } catch (Exception e) {  
  16.           e.printStackTrace(LogUtil.getWarnStream(LOG));  
  17.         }  
  18.       }  
  19.     });  
  20.     .....  
  21.     if (pt) threads.add(new Thread() {  
  22.       public void run() {  
  23.         try {  
  24.           List<Writable> res = getMapRecords(new Path(segment, ParseText.DIR_NAME), key);  
  25.           results.put("pt", res);  
  26.         } catch (Exception e) {  
  27.           e.printStackTrace(LogUtil.getWarnStream(LOG));  
  28.         }  
  29.       }  
  30.     });  
  31.     Iterator<Thread> it = threads.iterator();  
  32.     while (it.hasNext()) it.next().start(); // 运行所有线程  
  33.     int cnt;  
  34.   
  35.   
  36.     // 这里用一个循环还查看线程是否运行结束  
  37.     do {  
  38.       cnt = 0;  
  39.       try {  
  40.         Thread.sleep(5000);  
  41.       } catch (Exception e) {};  
  42.       it = threads.iterator();  
  43.       while (it.hasNext()) {  
  44.         if (it.next().isAlive()) cnt++;  
  45.       }  
  46.       if ((cnt > 0) && (LOG.isDebugEnabled())) {  
  47.         LOG.debug("(" + cnt + " to retrieve)");  
  48.       }  
  49.     } while (cnt > 0);  
  50.     // 把收集的结果输出到终端  
  51.     for (int i = 0; i < keys.length; i++) {  
  52.       List<Writable> res = results.get(keys[i][0]);  
  53.       if (res != null && res.size() > 0) {  
  54.         for (int k = 0; k < res.size(); k++) {  
  55.           writer.write(keys[i][1]);  
  56.           writer.write(res.get(k) + "\n");  
  57.         }  
  58.       }  
  59.       writer.flush();  
  60.     }  
  61.   }  


  3. 总结

  这里大概介绍了一下bin/nutch readseg的使用,主要是来查看前面generate和后面的fetch,parse的结束是否有问题。


  4. 参考

  http://download.oracle.com/javase/1.4.2/docs/api/java/util/HashMap.html
原创粉丝点击