- 浏览: 288239 次
- 性别:
- 来自: 北京
文章分类
最新评论
-
86614009:
如何在service层,如何获取绑定到当前线程的entitna ...
使用spring的OpenEntityManagerInView -
yajunyajun2011:
好帖子 怎么没人顶呢
Java 正则表达式最大,最小匹配问题 -
xtuali:
能说明一下,你的nutch是哪个版本的吗?谢谢!
搜索引擎Nutch源代码研究之一 网页抓取(1) -
dongmusic:
需要学习这么多的东西,吐血中...
如何提高Java开发能力 -
jiminsc:
cool
LDAP 验证、添加、修改、删除(转)
今天来看看Nutch如何Parse网页的:
Nutch使用了两种Html parser工具(NekoHTML和TagSoup)来实现html的提取,这两种工具是可通过配置来选择的。
当然你要自己实现Parser你还可以选择HTMLParser[基于visitor访问者模式同时也提供了Event driver的接口]来
提取网页。如果你用惯了XML一套处理方法,使用NekoHTML和TagSoup应该会比较顺手的。
我们来看看类public class HtmlParser implements Parser的实现:
首先为了更好的理解下面的代码先看看成员变量:
Nutch使用了两种Html parser工具(NekoHTML和TagSoup)来实现html的提取,这两种工具是可通过配置来选择的。
当然你要自己实现Parser你还可以选择HTMLParser[基于visitor访问者模式同时也提供了Event driver的接口]来
提取网页。如果你用惯了XML一套处理方法,使用NekoHTML和TagSoup应该会比较顺手的。
我们来看看类public class HtmlParser implements Parser的实现:
首先为了更好的理解下面的代码先看看成员变量:
- 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;
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部分提取,通过正则表达式很容易提取出编码
- 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;
- }
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
- URL base;
- try {
- base = new URL(content.getBaseUrl());
- } catch (MalformedURLException e) {
- return new ParseStatus(e).getEmptyParse(getConf());
- }
URL base; try { base = new URL(content.getBaseUrl()); } catch (MalformedURLException e) { return new ParseStatus(e).getEmptyParse(getConf()); }
提取encoding:
- //直接从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中的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
- 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());
- }
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指令
- 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();
- }
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:
- 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());
- }
- }
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对象:
- 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);
- }
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对象
- private DocumentFragment parse(InputSource input) throws Exception {
- if (parserImpl.equalsIgnoreCase("tagsoup"))
- return parseTagSoup(input);
- else return parseNeko(input);
- }
private DocumentFragment parse(InputSource input) throws Exception { if (parserImpl.equalsIgnoreCase("tagsoup")) return parseTagSoup(input); else return parseNeko(input); }
网页抓取部分到此基本结束,必要的部分相应再作补充。等研究好google的map-reduce再继续其他部分。
发表评论
-
【转】搜索引擎最新技术发展分析
2011-11-21 09:19 730一、提高搜索引擎对用户检索提问的理解为了提高搜索引擎对用户检索 ... -
nutch官网下载,compass官网下载,lucene官网下载
2010-12-06 21:52 1000nutch官网下载,compass官网下载,lucene官 ... -
Java开源搜索引擎[收藏]
2010-12-06 21:49 918Egothor Egothor是一个用Ja ... -
搜索引擎Nutch源代码研究之一 网页抓取(3)
2010-12-06 21:47 1024今天我们看看Nutch网页抓取,所用的几种数据结构: 主要涉及 ... -
搜索引擎Nutch源代码研究之一 网页抓取(2)
2010-12-06 21:46 966今天我们来看看Nutch的源代码中的protocol-ht ... -
搜索引擎Nutch源代码研究之一 网页抓取(1)
2010-12-06 21:45 1587搜索引擎Nutch源代码研究之一 网页抓取: Nutch的爬虫 ... -
一个简单的JAVA网页爬虫
2010-12-05 14:26 974public class Access impleme ... -
Google的PageRank原理
2010-12-02 12:50 819PageRank我想稍微接触 ... -
中文分词技术
2010-12-02 12:49 849中文分词技术属于自然语言处理技术范畴,对于一句话 ... -
搜索引擎概述
2010-12-02 12:49 1093搜索引擎的概念 搜索引擎是一应用于web上的 ... -
国内搜索引擎技术现状
2010-12-02 12:48 896当你登录某一个网站 ... -
搜索引擎的技术发展趋势
2010-12-02 12:48 821搜索引擎经过几年的 ... -
什么是第三代搜索引擎
2010-12-02 12:48 899(www.marketingman.net ... -
聚焦爬虫
2010-12-02 12:45 1112聚焦爬虫,又称主题爬虫(或专业爬虫),是“面向特定主 ... -
Google搜索引擎的工作流程
2010-12-02 12:44 1632①Google使用高速的 ... -
福布斯评出最具发展潜力10大搜索引擎
2010-12-02 12:43 800美国知名财经杂志《福布斯》网络版周二评出了最具发展潜 ... -
网页爬虫程序pageSpider
2010-12-02 12:34 7732009-05-05 19:44 该程 ... -
lucene教程
2010-10-24 18:34 730Lucene是apache组织的一个用java实现全文搜索引擎 ...
相关推荐
4. **索引更新与合并**:Nutch支持增量索引,这意味着它可以仅对新抓取或更新的网页进行索引,而不是重新索引整个集合。索引合并则用于定期整合多个小索引,以减少索引碎片并优化查询性能。 5. **搜索接口**:Nutch...
Nutch 的源代码解析对于深入理解搜索引擎的工作原理以及自定义搜索引擎的实现非常有帮助。下面我们将详细探讨 Nutch 的注入(Injector)过程,这是整个爬取流程的第一步。 Injector 类在 Nutch 中的作用是将输入的 ...
Nutch 1.5 是一个基于Java开发的开源搜索引擎项目,它主要负责网络抓取、索引和搜索等功能。这个源代码包包含了实现这些功能的所有模块和组件,为开发者提供了深入理解搜索引擎工作原理以及定制化搜索引擎的机会。接...
- **配置 Nutch 创建索引**:下载 Nutch 的源代码并解压,然后通过 Maven 进行编译。配置 Nutch 的 `conf/nutch-site.xml` 文件以设置存储路径、抓取策略等参数。 - **安装 Tomcat**:Tomcat 用于运行 Nutch 的 UI...
2. **Nutch源代码**:包括Nutch的爬虫模块、索引模块和搜索模块,可以帮助开发者学习如何配置和运行一个完整的网络爬虫,以及如何与Lucene集成进行全文检索。 3. **示例项目**:可能包含了一些示例应用,展示如何...
在使用Nutch之前,你需要配置Nutch的运行环境,包括安装Java、设置Hadoop(如果需要分布式爬取)、下载和编译Nutch源代码。还需要配置Nutch的`conf/nutch-site.xml`文件,指定抓取策略、存储路径、爬虫范围等参数。 ...
Apache Nutch 是一个开源的网络爬虫框架,用于抓取互联网上的网页并建立索引,以便于搜索引擎进行高效的检索。Nutch 2.3 版本是该项目的一个稳定版本,包含了丰富的功能和优化,是学习和研究网络爬虫技术的理想选择...
Nutch最初设计的目标是创建一个与商业搜索引擎相媲美的开放源代码搜索解决方案,用于企业内部或特定领域的信息检索。 2. **Nutch的功能** - **网页抓取**:Nutch使用爬虫技术,通过种子URL开始,遍历互联网上的...
其次,Nutch是基于Lucene构建的一个完整的网络爬虫项目,它不仅包括了网页抓取、解析、存储等功能,还集成了Lucene的索引和搜索能力。Nutch的工作流程如下: 1. 网页抓取:Nutch使用爬虫程序遍历互联网上的网页,...
Nutch 提供了内置的高亮功能,可以通过修改或扩展其源代码来实现。例如,`HeightLighter.java` 文件可能就是用于处理高亮逻辑的类。高亮过程通常包括以下步骤: - 在查询阶段,保存用户输入的关键词。 - 在搜索...
它基于Hadoop,能够从互联网上抓取和索引网页,构建搜索引擎。Nutch的强大之处在于其高度可定制性,这主要归功于它的插件系统。Nutch插件允许开发者根据特定需求定制和扩展Nutch的功能,如自定义爬虫策略、数据解析...
Apache Nutch 是一个开源的网络爬虫项目,用于抓取互联网上的网页并建立索引,以便于搜索引擎进行高效的信息检索。Nutch 源码的分析和理解对于想要深入研究搜索引擎工作原理、网页抓取技术和大数据处理的开发者来说...
Nutch的长远目标是打造一个成本低廉、易于配置且性能卓越的Web搜索引擎,能够每月抓取数十亿网页,维护庞大的索引,并提供快速且准确的搜索结果,同时保持较低的运营成本。 **1.4 Nutch VS Lucene** Nutch与Lucene...
这个版本的源码包含了实现Web抓取、索引和搜索功能的全部Java代码,为开发者提供了深入理解搜索引擎工作原理的机会。 Nutch的主要组件包括以下几个部分: 1. **Web抓取(Crawling)**:Nutch的抓取模块使用了...
Apache Nutch 是一个开源的网络爬虫框架,用于抓取互联网上的网页并建立索引,以便进行全文搜索。Nutch 2.2.1 是一个稳定版本,它依赖于其他几个组件来完成其功能,包括 Apache Ant、Apache Tomcat、Java 开发工具包...
1. **Nutch介绍**:Nutch是一个基于Java的开源Web爬虫,它能够抓取互联网上的网页,并对抓取的数据进行索引和搜索。Nutch的设计目标是提供可扩展性和高效率,适合大规模的Web数据处理。 2. **增量索引**:在Nutch中...
获取与解压Nutch源代码** Nutch的源代码可以通过两种方式获取: - **下载发行版**:从官方网站`http://lucene.apache.org/nutch/release/`下载最新版本的Nutch,然后解压缩到你选择的目录。 - **使用Subversion**...
它提供了一个可扩展的、高度模块化的框架,用于抓取、解析网页,并建立索引,是大数据和信息检索领域的重要工具。下面,我们将详细讲解如何搭建Nutch的开发环境。 **步骤一:系统准备** 在开始搭建Nutch开发环境...
Nutch是一款开源的网络爬虫软件,主要用于抓取和索引互联网上的网页,是大数据领域中的重要工具之一。本文将深入解析Nutch的基础知识,帮助初学者快速入门。 1. **Nutch简介** Nutch是由Apache软件基金会开发的一...
Nutch是一款开源的网络爬虫项目,主要用于抓取和索引互联网上的网页内容。它由Apache软件基金会开发,是Hadoop大数据生态系统的一部分,利用Java语言编写。本资料包围绕Nutch爬虫,提供了相关的参考书籍和源代码分析...