文档章节

Nutch学习笔记3:Nutch 1.7 版本 之 HtmlParser 解析流程分析

强子大叔的码田
 强子大叔的码田
发布于 2014/06/25 20:25
字数 1401
阅读 445
收藏 1

为了彻底弄明白Nutch中的Html页面解析流程,所以接下来研究下HtmlParser类。

路径:$nutch-1.7/src/plugin/parse-html/src/java/org/apache/nutch/parse/html

类:HtmlParser.java

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

1 setConf函数

代码如下:

public void setConf(Configuration conf) {
    this.conf = conf;
    this.htmlParseFilters = new HtmlParseFilters(getConf());
    this.parserImpl = getConf().get("parser.html.impl", "neko");
    this.defaultCharEncoding = getConf().get(
        "parser.character.encoding.default", "windows-1252");
    this.utils = new DOMContentUtils(conf);
    this.cachingPolicy = getConf().get("parser.caching.forbidden.policy",
        Nutch.CACHING_FORBIDDEN_CONTENT);
  }

这个函数应该是首先被调用的,我们来看看这个函数做了什么!

添加打印代码,打印的结果如下所示:

2014-06-26 10:22:25,809 INFO  parse.html - save conf now!!!
2014-06-26 10:22:25,811 INFO  parse.html - parserImpl---neko
2014-06-26 10:22:25,811 INFO  parse.html - defaultCharEncoding---windows-1252
2014-06-26 10:22:25,814 INFO  parse.html - get DOMContentUtils Object ok!
2014-06-26 10:22:25,814 INFO  parse.html - cachingPolicy---content

结论:

代码的作用是:

1保存配置conf.

2从配置里获取所有的htmlParseFilters插件供后续使用

3 真正的解析类是neko.

4 默认的编码方式是-windows-1252.

5构造了一个DOMContentUtils对象。

6cachingPolicy的值为content.

从打印信息中知道目前系统内部还没有HtmlParseFilter插件。

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

 接下来进入到最关键的函数

public ParseResult getParse(Content content) {

代码如下:

public ParseResult getParse(Content content) {
    HTMLMetaTags metaTags = new HTMLMetaTags();

    URL base;
    try {
      base = new URL(content.getBaseUrl());
    } catch (MalformedURLException e) {
      return new ParseStatus(e).getEmptyParseResult(content.getUrl(), getConf());
    }

    String text = "";
    String title = "";
    Outlink[] outlinks = new Outlink[0];
    Metadata metadata = new Metadata();

    // parse the content
    DocumentFragment root;
    try {
      byte[] contentInOctets = content.getContent();
      InputSource input = new InputSource(new ByteArrayInputStream(contentInOctets));

      EncodingDetector detector = new EncodingDetector(conf);
      detector.autoDetectClues(content, true);
      detector.addClue(sniffCharacterEncoding(contentInOctets), "sniffed");
      String encoding = detector.guessEncoding(content, defaultCharEncoding);

      metadata.set(Metadata.ORIGINAL_CHAR_ENCODING, encoding);
      metadata.set(Metadata.CHAR_ENCODING_FOR_CONVERSION, encoding);

      input.setEncoding(encoding);
      if (LOG.isTraceEnabled()) { LOG.trace("Parsing..."); }
      root = parse(input);
    } catch (IOException e) {
      return new ParseStatus(e).getEmptyParseResult(content.getUrl(), getConf());
    } catch (DOMException e) {
      return new ParseStatus(e).getEmptyParseResult(content.getUrl(), getConf());
    } catch (SAXException e) {
      return new ParseStatus(e).getEmptyParseResult(content.getUrl(), getConf());
    } catch (Exception e) {
      LOG.error("Error: ", e);
      return new ParseStatus(e).getEmptyParseResult(content.getUrl(), getConf());
    }
      
    // get meta directives
    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();
    }
      
    if (!metaTags.getNoFollow()) {              // okay to follow links
      ArrayList<Outlink> l = new ArrayList<Outlink>();   // extract outlinks
      URL baseTag = utils.getBase(root);
      if (LOG.isTraceEnabled()) { LOG.trace("Getting links..."); }
      utils.getOutlinks(baseTag!=null?baseTag:base, l, root);
      outlinks = l.toArray(new Outlink[l.size()]);
      if (LOG.isTraceEnabled()) {
        LOG.trace("found "+outlinks.length+" outlinks in "+content.getUrl());
      }
    }
    
    ParseStatus status = new ParseStatus(ParseStatus.SUCCESS);
    if (metaTags.getRefresh()) {
      status.setMinorCode(ParseStatus.SUCCESS_REDIRECT);
      status.setArgs(new String[] {metaTags.getRefreshHref().toString(),
        Integer.toString(metaTags.getRefreshTime())});      
    }
    ParseData parseData = new ParseData(status, title, outlinks,
                                        content.getMetadata(), metadata);
    ParseResult parseResult = ParseResult.createParseResult(content.getUrl(), 
                                                 new ParseImpl(text, parseData));

    // run filters on parse
    ParseResult filteredParse = this.htmlParseFilters.filter(content, parseResult, 
                                                             metaTags, root);
    if (metaTags.getNoCache()) {             // not okay to cache
      for (Map.Entry<org.apache.hadoop.io.Text, Parse> entry : filteredParse) 
        entry.getValue().getData().getParseMeta().set(Nutch.CACHING_FORBIDDEN_KEY, 
                                                      cachingPolicy);
    }
    return filteredParse;
  }

下面来看看这个函数到底是怎么解析的!

根据打印信息,知道:

content.getContent---内容是原汁原味的html内容,不经过任何加工。

通过日志发现,Nutch的content.getContent().length的长度总是为65536.这就很奇怪了,看来Nutch在配置中做了限制。

查看conf/nutch-default.xml发现

<property>
  <name>http.content.limit</name>
  <value>65536</value>
  <description>The length limit for downloaded content using the http://
  protocol, in bytes. If this value is nonnegative (>=0), content longer
  than it will be truncated; otherwise, no truncation at all. Do not
  confuse this setting with the file.content.limit setting.
  </description>
</property>

这就好办了,将这部分复制到conf/nutch-site.xml,然后修改为

<property>
  <name>http.content.limit</name>
  <value>1048576</value>
  <description>The length limit for downloaded content using the http://
  protocol, in bytes. If this value is nonnegative (>=0), content longer
  than it will be truncated; otherwise, no truncation at all. Do not
  confuse this setting with the file.content.limit setting.
  </description>
</property>

 然后看

root = parse(input);

知道默认解析方式是:

Neko

~~~~~~~继续往下分析下面的代码。

 

HTMLMetaProcessor.getMetaTags(metaTags, root, base);


获取meta信息

其中一个可能的形式如下:

metaTags information---base=null, noCache=false, noFollow=false, noIndex=false, refresh=false, refreshHref=null
 * general tags:
   - title      =       优酷音乐-无音乐,不生活!
   - keywords   =       优酷音乐 优酷音乐频道 优酷 优酷视频 优酷电影 优酷电视剧,优酷视频
   - msapplication-task =       name=上传视频;action-uri=http://www.youku.com/v/upload/;icon-uri=http://www.youku.com/favicon.ico
   - application-name   =       优酷网
   - description        =       优酷音乐-无音乐,不生活! - 快速找到你所想要的,超棒视听空间! - 优酷视频
   - msapplication-starturl     =       http://www.youku.com/
 * http-equiv tags:
   - content-type       =       text/html; charset=UTF-8
   - content-language   =       zh-CN

这样的话,后续想要什么东西,可以考虑从这里获取。

 然后下面解析是提取若干内容

text----对应着网页中去掉标签后的文本。

title----网页的标题

outlinks---当前网页中的外链数组。

这个都比较简单。后续如果想过滤外链的话就可以考虑从这里过滤。

~~~~~~~~~~~~

接下来是构造parseResult

相关的一些字段的值如下:

content.getMetadata()------ETag="1951694047" Date=Thu, 26 Jun 2014 05:33:38 GMT Vary=Accept-Encoding Content-Length=20774 nutch.crawl.score=1.0 Content-Encoding=deflate Last-Modified=Thu, 26 Jun 2014 05:31:29 GMT _fst_=33 nutch.segment.name=20140626133401 Connection=close Content-Type=text/html Server=b28www1

content.getUrl()------http://music.youku.com/

 剩下的就是过滤器的作用了。

分析完毕。

 到这里只是解析完毕。

有兴趣的读者请再回头查看org.apache.nutch.crawl.Crawl.java的 public int run(String[] args) throws Exception {

函数。

这里简单解释一下流程。

1 获取参数,比如线程个数,topN的个数。

2 生成各个对象,比如

Injector injector = new Injector(getConf());
    Generator generator = new Generator(getConf());
    Fetcher fetcher = new Fetcher(getConf());
    ParseSegment parseSegment = new ParseSegment(getConf());
    CrawlDb crawlDbTool = new CrawlDb(getConf());
    LinkDb linkDbTool = new LinkDb(getConf());

3 只执行一次注入操作

// initialize crawlDb
    injector.inject(crawlDb, rootUrlDir);

4 根据depth决定循环次数

for (i = 0; i < depth; i++) {             // generate new segment
      Path[] segs = generator.generate(crawlDb, segments, -1, topN, System
          .currentTimeMillis());
      if (segs == null) {
        LOG.info("Stopping at depth=" + i + " - no more URLs to fetch.");
        break;
      }
      fetcher.fetch(segs[0], threads);  // fetch it
      if (!Fetcher.isParsing(job)) {
        parseSegment.parse(segs[0]);    // parse it, if needed
      }
      crawlDbTool.update(crawlDb, segs, true, true); // update crawldb
    }

5 执行

linkDbTool.invert(linkDb, segments, true, true, false); // invert links

6 如果设置了solr的url

执行针对solr的索引操作

if (solrUrl != null) {
        // index, dedup & merge
        FileStatus[] fstats = fs.listStatus(segments, HadoopFSUtil.getPassDirectoriesFilter(fs));
        
        IndexingJob indexer = new IndexingJob(getConf());
        indexer.index(crawlDb, linkDb, 
                Arrays.asList(HadoopFSUtil.getPaths(fstats)));

        SolrDeleteDuplicates dedup = new SolrDeleteDuplicates();
        dedup.setConf(getConf());
        dedup.dedup(solrUrl);
      }

 

到此,所有的爬取网页-解析-非索引的操作就完成了。后续讲解索引操作。

目标是ElasticSearch.

 

 

© 著作权归作者所有

强子大叔的码田

强子大叔的码田

粉丝 922
博文 1496
码字总数 1247433
作品 9
南京
架构师
私信 提问
nutch修改parse-html爬博客的标题

修改parse-html的HtmlParser类的getParse方法 下面就是对博客标题抽取 修改这2部分,然后运行nutch就可以抓取到博客的标题了。 参考文章:Nutch二次开发之定制爬取网站信息...

cjun1990
2015/04/29
15
0
扩展 HTMLParser 对自定义标签的处理能力

HTMLParser 是一个用来解析 HTML 文档的开放源码项目,它具有小巧、快速、使用简单的特点以及拥有强大的功能。 如果是 HTML 文档,那么用 HTMLParser 已经差不多可以满足你至少 90% 的需求。...

红薯
2008/10/05
860
0
九Python之HTML的解析(网页抓取一)

对html的解析是网页抓取的基础,分析抓取的结果找到自己想要的内容或标签以达到抓取的目的。 HTMLParser是python用来解析html的模块。它可以分析出html里面的标签、数据等等,是一种处理htm...

dkz
2013/03/05
1.7K
2
Python2.7 HTMLParser模块学习

HTMLParser模块主要是用来解析HTML文件。 HTMLParser模块有一个HTMLParser类,用户在解析HTML文件时需要重定义其中handle_*方法,该类有如下方法: feed(data)分析一些文本数据 close()如果碰...

China_OS
2012/12/03
1K
0
解析--import--htmllib--xml

--import ConfigParser 模块------解析配置文件--------------------------------------------------------------------- test.conf内容: [first] w = 2 v: 3 c =11-3 [second] sw=4 test: ......

liapple6
2018/10/28
0
0

没有更多内容

加载失败,请刷新页面

加载更多

处理CSV文件中的逗号

我正在寻找有关如何处理正在创建的csv文件的建议,然后由我们的客户上传,并且该值可能带有逗号(例如公司名称)。 我们正在研究的一些想法是:带引号的标识符(值“,”值“,”等)或使用|...

javail
35分钟前
21
0
如何克隆一个Date对象?

将Date变量分配给另一个变量会将引用复制到同一实例。 这意味着更改一个将更改另一个。 如何实际克隆或复制Date实例? #1楼 简化版: Date.prototype.clone = function () { return new ...

技术盛宴
今天
58
0
计算一个数的数位之和

计算一个数的数位之和 例如:128 :1+2+8 = 11 public int numSum(int num) { int sum = 0; do { sum += num % 10; } while ((num = num / 10) > 0); return sum;......

SongAlone
今天
67
0
为什么图片反复压缩后普遍会变绿,而不是其他颜色?

作者:Lion Yang 链接:https://www.zhihu.com/question/29355920/answer/119088684 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 业余版概要:安卓的...

shzwork
今天
47
0
每天AC系列(二):最接近的三数之和

1 题目 leetcode第16题,给定一个数组与一个目标数,找出数组中其中的三个数,这三个数的和要与目标数最接近。 2 暴力 按惯例先来一次O(n3)的暴力: int temp = nums[0]+nums[1]+nums[2];fo...

Blueeeeeee
今天
46
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部