`

索引擎Nutch源代码研究之一 网页抓取(4)

阅读更多
今天来看看Nutch如何Parse网页的:
Nutch使用了两种Html parser工具(NekoHTML和TagSoup)来实现html的提取,这两种工具是可通过配置来选择的。
当然你要自己实现Parser你还可以选择HTMLParser[基于visitor访问者模式同时也提供了Event driver的接口]来
提取网页。如果你用惯了XML一套处理方法,使用NekoHTML和TagSoup应该会比较顺手的。
我们来看看类public class HtmlParser implements Parser的实现:
首先为了更好的理解下面的代码先看看成员变量:
Java代码 复制代码
  1. private static final int CHUNK_SIZE = 2000;   
  2.  private static Pattern metaPattern =   
  3.    Pattern.compile("<meta\\s+([^>]*http-equiv=\"?content-type\"?[^>]*)>",   
  4.                    Pattern.CASE_INSENSITIVE);   
  5.  private static Pattern charsetPattern =   
  6.    Pattern.compile("charset=\\s*([a-z][_\\-0-9a-z]*)",   
  7.                    Pattern.CASE_INSENSITIVE);   
  8.     
  9.  private String parserImpl;  
 private static final int CHUNK_SIZE = 2000;
  private static Pattern metaPattern =
    Pattern.compile("<meta\\s+([^>]*http-equiv=\"?content-type\"?[^>]*)>",
                    Pattern.CASE_INSENSITIVE);
  private static Pattern charsetPattern =
    Pattern.compile("charset=\\s*([a-z][_\\-0-9a-z]*)",
                    Pattern.CASE_INSENSITIVE);
  
  private String parserImpl;

CHUNK_SIZE提取html meta tag部分的html片断的长度,一般meta tag没有超过2000bytes的,所以只需要从这部分
提取就行了
metaPattern为meta tag匹的正则模式
charsetPattern为字符集编码的正则模式
parserImpl是具体使用的是NekoHTML还是TagSoup来parser html.如果parserImpl为"tagsoup"就使用TagSoup,否则就使用NekoHTML。
用来从html在meta tag里面提取出charset或Content-Type中指定的编码:
length限定在meta tag部分提取,通过正则表达式很容易提取出编码
Java代码 复制代码
  1. private static String sniffCharacterEncoding(byte[] content) {   
  2.     int length = content.length < CHUNK_SIZE ?    
  3.                  content.length : CHUNK_SIZE;   
  4.   
  5.     // We don't care about non-ASCII parts so that it's sufficient   
  6.     // to just inflate each byte to a 16-bit value by padding.    
  7.     // For instance, the sequence {0x41, 0x82, 0xb7} will be turned into    
  8.     // {U+0041, U+0082, U+00B7}.    
  9.     String str = new String(content, 00, length);    
  10.   
  11.     Matcher metaMatcher = metaPattern.matcher(str);   
  12.     String encoding = null;   
  13.     if (metaMatcher.find()) {   
  14.       Matcher charsetMatcher = charsetPattern.matcher(metaMatcher.group(1));   
  15.       if (charsetMatcher.find())    
  16.         encoding = new String(charsetMatcher.group(1));   
  17.     }   
  18.   
  19.     return encoding;   
  20.   }  
private static String sniffCharacterEncoding(byte[] content) {
    int length = content.length < CHUNK_SIZE ? 
                 content.length : CHUNK_SIZE;

    // We don't care about non-ASCII parts so that it's sufficient
    // to just inflate each byte to a 16-bit value by padding. 
    // For instance, the sequence {0x41, 0x82, 0xb7} will be turned into 
    // {U+0041, U+0082, U+00B7}. 
    String str = new String(content, 0, 0, length); 

    Matcher metaMatcher = metaPattern.matcher(str);
    String encoding = null;
    if (metaMatcher.find()) {
      Matcher charsetMatcher = charsetPattern.matcher(metaMatcher.group(1));
      if (charsetMatcher.find()) 
        encoding = new String(charsetMatcher.group(1));
    }

    return encoding;
  }

最重要的一个方法是:
public Parse getParse(Content content)
这个方法返回了包含了提取所有结果Parse对象:
这个方法写的比较长,近100行,其实整个方法可以分解成几个小方法:
提取base url,提取encoding,根据提取出的编码提取content,提取meta tags,提取outlinks,最后根据提取得到的
text和parseDate构造Parse对象
下面我们一个一个看:
提取base url
Java代码 复制代码
  1. URL base;   
  2.     try {   
  3.       base = new URL(content.getBaseUrl());   
  4.     } catch (MalformedURLException e) {   
  5.       return new ParseStatus(e).getEmptyParse(getConf());   
  6.     }  
URL base;
    try {
      base = new URL(content.getBaseUrl());
    } catch (MalformedURLException e) {
      return new ParseStatus(e).getEmptyParse(getConf());
    }

提取encoding:
Java代码 复制代码
  1.  //直接从content中的metadata中提取   
  2.  byte[] contentInOctets = content.getContent();   
  3.  InputSource input = new InputSource(new ByteArrayInputStream(contentInOctets));   
  4.  String contentType = content.getMetadata().get(Response.CONTENT_TYPE);   
  5.  String encoding = StringUtil.parseCharacterEncoding(contentType);   
  6.  if ((encoding != null) && !("".equals(encoding))) {   
  7.    metadata.set(Metadata.ORIGINAL_CHAR_ENCODING, encoding);   
  8.    if ((encoding = StringUtil.resolveEncodingAlias(encoding)) != null) {   
  9.      metadata.set(Metadata.CHAR_ENCODING_FOR_CONVERSION, encoding);   
  10.      if (LOG.isTraceEnabled()) {   
  11.        LOG.trace(base + ": setting encoding to " + encoding);   
  12.      }   
  13.    }   
  14.  }   
  15. //如果从metadata中没有提取到,使用前面sniffCharacterEncoding从meta tag提取   
  16.  // sniff out 'charset' value from the beginning of a document   
  17.  if ((encoding == null) || ("".equals(encoding))) {   
  18.    encoding = sniffCharacterEncoding(contentInOctets);   
  19.    if (encoding!=null) {   
  20.      metadata.set(Metadata.ORIGINAL_CHAR_ENCODING, encoding);   
  21.      if ((encoding = StringUtil.resolveEncodingAlias(encoding)) != null) {   
  22.        metadata.set(Metadata.CHAR_ENCODING_FOR_CONVERSION, encoding);   
  23.        if (LOG.isTraceEnabled()) {   
  24.          LOG.trace(base + ": setting encoding to " + encoding);   
  25.        }   
  26.      }   
  27.    }   
  28.  }   
  29.  //如果还没有提取到,使用默认的编码   
  30.  if (encoding == null) {   
  31.    // fallback encoding.   
  32.    // FIXME : In addition to the global fallback value,   
  33.    // we should make it possible to specify fallback encodings for each ccTLD.   
  34.    // (e.g. se: windows-1252, kr: x-windows-949, cn: gb18030, tw: big5   
  35.    // doesn't work for jp because euc-jp and shift_jis have about the   
  36.    // same share)   
  37.    encoding = defaultCharEncoding;   
  38.    metadata.set(Metadata.CHAR_ENCODING_FOR_CONVERSION, defaultCharEncoding);   
  39.    if (LOG.isTraceEnabled()) {   
  40.      LOG.trace(base + ": falling back to " + defaultCharEncoding);   
  41.    }   
  42.  }  
      //直接从content中的metadata中提取
      byte[] contentInOctets = content.getContent();
      InputSource input = new InputSource(new ByteArrayInputStream(contentInOctets));
      String contentType = content.getMetadata().get(Response.CONTENT_TYPE);
      String encoding = StringUtil.parseCharacterEncoding(contentType);
      if ((encoding != null) && !("".equals(encoding))) {
        metadata.set(Metadata.ORIGINAL_CHAR_ENCODING, encoding);
        if ((encoding = StringUtil.resolveEncodingAlias(encoding)) != null) {
          metadata.set(Metadata.CHAR_ENCODING_FOR_CONVERSION, encoding);
          if (LOG.isTraceEnabled()) {
            LOG.trace(base + ": setting encoding to " + encoding);
          }
        }
      }
     //如果从metadata中没有提取到,使用前面sniffCharacterEncoding从meta tag提取
      // sniff out 'charset' value from the beginning of a document
      if ((encoding == null) || ("".equals(encoding))) {
        encoding = sniffCharacterEncoding(contentInOctets);
        if (encoding!=null) {
          metadata.set(Metadata.ORIGINAL_CHAR_ENCODING, encoding);
          if ((encoding = StringUtil.resolveEncodingAlias(encoding)) != null) {
            metadata.set(Metadata.CHAR_ENCODING_FOR_CONVERSION, encoding);
            if (LOG.isTraceEnabled()) {
              LOG.trace(base + ": setting encoding to " + encoding);
            }
          }
        }
      }
      //如果还没有提取到,使用默认的编码
      if (encoding == null) {
        // fallback encoding.
        // FIXME : In addition to the global fallback value,
        // we should make it possible to specify fallback encodings for each ccTLD.
        // (e.g. se: windows-1252, kr: x-windows-949, cn: gb18030, tw: big5
        // doesn't work for jp because euc-jp and shift_jis have about the
        // same share)
        encoding = defaultCharEncoding;
        metadata.set(Metadata.CHAR_ENCODING_FOR_CONVERSION, defaultCharEncoding);
        if (LOG.isTraceEnabled()) {
          LOG.trace(base + ": falling back to " + defaultCharEncoding);
        }
      }

设置好编码方式,从content中提取DocumentFragment
Java代码 复制代码
  1. input.setEncoding(encoding);   
  2.      if (LOG.isTraceEnabled()) { LOG.trace("Parsing..."); }   
  3.      root = parse(input);   
  4.    } catch (IOException e) {   
  5.      return new ParseStatus(e).getEmptyParse(getConf());   
  6.    } catch (DOMException e) {   
  7.      return new ParseStatus(e).getEmptyParse(getConf());   
  8.    } catch (SAXException e) {   
  9.      return new ParseStatus(e).getEmptyParse(getConf());   
  10.    } catch (Exception e) {   
  11.      e.printStackTrace(LogUtil.getWarnStream(LOG));   
  12.      return new ParseStatus(e).getEmptyParse(getConf());   
  13.    }  
 input.setEncoding(encoding);
      if (LOG.isTraceEnabled()) { LOG.trace("Parsing..."); }
      root = parse(input);
    } catch (IOException e) {
      return new ParseStatus(e).getEmptyParse(getConf());
    } catch (DOMException e) {
      return new ParseStatus(e).getEmptyParse(getConf());
    } catch (SAXException e) {
      return new ParseStatus(e).getEmptyParse(getConf());
    } catch (Exception e) {
      e.printStackTrace(LogUtil.getWarnStream(LOG));
      return new ParseStatus(e).getEmptyParse(getConf());
    }

提取meta tag,并检查meta指令
Java代码 复制代码
  1. HTMLMetaProcessor.getMetaTags(metaTags, root, base);   
  2.  if (LOG.isTraceEnabled()) {   
  3.    LOG.trace("Meta tags for " + base + ": " + metaTags.toString());   
  4.  }   
  5.  // check meta directives   
  6.  if (!metaTags.getNoIndex()) {               // okay to index   
  7.    StringBuffer sb = new StringBuffer();   
  8.    if (LOG.isTraceEnabled()) { LOG.trace("Getting text..."); }   
  9.    utils.getText(sb, root);          // extract text   
  10.    text = sb.toString();   
  11.    sb.setLength(0);   
  12.    if (LOG.isTraceEnabled()) { LOG.trace("Getting title..."); }   
  13.    utils.getTitle(sb, root);         // extract title   
  14.    title = sb.toString().trim();   
  15.  }  
   HTMLMetaProcessor.getMetaTags(metaTags, root, base);
    if (LOG.isTraceEnabled()) {
      LOG.trace("Meta tags for " + base + ": " + metaTags.toString());
    }
    // check meta directives
    if (!metaTags.getNoIndex()) {               // okay to index
      StringBuffer sb = new StringBuffer();
      if (LOG.isTraceEnabled()) { LOG.trace("Getting text..."); }
      utils.getText(sb, root);          // extract text
      text = sb.toString();
      sb.setLength(0);
      if (LOG.isTraceEnabled()) { LOG.trace("Getting title..."); }
      utils.getTitle(sb, root);         // extract title
      title = sb.toString().trim();
    }

提取出outlinks:
Java代码 复制代码
  1. if (!metaTags.getNoFollow()) {              // okay to follow links   
  2.       ArrayList l = new ArrayList();              // extract outlinks   
  3.       URL baseTag = utils.getBase(root);   
  4.       if (LOG.isTraceEnabled()) { LOG.trace("Getting links..."); }   
  5.       utils.getOutlinks(baseTag!=null?baseTag:base, l, root);   
  6.       outlinks = (Outlink[])l.toArray(new Outlink[l.size()]);   
  7.       if (LOG.isTraceEnabled()) {   
  8.         LOG.trace("found "+outlinks.length+" outlinks in "+content.getUrl());   
  9.       }   
  10.     }  
if (!metaTags.getNoFollow()) {              // okay to follow links
      ArrayList l = new ArrayList();              // extract outlinks
      URL baseTag = utils.getBase(root);
      if (LOG.isTraceEnabled()) { LOG.trace("Getting links..."); }
      utils.getOutlinks(baseTag!=null?baseTag:base, l, root);
      outlinks = (Outlink[])l.toArray(new Outlink[l.size()]);
      if (LOG.isTraceEnabled()) {
        LOG.trace("found "+outlinks.length+" outlinks in "+content.getUrl());
      }
    }

构建parse对象:
Java代码 复制代码
  1. ParseStatus status = new ParseStatus(ParseStatus.SUCCESS);   
  2.     if (metaTags.getRefresh()) {   
  3.       status.setMinorCode(ParseStatus.SUCCESS_REDIRECT);   
  4.       status.setMessage(metaTags.getRefreshHref().toString());   
  5.     }   
  6.     ParseData parseData = new ParseData(status, title, outlinks,   
  7.                                         content.getMetadata(), metadata);   
  8.     parseData.setConf(this.conf);   
  9.     Parse parse = new ParseImpl(text, parseData);   
  10.   
  11.     // run filters on parse   
  12.     parse = this.htmlParseFilters.filter(content, parse, metaTags, root);   
  13.     if (metaTags.getNoCache()) {             // not okay to cache   
  14.       parse.getData().getParseMeta().set(Nutch.CACHING_FORBIDDEN_KEY, cachingPolicy);   
  15.     }  
ParseStatus status = new ParseStatus(ParseStatus.SUCCESS);
    if (metaTags.getRefresh()) {
      status.setMinorCode(ParseStatus.SUCCESS_REDIRECT);
      status.setMessage(metaTags.getRefreshHref().toString());
    }
    ParseData parseData = new ParseData(status, title, outlinks,
                                        content.getMetadata(), metadata);
    parseData.setConf(this.conf);
    Parse parse = new ParseImpl(text, parseData);

    // run filters on parse
    parse = this.htmlParseFilters.filter(content, parse, metaTags, root);
    if (metaTags.getNoCache()) {             // not okay to cache
      parse.getData().getParseMeta().set(Nutch.CACHING_FORBIDDEN_KEY, cachingPolicy);
    }

下面这个方法根据parserImpl字段,使用NekoHTML或TagSoup来提取content得到DocumentFragment对象
Java代码 复制代码
  1. private DocumentFragment parse(InputSource input) throws Exception {   
  2.    if (parserImpl.equalsIgnoreCase("tagsoup"))   
  3.      return parseTagSoup(input);   
  4.    else return parseNeko(input);   
  5.  }  
 private DocumentFragment parse(InputSource input) throws Exception {
    if (parserImpl.equalsIgnoreCase("tagsoup"))
      return parseTagSoup(input);
    else return parseNeko(input);
  }

网页抓取部分到此基本结束,必要的部分相应再作补充。等研究好google的map-reduce再继续其他部分。
分享到:
评论

相关推荐

    nutch-2.1源代码

    4. **索引更新与合并**:Nutch支持增量索引,这意味着它可以仅对新抓取或更新的网页进行索引,而不是重新索引整个集合。索引合并则用于定期整合多个小索引,以减少索引碎片并优化查询性能。 5. **搜索接口**:Nutch...

    nutch的源代码解析

    Nutch 的源代码解析对于深入理解搜索引擎的工作原理以及自定义搜索引擎的实现非常有帮助。下面我们将详细探讨 Nutch 的注入(Injector)过程,这是整个爬取流程的第一步。 Injector 类在 Nutch 中的作用是将输入的 ...

    nutch 1.5的源代码

    Nutch 1.5 是一个基于Java开发的开源搜索引擎项目,它主要负责网络抓取、索引和搜索等功能。这个源代码包包含了实现这些功能的所有模块和组件,为开发者提供了深入理解搜索引擎工作原理以及定制化搜索引擎的机会。接...

    nutch网页爬取总结

    - **配置 Nutch 创建索引**:下载 Nutch 的源代码并解压,然后通过 Maven 进行编译。配置 Nutch 的 `conf/nutch-site.xml` 文件以设置存储路径、抓取策略等参数。 - **安装 Tomcat**:Tomcat 用于运行 Nutch 的 UI...

    Lucene+nutch搜索引擎开发(源代码)

    2. **Nutch源代码**:包括Nutch的爬虫模块、索引模块和搜索模块,可以帮助开发者学习如何配置和运行一个完整的网络爬虫,以及如何与Lucene集成进行全文检索。 3. **示例项目**:可能包含了一些示例应用,展示如何...

    nutch使用&Nutch;入门教程

    在使用Nutch之前,你需要配置Nutch的运行环境,包括安装Java、设置Hadoop(如果需要分布式爬取)、下载和编译Nutch源代码。还需要配置Nutch的`conf/nutch-site.xml`文件,指定抓取策略、存储路径、爬虫范围等参数。 ...

    apache-nutch-2.3

    Apache Nutch 是一个开源的网络爬虫框架,用于抓取互联网上的网页并建立索引,以便于搜索引擎进行高效的检索。Nutch 2.3 版本是该项目的一个稳定版本,包含了丰富的功能和优化,是学习和研究网络爬虫技术的理想选择...

    nutch帮助文档;nutch学习 入门

    Nutch最初设计的目标是创建一个与商业搜索引擎相媲美的开放源代码搜索解决方案,用于企业内部或特定领域的信息检索。 2. **Nutch的功能** - **网页抓取**:Nutch使用爬虫技术,通过种子URL开始,遍历互联网上的...

    Lucene+Nutch搜索引擎开发.王学松源代码

    其次,Nutch是基于Lucene构建的一个完整的网络爬虫项目,它不仅包括了网页抓取、解析、存储等功能,还集成了Lucene的索引和搜索能力。Nutch的工作流程如下: 1. 网页抓取:Nutch使用爬虫程序遍历互联网上的网页,...

    nutch解决搜索结果高亮和网页快照链接无效及网页变形

    Nutch 提供了内置的高亮功能,可以通过修改或扩展其源代码来实现。例如,`HeightLighter.java` 文件可能就是用于处理高亮逻辑的类。高亮过程通常包括以下步骤: - 在查询阶段,保存用户输入的关键词。 - 在搜索...

    Nutch_插件深入研究

    它基于Hadoop,能够从互联网上抓取和索引网页,构建搜索引擎。Nutch的强大之处在于其高度可定制性,这主要归功于它的插件系统。Nutch插件允许开发者根据特定需求定制和扩展Nutch的功能,如自定义爬虫策略、数据解析...

    apache-nutch的源码

    Apache Nutch 是一个开源的网络爬虫项目,用于抓取互联网上的网页并建立索引,以便于搜索引擎进行高效的信息检索。Nutch 源码的分析和理解对于想要深入研究搜索引擎工作原理、网页抓取技术和大数据处理的开发者来说...

    nutch入门教程

    Nutch的长远目标是打造一个成本低廉、易于配置且性能卓越的Web搜索引擎,能够每月抓取数十亿网页,维护庞大的索引,并提供快速且准确的搜索结果,同时保持较低的运营成本。 **1.4 Nutch VS Lucene** Nutch与Lucene...

    nutch-1.5.1源码

    这个版本的源码包含了实现Web抓取、索引和搜索功能的全部Java代码,为开发者提供了深入理解搜索引擎工作原理的机会。 Nutch的主要组件包括以下几个部分: 1. **Web抓取(Crawling)**:Nutch的抓取模块使用了...

    nutch2.2.1安装步骤.docx

    Apache Nutch 是一个开源的网络爬虫框架,用于抓取互联网上的网页并建立索引,以便进行全文搜索。Nutch 2.2.1 是一个稳定版本,它依赖于其他几个组件来完成其功能,包括 Apache Ant、Apache Tomcat、Java 开发工具包...

    nutch开发资料 搜索引擎

    1. **Nutch介绍**:Nutch是一个基于Java的开源Web爬虫,它能够抓取互联网上的网页,并对抓取的数据进行索引和搜索。Nutch的设计目标是提供可扩展性和高效率,适合大规模的Web数据处理。 2. **增量索引**:在Nutch中...

    关于Nutch的安装

    获取与解压Nutch源代码** Nutch的源代码可以通过两种方式获取: - **下载发行版**:从官方网站`http://lucene.apache.org/nutch/release/`下载最新版本的Nutch,然后解压缩到你选择的目录。 - **使用Subversion**...

    搭建nutch开发环境步骤

    它提供了一个可扩展的、高度模块化的框架,用于抓取、解析网页,并建立索引,是大数据和信息检索领域的重要工具。下面,我们将详细讲解如何搭建Nutch的开发环境。 **步骤一:系统准备** 在开始搭建Nutch开发环境...

    分享一个Nutch入门学习的资料

    Nutch是一款开源的网络爬虫软件,主要用于抓取和索引互联网上的网页,是大数据领域中的重要工具之一。本文将深入解析Nutch的基础知识,帮助初学者快速入门。 1. **Nutch简介** Nutch是由Apache软件基金会开发的一...

    nutch爬虫资料

    Nutch是一款开源的网络爬虫项目,主要用于抓取和索引互联网上的网页内容。它由Apache软件基金会开发,是Hadoop大数据生态系统的一部分,利用Java语言编写。本资料包围绕Nutch爬虫,提供了相关的参考书籍和源代码分析...

Global site tag (gtag.js) - Google Analytics