基于htmlparser实现网页内容解析

网页解析,即程序自动分析网页内容、获取信息,从而进一步处理信息。

网页解析是实现网络爬虫中不可缺少而且十分重要的一环,由于本人经验也很有限,我仅就我们团队开发基于关键词匹配和模板匹配的主题爬虫的经验谈谈如何实现网页解析。

首先,必须说在最前的是我们使用的工具——htmlparser

简要地说,htmlparser包提供方便、简洁的处理html文件的方法,它将html页面中的标签按树形结构解析成一个一个结点,一种类型的结点对应一个类,通过调用其方法可以轻松地访问标签中的内容。

我所使用的是htmlparser2.0,也就是最新版本。强烈推荐。

好,进入正题。

对于主题爬虫,它的功能就是将与主题相关的网页下载到本地,将网页的相关信息存入数据库。

网页解析模块要实现两大功能:1.从页面中提取出子链接,加入到爬取url队列中;2.解析网页内容,与主题进行相关度计算。

由于网页内容解析需要频繁地访问网页文件,如果通过url访问网络获取文件的时间开销比较大,所以我们的做法是将爬取队列中的网页统统下载到本地,对本地的网页文件进行页面内容解析,最后删除不匹配的网页。而子链接的提取比较简单,通过网络获取页面文件即可。对于给定url通过网络访问网页,和给定文件路径访问本地网页文件,htmlparser都是支持的

1.子链接的提取:

做页面子链接提取的基本思路是:

1.用被提取的网页的url实例化一个Parser

2.实例化Filter,设置页面过滤条件——只获取<a>标签与<frame>标签的内容

3.用Parser提取页面中所有通过Filter的结点,得到NodeList

4.遍历NodeList,调用Node的相应方法得到其中的链接,加入子链接的集合

5.返回子链接集合

OK,上代码:

 1 package Crawler;
2
3
4 import java.util.HashSet;
5 import java.util.Set;
6
7 import org.htmlparser.Node;
8 import org.htmlparser.NodeFilter;
9 import org.htmlparser.Parser;
10 import org.htmlparser.filters.NodeClassFilter;
11 import org.htmlparser.filters.OrFilter;
12 import org.htmlparser.tags.LinkTag;
13 import org.htmlparser.util.NodeList;
14 import org.htmlparser.util.ParserException;
15
16 public class HtmlLinkParser {
17 //获取子链接,url为网页url,filter是链接过滤器,返回该页面子链接的HashSet
18 public static Set<String> extracLinks(String url, LinkFilter filter) {
19
20 Set<String> links = new HashSet<String>();
21 try {
22 Parser parser = new Parser(url);
23 parser.setEncoding("utf-8");
24 // 过滤 <frame >标签的 filter,用来提取 frame 标签里的 src 属性所表示的链接
25 NodeFilter frameFilter = new NodeFilter() {
26 public boolean accept(Node node) {
27 if (node.getText().startsWith("frame src=")) {
28 return true;
29 } else {
30 return false;
31 }
32 }
33 };
34 // OrFilter 接受<a>标签或<frame>标签,注意NodeClassFilter()可用来过滤一类标签,linkTag对应<标签>
35 OrFilter linkFilter = new OrFilter(new NodeClassFilter(
36 LinkTag.class), frameFilter);
37 // 得到所有经过过滤的标签,结果为NodeList
38 NodeList list = parser.extractAllNodesThatMatch(linkFilter);
39 for (int i = 0; i < list.size(); i++) {
40 Node tag = list.elementAt(i);
41 if (tag instanceof LinkTag)// <a> 标签
42 {
43 LinkTag link = (LinkTag) tag;
44 String linkUrl = link.getLink();// 调用getLink()方法得到<a>标签中的链接
45 if (filter.accept(linkUrl))//将符合filter过滤条件的链接加入链接表
46 links.add(linkUrl);
47 } else{// <frame> 标签
48 // 提取 frame 里 src 属性的链接如 <frame src="test.html"/>
49 String frame = tag.getText();
50 int start = frame.indexOf("src=");
51 frame = frame.substring(start);
52 int end = frame.indexOf(" ");
53 if (end == -1)
54 end = frame.indexOf(">");
55 String frameUrl = frame.substring(5, end - 1);
56 if (filter.accept(frameUrl))
57 links.add(frameUrl);
58 }
59 }
60 } catch (ParserException e) {//捕捉parser的异常
61 e.printStackTrace();
62 }
63 return links;
64 }
65 }

此时可能有读者在想:呵~呵~博主忽略了相对url链接的问题了(-.-)

其实我想到了,一开始我写了一个private方法专门把任何url转换成绝对url链接。后来调试的时候我发现我的方法根本没用,因为htmlparser很人性化地自动完成了这个转换!

另外,Parser是需要设置编码的,在这段程序中我直接设置为utf-8。实际上网页的编码方式是多种多样的,在<meta>标签中有关于编码方式的信息,如果编码不正确,页面的文本内容可能是乱码。不过,在子链接提取的部分,我们仅对标签内部的内容进行处理,这些内容是根据html语法编写的,不涉及编码的问题。

2.解析网页内容:

基本思路:

1.读取html文件,获得页面编码,获得String格式的文件内容

2.用页面编码实例化html文件的Parser

3.对需要提取的结点设置相应的Filter

4.根据给定的Filter,用Parser解析html文件

5.提取结点中的文本内容,进行处理(本例中是关键字匹配,计算主题相关度)

  1 import java.io.BufferedReader;
2 import java.io.FileInputStream;
3 import java.io.FileNotFoundException;
4 import java.io.FileReader;
5 import java.io.IOException;
6 import java.io.InputStreamReader;
7 import java.util.regex.Matcher;
8 import java.util.regex.Pattern;
9
10 import org.htmlparser.Parser;
11 import org.htmlparser.filters.NodeClassFilter;
12 import org.htmlparser.tags.HeadingTag;
13 import org.htmlparser.tags.LinkTag;
14 import org.htmlparser.tags.MetaTag;
15 import org.htmlparser.tags.ParagraphTag;
16 import org.htmlparser.tags.TitleTag;
17 import org.htmlparser.util.NodeList;
18 import org.htmlparser.util.ParserException;
19
20 import java.util.Set;
21 import multi.patt.match.ac.*;
22
23 public class HtmlFileParser {
24 String filepath=new String();//html文件路径
25 private static String[] keyWords;//关键词列表
26 /*static{
27 keyWords=read("filePath");//从指定文件中读取关键词列表
28 }*/
29 public HtmlFileParser(String filepath){
30 this.filepath=filepath;
31 }
32 public String getTitle(){//得到页面标题
33 FileAndEnc fae=readHtmlFile();
34 int i=0;
35 try{
36 //实例化一个本地html文件的Parser
37 Parser titleParser = Parser.createParser(fae.getFile(),fae.getEnc());
38 NodeClassFilter titleFilter =new NodeClassFilter(TitleTag.class);
39 NodeList titleList = titleParser.extractAllNodesThatMatch(titleFilter);
40 //实际上一个网页应该只有一个<title>标签,但extractAllNodesThatMatch方法返回的只能是一个NodeList
41 for (i = 0; i < titleList.size(); i++) {
42 TitleTag title_tag = (TitleTag) titleList.elementAt(i);
43 return title_tag.getTitle();
44 }
45 }catch(ParserException e) {
46 return null;
47 }
48 return null;
49 }
50 public String getEncoding(){//获得页面编码
51 FileAndEnc fae=readHtmlFile();
52 return fae.getEnc();
53 }
54 public float getRelatGrade(){//计算网页的主题相关度
55 FileAndEnc fae=readHtmlFile();
56 String file=fae.getFile();
57 String enC=fae.getEnc();
58 String curString;
59 int curWordWei = 1;//当前关键词权重
60 float curTagWei = 0;//当前标签权重
61 float totalGra = 0;//总相关度分
62 int i;
63 AcApply obj = new AcApply();//实例化ac自动机
64 Pattern p = null;
65 Matcher m = null;
66 try{//根据不同标签依次进行相关度计算
67 //title tag <title>
68 curTagWei=5;
69 Parser titleParser = Parser.createParser(file,enC);
70 NodeClassFilter titleFilter =new NodeClassFilter(TitleTag.class);
71 NodeList titleList = titleParser.extractAllNodesThatMatch(titleFilter);
72 for (i = 0; i < titleList.size(); i++) {
73 TitleTag titleTag=(TitleTag)titleList.elementAt(i);
74 curString=titleTag.getTitle();
75 Set result = obj.findWordsInArray(keyWords, curString);//ac自动机的方法返回匹配的词的表
76 totalGra=totalGra+result.size()*curTagWei;//计算相关度
77 }
78 //meta tag of description and keyword <meta>
79 curTagWei=4;
80 Parser metaParser = Parser.createParser(file,enC);
81 NodeClassFilter metaFilter =new NodeClassFilter(MetaTag.class);
82 NodeList metaList = metaParser.extractAllNodesThatMatch(metaFilter);
83 p = Pattern.compile("\\b(description|keywords)\\b",Pattern.CASE_INSENSITIVE);
84 for (i = 0; i < metaList.size(); i++) {
85 MetaTag metaTag=(MetaTag)metaList.elementAt(i);
86 curString=metaTag.getMetaTagName();
87 if(curString==null){
88 continue;
89 }
90 m = p.matcher(curString); //正则匹配name是description或keyword的<meta>标签
91 if(m.find()){
92 curString=metaTag.getMetaContent();//提取其content
93 Set result = obj.findWordsInArray(keyWords, curString);
94 totalGra=totalGra+result.size()*curTagWei;
95 }
96 else{
97 curString=metaTag.getMetaContent();
98 Set result = obj.findWordsInArray(keyWords, curString);
99 totalGra=totalGra+result.size()*2;
100 }
101 }
102 //heading tag <h*>
103 curTagWei=3;
104 Parser headingParser = Parser.createParser(file,enC);
105 NodeClassFilter headingFilter =new NodeClassFilter(HeadingTag.class);
106 NodeList headingList = headingParser.extractAllNodesThatMatch(headingFilter);
107 for (i = 0; i < headingList.size(); i++) {
108 HeadingTag headingTag=(HeadingTag)headingList.elementAt(i);
109 curString=headingTag.toPlainTextString();//得到<h*>标签中的纯文本
110 if(curString==null){
111 continue;
112 }
113 Set result = obj.findWordsInArray(keyWords, curString);
114 totalGra=totalGra+result.size()*curTagWei;
115 }
116 //paragraph tag <p>
117 curTagWei=(float)2.5;
118 Parser paraParser = Parser.createParser(file,enC);
119 NodeClassFilter paraFilter =new NodeClassFilter(ParagraphTag.class);
120 NodeList paraList = paraParser.extractAllNodesThatMatch(paraFilter);
121 for (i = 0; i < paraList.size(); i++) {
122 ParagraphTag paraTag=(ParagraphTag)paraList.elementAt(i);
123 curString=paraTag.toPlainTextString();
124 if(curString==null){
125 continue;
126 }
127 Set result = obj.findWordsInArray(keyWords, curString);
128 totalGra=totalGra+result.size()*curTagWei;
129 }
130 //link tag <a>
131 curTagWei=(float)0.25;
132 Parser linkParser = Parser.createParser(file,enC);
133 NodeClassFilter linkFilter =new NodeClassFilter(LinkTag.class);
134 NodeList linkList = linkParser.extractAllNodesThatMatch(linkFilter);
135 for (i = 0; i < linkList.size(); i++) {
136 LinkTag linkTag=(LinkTag)linkList.elementAt(i);
137 curString=linkTag.toPlainTextString();
138 if(curString==null){
139 continue;
140 }
141 Set result = obj.findWordsInArray(keyWords, curString);
142 totalGra=totalGra+result.size()*curTagWei;
143 }
144 }catch(ParserException e) {
145 return 0;
146 }
147 return totalGra;
148 }
149 private FileAndEnc readHtmlFile(){//读取html文件,返回字符串格式的文件与其编码
150 StringBuffer abstr = new StringBuffer();
151 FileAndEnc fae=new FileAndEnc();
152 try{
153 //实例化默认编码方式的BufferefReader
154 BufferedReader enCReader= new BufferedReader(new InputStreamReader(new FileInputStream(filepath),"UTF-8"));
155 String temp=null;
156 while((temp=enCReader.readLine())!=null){//得到字符串格式的文件
157 abstr.append(temp);
158 abstr.append("\r\n");
159 }
160 String result=abstr.toString();
161 fae.setFile(result);
162 String encoding=getEnc(result);
163 fae.setEnc(encoding);//得到页面编码
164 //根据得到的编码方式实例化BufferedReader
165 BufferedReader reader= new BufferedReader(new InputStreamReader(new FileInputStream(filepath),encoding));
166 StringBuffer abstrT = new StringBuffer();
167 while((temp=reader.readLine())!=null){
168 abstrT.append(temp);
169 abstrT.append("\r\n");
170 }
171 result=abstrT.toString();
172 fae.setFile(result);//得到真正的页面内容
173 } catch (FileNotFoundException e) {
174 System.out.println("file not found");
175 fae=null;
176 } catch (IOException e) {
177 // TODO Auto-generated catch block
178 e.printStackTrace();
179 fae=null;
180 } finally {
181 return fae;
182 }
183 }
184 private String getEnc(String file){//根据正则匹配得到页面编码
185 String enC="utf-8";
186 Pattern p = Pattern.compile("(charset|Charset|CHARSET)\\s*=\\s*\"?\\s*([-\\w]*?)[^-\\w]");
187 Matcher m = p.matcher(file);
188 if(m.find()){
189 enC=m.group(2);
190 }
191 return enC;
192 }
193 }

读者需要注意两点:

1.用BufferedReader读取文件是需要编码方式的,但是第一次读取我们必然不知道网页的编码。好在网页对于编码的描述在html语言框架中,我们用默认的编码方式读取文件就可以获取编码。但这个读取的文件的文本内容可能因为编码不正确而产生乱码,所以得到编码后,我们应使用得到的编码再实例化一个BufferedReader读取文件,这样得到的文件就是正确的了(除非网页本身给的编码就不对)。

获得正确的编码对于解析网页内容是非常重要的,而网络上什么样的网页都有,我推荐使用比较基础、可靠的方法获得编码,我使用的是正则匹配。

举个例子:

这是http://kb.cnblogs.com/page/143965/的对编码的描述:

<meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>

这是http://www.ucsd.edu/的对编码的描述:

<meta charset="utf-8"/>

2.不熟悉html的读者可能有所不知<meta>的作用,来看看博客园首页的源码:

<meta name="keywords" content="博客园,开发者,程序员,软件开发,编程,代码,极客,Developer,Programmer,Coder,Code,Coding,Greek,IT学习"/><meta name="description" content="博客园是面向程序员的高品质IT技术学习社区,是程序员学习成长的地方。博客园致力于为程序员打造一个优秀的互联网平台,帮助程序员学好IT技术,更好地用技术改变世界。" />

这两类<meta>标签的很好的描述了网页的内容

@编辑 博客园首页这个keyword的内容里这“Greek”……极客是“Geek”,“Greek”是希腊人

3.由于网页的正文通常是一段最长的纯文本内容,所以当我们得到一个<p>,<li>,<ul>标签的纯文本后,我们可以通过判断字符串的长度来得到网页的正文。

对页面大量的信息进行处理是很费时的,页面的<title>标签和<meta>标签中往往有对网页内容最精炼的描述,开发者应该考虑性能与代价

好,我的经验就介绍完了。我还很菜,如有说的不对、讲得不好的地方望读者指正、提出建议!

 
 

基于htmlparser实现网页内容解析的更多相关文章

  1. 史上最最靠谱,又双叒叒简单的基于MSXML的XML解析指南-C++

    目录 史上最最靠谱,又双叒叒简单的基于MSXML的XML解析指南 流程设计 xml信息有哪几种读取形式(xml文件或wchar) 如何选取节点,and取节点属性有哪些方法? IXMLDOMNode与I ...

  2. 基于Jquery的XML解析器,返回定制的HTML

    依据HTML模板返回解析的XML 依赖jQuery 1.4​1. [代码]基于Jquery的xml解析器并返回定制的HTML     /** *  jQuery插件 *  Author: pureco ...

  3. OO第四单元——基于UML的UML解析器总结&OO课程总结

    OO第四单元--基于UML的UML解析器总结&OO课程总结 前言:一学期愉快(痛苦)的OO课程学习结束了,OO几个单元作业都各有特色,实验也各有特色,仔细回味起来,不再是单纯的敲代码(但自己还 ...

  4. OSS.Core基于Dapper封装(表达式解析+Emit)仓储层的构思及实现

    最近趁着不忙,在构思一个搭建一个开源的完整项目,至于原因以及整个项目框架后边文章我再说明.既然要起一个完整的项目,那么数据仓储访问就必不可少,这篇文章我主要介绍这个新项目(OSS.Core)中我对仓储 ...

  5. Python:使用基于事件驱动的SAX解析XML

    SAX的特点: 是基于事件的 API 在一个比 DOM 低的级别上操作 为您提供比 DOM 更多的控制 几乎总是比 DOM 更有效率 但不幸的是,需要比 DOM 更多的工作 基于对象和基于事件的接口 ...

  6. Java HttpURLConnection 抓取网页内容 解析gzip格式输入流数据并转换为String格式字符串

    最近GFW为了刷存在感,搞得大家是头晕眼花,修改hosts 几乎成了每日必备工作. 索性写了一个小程序,给办公室的同事们分享,其中有个内容 就是抓取网络上的hosts,废了一些周折. 我是在一个博客上 ...

  7. NullSafe基于Runtime的深度解析

    Objective-C是一门动态语言,一个函数是由一个selector(SEL),和一个implement(IML)组成的. 执行一个方法时如果系统找不到方法会给几次机会寻找方法,实在没有此方法就会抛 ...

  8. 几种基于Java的SQL解析工具的比较与调用

    1.sqlparser http://www.sqlparser.com/ 优点:支持的数据库最多,除了传统数据库外还支持hive和greenplum一类比较新的数据库,调用比较方便,功能不错 缺点: ...

  9. Python 爬虫 —— 网页内容解析(lxml)

    0. xpath 语法 找到所有 <img src=....> 图像的链接: xpath = './/img/@src' img_urls = html.xpath(xpath) @修饰节 ...

随机推荐

  1. JavaScript 实现数组的foreach

    Array.prototype.forEach = function (action) { for (var i = 0; i < this.length; i++) { action(this ...

  2. 部分无线终端不响应键盘事件(keydown,keypress,keyup)的解决办法

    在无线侧实现搜索显示smartbox功能的时候,会对输入框绑定keydown.keyup.keypress事件,从而在检测到输入框的值发生改变时,发出请求拉取smartbox的内容. 但是,在iPho ...

  3. python部落刷题宝学到的内置函数(二)

    感觉到刷题宝有一个好处,也许也不是好处,它的答案必须是真正输出的值,也就是说应该输出字符串aaaa的时候,答案必须写成界面上返回的值,即'aaaa'.有利于真正记忆返回值类型,但是....太繁琐了 1 ...

  4. ELT工具Kettle之CDC(Change Data Capture)实现实例

    ETL过程的第一步就是从不同的数据源抽取数据并把数据存储在数据的缓存区.这个过程的主要挑战就是初始加载数据量大和比较慢的网络延迟.在初始加载完成之后,不能再把所有数据重新加载一遍,我们需要的只是变化的 ...

  5. Python进阶(面向对象编程基础)(二)

    1.初始化实例属性 #!/usr/bin/env python # -*- coding:utf-8 -*- __author__ = 'ziv·chan' #定义Person类的__init__方法 ...

  6. Xcode7真机测试

    根据这个网址上的步骤能够完成真机测试,我已经试过了,还不错 http://www.bubuko.com/infodetail-1061938.html

  7. 微信中web页面实现和公众号中查看图片一样的效果

    最近开发了一套资讯相关的web页面,嵌套在微信中,可支持点赞.评论等...在文章详情中,图片需要点击放大,随手势放大缩小,左右可滑动切换,总之类似于微信公众号效果. 开始想的方案是用轮播插件.或者在i ...

  8. EasyInvoice 简介

    注:本文首发于博客园 EasyInvoice 简介,转载请保留本链接 EasyInvoice(简称 EI) 是一款专门为网上卖家量身打造的管理进销存的软件. 1. 简介 解决卖家日常经营中一直存在的商 ...

  9. 结合tcpdump命令对traceroute深入分析

    昨天突然被问到traceroute的原理,一时竟也说不出来,有些命令平时虽然经常在用,但实际原理确并不了解,趁这次机会就来梳理一下. traceroute:是网络诊断中,用来分析IP包经过那些路由的命 ...

  10. 简易浏览器App webview

    使用 public class MainActivity extends Activity {     @Override     protected void onCreate(Bundle sav ...