- 浏览: 275535 次
- 性别:
- 来自: 北京
文章分类
最新评论
-
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 684一、提高搜索引擎对用户检索提问的理解为了提高搜索引擎对用户检索 ... -
nutch官网下载,compass官网下载,lucene官网下载
2010-12-06 21:52 900nutch官网下载,compass官网下载,lucene官 ... -
Java开源搜索引擎[收藏]
2010-12-06 21:49 861Egothor Egothor是一个用Ja ... -
搜索引擎Nutch源代码研究之一 网页抓取(3)
2010-12-06 21:47 973今天我们看看Nutch网页抓取,所用的几种数据结构: 主要涉及 ... -
搜索引擎Nutch源代码研究之一 网页抓取(2)
2010-12-06 21:46 893今天我们来看看Nutch的源代码中的protocol-ht ... -
搜索引擎Nutch源代码研究之一 网页抓取(1)
2010-12-06 21:45 1496搜索引擎Nutch源代码研究之一 网页抓取: Nutch的爬虫 ... -
一个简单的JAVA网页爬虫
2010-12-05 14:26 925public class Access impleme ... -
Google的PageRank原理
2010-12-02 12:50 779PageRank我想稍微接触 ... -
中文分词技术
2010-12-02 12:49 799中文分词技术属于自然语言处理技术范畴,对于一句话 ... -
搜索引擎概述
2010-12-02 12:49 1056搜索引擎的概念 搜索引擎是一应用于web上的 ... -
国内搜索引擎技术现状
2010-12-02 12:48 848当你登录某一个网站 ... -
搜索引擎的技术发展趋势
2010-12-02 12:48 769搜索引擎经过几年的 ... -
什么是第三代搜索引擎
2010-12-02 12:48 851(www.marketingman.net ... -
聚焦爬虫
2010-12-02 12:45 1056聚焦爬虫,又称主题爬虫(或专业爬虫),是“面向特定主 ... -
Google搜索引擎的工作流程
2010-12-02 12:44 1588①Google使用高速的 ... -
福布斯评出最具发展潜力10大搜索引擎
2010-12-02 12:43 759美国知名财经杂志《福布斯》网络版周二评出了最具发展潜 ... -
网页爬虫程序pageSpider
2010-12-02 12:34 7392009-05-05 19:44 该程 ... -
lucene教程
2010-10-24 18:34 652Lucene是apache组织的一个用java实现全文搜索引擎 ...
相关推荐
nutch 源代码的详细分析,对于自己实现自己的搜索引擎很有帮助,尤其是将nutch项目嵌入到 自己的项目 当中很有帮助!
Nutch 是一个开源Java 实现的搜索引擎。它提供了我们运行自己的搜索引擎所需的全部工具。包括全文搜索和Web爬虫。 本资源官网上下的源代码。 nutch-2.1 适用于windows系统
nutch源代码,共享给大家。nutch是一个开源的搜索引擎
相对于那些商用的搜索引擎, Nutch作为开放源代码 搜索引擎将会更加透明, 从而更值得大家信赖. 现在所有主要的搜索引擎都采用私有的排序算法, 而不会解释为什么一个网页会排在一个特定的位置. 除此之外, 有的搜索...
lucene nutch 搜索引擎 ...包含lucene使用的所有源代码,从建立索引,搜索,删除,排序,都有,非常齐全 还有PDF 解析,WORD解析 ,EXCEL,ppt,xml解析等,,都有源码实现 还有nutch源码,spider源码。。。 非常齐全
《Lucene+nutch搜索引擎开发》书附带的源代码
Nutch Htmlunit Plugin 重要说明: 当前项目基于Nutch 1.X系列已停止更新维护,转向Nutch 2.x系列版本的新项目:http://www.oschina.net/p/nutch-ajax 项目简介 基于Apache Nutch 1.8和Htmlunit...
Nutch1.7二次开发培训讲义 之 腾讯微博抓取分析
lucene学习的基本代码资料,里面有nutch扩展爬虫代码,可以抓取网页信息,新闻信息等,代码很详细,初学者的好帮手。
nutch分布式搜索索引热替换程序,当使用nutch分布式搜索的时候,通过修改nutch来实现重建索引和分布式搜索分隔开,相互不影响
nutch 0.9分页代码(粘贴可用)
nutch解决搜索结果高亮和网页快照链接无效及网页变形
Nutch的创始人是Doug Cutting,他同时也... Nutch诞生于2002年8月,是Apache旗下的一个用Java实现的开源搜索引擎项目,自Nutch1.2版本之后,Nutch已经从搜索引擎演化为网络爬虫,接着Nutch进一步演化为两大分支版本:1.
Nutch是一个优秀的开放源代码的Web搜索引擎。虽然Nutch的页面排序方法比较合理,但是很多情况下仍然不能 满足需要。分析开源搜索引擎Nutch代码,研究了Nutch的页面排序方法。在Nutch原有的结构基础上提出了3种修改...
Lucene+nutch搜索引擎开发(源代码),内含本书的PDF电子下载地址。
包括nutch的参考书,和NUTCH源代码分析
(1) 透明度:nutch 是开放源代码的,因此任何人都可以查看他的排序算法 是如何工作的。商业的搜索引擎排序算法都是保密的,我们无法知道为 什么搜索出来的排序结果是如何算出来的。更进一步,一些搜索引擎允 许竞价...
网络蜘蛛程序源代码nutch0·8,压缩文件
如何通过java程序获得Nutch中网页的详细信息
介绍 Nutch 的背景知识,包括 Nutch 架构,爬虫和...然后示例说明 Nutch 爬虫如何抓取目标网站内容,产生片断和索引,并将结果存放在集群的2个节点上。最后使用 Nutch 检索器提供的 API 开发应用,为用户提供搜索接口。