Posted五月19,2008Comments(11)
需要做一个垂直搜索引擎,比较了nekohtml和htmlparser的功能,尽管nekohtml在容错性、性能等方面的口碑好像比htmlparser好(htmlunit也用的是nekohtml),但感觉nekohtml的测试用例和文档都比htmlparser都少,而且htmlparser基本上能够满足垂直搜索引擎页面处理分析的需求,因此先研究一下htmlparser的使用,有空再研究nekohtml和mozillahtmlparser的使用。html的功能还是官方说得最为清楚,
HTMLParserisaJavalibraryusedtoparseHTMLineitheralinearornestedfashion.Primarilyusedfortransformationorextraction,itfeaturesfilters,visitors,customtagsandeasytouseJavaBeans.Itisafast,robustandwelltestedpackage.
Thetwofundamentaluse-casesthatarehandledbytheparserareextractionandtransformation(thesynthesesuse-case,whereHTMLpagesarecreatedfromscratch,isbetterhandledbyothertoolsclosertothesourceofdata).Whilepriorversionsconcentratedondataextractionfromwebpages,Version1.4oftheHTMLParserhassubstantialimprovementsintheareaoftransformingwebpages,withsimplifiedtagcreationandediting,andverbatimtoHtml()methodoutput.
研究的重点还是extraction的使用,有空再研究transformation的使用。
1、htmlparser对html页面处理的数据结构
如图所示,HtmlParser采用了经典的Composite模式,通过RemarkNode、TextNode、TagNode、AbstractNode和Tag来描述HTML页面各元素。
·org.htmlparser.Node:
Node接口定义了进行树形结构节点操作的各种典型操作方法,包括:
节点到html文本、text文本的方法:toPlainTextString、toHtml
典型树形结构遍历的方法:getParent、getChildren、getFirstChild、getLastChild、getPreviousSibling、getNextSibling、getText
获取节点对应的树形结构结构的顶级节点Page对象方法:getPage
获取节点起始位置的方法:getStartPosition、getEndPosition
Visitor方法遍历节点时候方法:accept(NodeVisitorvisitor)
Filter方法:collectInto(NodeListlist,NodeFilterfilter)
Object方法:toString、clone
·org.htmlparser.nodes.AbstractNode:
AbstractNode是形成HTML树形结构抽象基类,实现了Node接口。
在htmlparser中,Node分成三类:
RemarkNode:代表Html中的注释
TagNode:标签节点。
TextNode:文本节点
这三类节点都继承AbstractNode。
·org.htmlparser.nodes.TagNode:
TagNode包含了对HTML处理的核心的各个类,是所有TAG的基类,其中有分为包含其他TAG的复合节点ComositeTag和不包含其他TAG的叶子节点Tag。
复合节点CompositeTag:
AppletTag,BodyTag,Bullet,BulletList,DefinitionList,DefinitionListBullet,Div,FormTag,FrameSetTag,HeadingTag,
HeadTag,Html,LabelTag,LinkTag,ObjectTag,ParagraphTag,ScriptTag,SelectTag,Span,StyleTag,TableColumn,
TableHeader,TableRow,TableTag,TextareaTag,TitleTag
叶子节点TAG:
BaseHrefTag,DoctypeTag,FrameTag,ImageTag,InputTag,JspTag,MetaTag,ProcessingInstructionTag,
2、htmlparser对html页面处理的算法
主要是如下几种方式
·采用Visitor方式访问Html
try{
Parserparser=newParser();
parser.setURL(“http://www.google.com”);
parser.setEncoding(parser.getEncoding());
NodeVisitorvisitor=newNodeVisitor(){
publicvoidvisitTag(Tagtag){
logger.fatal(“testVisitorAll()Tagnameis:”
+tag.getTagName()+”\nClassis:”
+tag.getClass());
}
};
parser.visitAllNodesWith(visitor);
}catch(ParserExceptione){
e.printStackTrace();
}
·采用Filter方式访问html
try{
NodeFilterfilter=newNodeClassFilter(LinkTag.class);
Parserparser=newParser();
parser.setURL(“http://www.google.com”);
parser.setEncoding(parser.getEncoding());
NodeListlist=parser.extractAllNodesThatMatch(filter);
for(inti=0;i<list.size();i++){
LinkTagnode=(LinkTag)list.elementAt(i);
logger.fatal(“testLinkTag()Linkis:”+node.extractLink());
}
}catch(Exceptione){
e.printStackTrace();
}
·采用org.htmlparser.beans方式
另外htmlparser还在org.htmlparser.beans中对一些常用的方法进行了封装,以简化操作,例如:
Parserparser=newParser();
LinkBeanlinkBean=newLinkBean();
linkBean.setURL(“http://www.google.com”);
URL[]urls=linkBean.getLinks();
for(inti=0;i<urls.length;i++){
URLurl=urls[i];
logger.fatal(“testLinkBean()-urlis:”+url);
}
3、htmlparser关键包结构说明
htmlparser其实核心代码并不多,好好研究一下其代码,弥补文档不足的问题。同时htmlparser的代码注释和单元测试用例还是很齐全的,也有助于了解htmlparser的用法。
3.1、org.htmlparser
定义了htmlparser的一些基础类。其中最为重要的是Parser类。
Parser是htmlparser的最核心的类,其构造函数提供了如下:Parser.createParser(Stringhtml,Stringcharset)、Parser()、Parser(Lexerlexer,ParserFeedbackfb)、Parser(URLConnectionconnection,ParserFeedbackfb)、Parser(Stringresource,ParserFeedbackfeedback)、Parser(Stringresource)
各构造函数的具体用法及含义可以查看其代码,很容易理解。
Parser常用的几个方法:
·elements获取元素
Parserparser=newParser(“http://www.google.com”);
for(NodeIteratori=parser.elements();i.hasMoreElements();)
processMyNodes(i.nextNode());
·parse(NodeFilterfilter):通过NodeFilter方式获取
·visitAllNodesWith(NodeVisitorvisitor):通过Nodevisitor方式
·extractAllNodesThatMatch(NodeFilterfilter):通过NodeFilter方式
3.2、org.htmlparser.beans
对Visitor和Filter的方法进行了封装,定义了针对一些常用html元素操作的bean,简化对常用元素的提取操作。
包括:FilterBean、HTMLLinkBean、HTMLTextBean、LinkBean、StringBean、BeanyBaby等。
3.3、org.htmlparser.nodes
定义了基础的node,包括:AbstractNode、RemarkNode、TagNode、TextNode等。
3.4、org.htmlparser.tags
定义了htmlparser的各种tag。
3.5、org.htmlparser.filters
定义了htmlparser所提供的各种filter,主要通过extractAllNodesThatMatch(NodeFilterfilter)来对html页面指定类型的元素进行过滤,包括:AndFilter、CssSelectorNodeFilter、HasAttributeFilter、HasChildFilter、HasParentFilter、HasSiblingFilter、IsEqualFilter、LinkRegexFilter、LinkStringFilter、NodeClassFilter、NotFilter、OrFilter、RegexFilter、StringFilter、TagNameFilter、XorFilter
3.6、org.htmlparser.visitors
定义了htmlparser所提供的各种visitor,主要通过visitAllNodesWith(NodeVisitorvisitor)来对html页面元素进行遍历,包括:HtmlPage、LinkFindingVisitor、NodeVisitor、ObjectFindingVisitor、StringFindingVisitor、TagFindingVisitor、TextExtractingVisitor、UrlModifyingVisitor
3.7、org.htmlparser.parserapplications
定义了一些实用的工具,包括LinkExtractor、SiteCapturer、StringExtractor、WikiCapturer,这几个类也可以作为htmlparser使用样例。
3.8、org.htmlparser.tests
对各种功能的单元测试用例,也可以作为htmlparser使用的样例。
4、htmlparser的使用样例
importjava.net.URL;
importjunit.framework.TestCase;
importorg.apache.log4j.Logger;
importorg.htmlparser.Node;
importorg.htmlparser.NodeFilter;
importorg.htmlparser.Parser;
importorg.htmlparser.Tag;
importorg.htmlparser.beans.LinkBean;
importorg.htmlparser.filters.NodeClassFilter;
importorg.htmlparser.filters.OrFilter;
importorg.htmlparser.filters.TagNameFilter;
importorg.htmlparser.tags.HeadTag;
importorg.htmlparser.tags.ImageTag;
importorg.htmlparser.tags.InputTag;
importorg.htmlparser.tags.LinkTag;
importorg.htmlparser.tags.OptionTag;
importorg.htmlparser.tags.SelectTag;
importorg.htmlparser.tags.TableColumn;
importorg.htmlparser.tags.TableRow;
importorg.htmlparser.tags.TableTag;
importorg.htmlparser.tags.TitleTag;
importorg.htmlparser.util.NodeIterator;
importorg.htmlparser.util.NodeList;
importorg.htmlparser.util.ParserException;
importorg.htmlparser.visitors.HtmlPage;
importorg.htmlparser.visitors.NodeVisitor;
importorg.htmlparser.visitors.ObjectFindingVisitor;
publicclassParserTestCaseextendsTestCase{
privatestaticfinalLoggerlogger=Logger.getLogger(ParserTestCase.class);
publicParserTestCase(Stringname){
super(name);
}
/*
*测试ObjectFindVisitor的用法
*/
publicvoidtestImageVisitor(){
try{
ImageTagimgLink;
ObjectFindingVisitorvisitor=newObjectFindingVisitor(
ImageTag.class);
Parserparser=newParser();
parser.setURL(“http://www.google.com”);
parser.setEncoding(parser.getEncoding());
parser.visitAllNodesWith(visitor);
Node[]nodes=visitor.getTags();
for(inti=0;i<nodes.length;i++){
imgLink=(ImageTag)nodes[i];
logger.fatal(“testImageVisitor()ImageURL=”
+imgLink.getImageURL());
logger.fatal(“testImageVisitor()ImageLocation=”
+imgLink.extractImageLocn());
logger.fatal(“testImageVisitor()SRC=”
+imgLink.getAttribute(“SRC”));
}
}
catch(Exceptione){
e.printStackTrace();
}
}
/*
*测试TagNameFilter用法
*/
publicvoidtestNodeFilter(){
try{
NodeFilterfilter=newTagNameFilter(“IMG”);
Parserparser=newParser();
parser.setURL(“http://www.google.com”);
parser.setEncoding(parser.getEncoding());
NodeListlist=parser.extractAllNodesThatMatch(filter);
for(inti=0;i<list.size();i++){
logger.fatal(“testNodeFilter()”+list.elementAt(i).toHtml());
}
}catch(Exceptione){
e.printStackTrace();
}
}
/*
*测试NodeClassFilter用法
*/
publicvoidtestLinkTag(){
try{
NodeFilterfilter=newNodeClassFilter(LinkTag.class);
Parserparser=newParser();
parser.setURL(“http://www.google.com”);
parser.setEncoding(parser.getEncoding());
NodeListlist=parser.extractAllNodesThatMatch(filter);
for(inti=0;i<list.size();i++){
LinkTagnode=(LinkTag)list.elementAt(i);
logger.fatal(“testLinkTag()Linkis:”+node.extractLink());
}
}catch(Exceptione){
e.printStackTrace();
}
}
/*
*测试<linkhref=”text=’text/css’rel=’stylesheet’/>用法
*/
publicvoidtestLinkCSS(){
try{
Parserparser=newParser();
parser
.setInputHTML(“<head><title>LinkTest</title>”
+“<linkhref=’/test01/css.css’text=’text/css’rel=’stylesheet’/>”
+“<linkhref=’/test02/css.css’text=’text/css’rel=’stylesheet’/>”
+“</head>”+“<body>”);
parser.setEncoding(parser.getEncoding());
NodeListnodeList=null;
for(NodeIteratore=parser.elements();e.hasMoreNodes();){
Nodenode=e.nextNode();
logger
.fatal(“testLinkCSS()”+node.getText()
+node.getClass());
}
}catch(Exceptione){
e.printStackTrace();
}
}
/*
*测试OrFilter的用法
*/
publicvoidtestOrFilter(){
NodeFilterinputFilter=newNodeClassFilter(InputTag.class);
NodeFilterselectFilter=newNodeClassFilter(SelectTag.class);
ParsermyParser;
NodeListnodeList=null;
try{
Parserparser=newParser();
parser
.setInputHTML(“<head><title>OrFilterTest</title>”
+“<linkhref=’/test01/css.css’text=’text/css’rel=’stylesheet’/>”
+“<linkhref=’/test02/css.css’text=’text/css’rel=’stylesheet’/>”
+“</head>”
+“<body>”
+“<inputtype=’text’value=’text1′name=’text1′/>”
+“<inputtype=’text’value=’text2′name=’text2′/>”
+“<select><optionid=’1′>1</option><optionid=’2′>2</option><optionid=’3′></option></select>”
+“<ahref=’http://www.yeeach.com’>yeeach.com</a>”
+“</body>”);
parser.setEncoding(parser.getEncoding());
OrFilterlastFilter=newOrFilter();
lastFilter.setPredicates(newNodeFilter[]{selectFilter,
inputFilter});
nodeList=parser.parse(lastFilter);
for(inti=0;i<=nodeList.size();i++){
if(nodeList.elementAt(i)instanceofInputTag){
InputTagtag=(InputTag)nodeList.elementAt(i);
logger.fatal(“OrFiltertagnameis:”+tag.getTagName()
+”,tagvalueis:”+tag.getAttribute(“value”));
}
if(nodeList.elementAt(i)instanceofSelectTag){
SelectTagtag=(SelectTag)nodeList.elementAt(i);
NodeListlist=tag.getChildren();
for(intj=0;j<list.size();j++){
OptionTagoption=(OptionTag)list.elementAt(j);
logger
.fatal(“OrFilterOption”
+option.getOptionText());
}
}
}
}catch(ParserExceptione){
e.printStackTrace();
}
}
/*
*测试对<table><tr><td></td></tr></table>的解析
*/
publicvoidtestTable(){
ParsermyParser;
NodeListnodeList=null;
myParser=Parser.createParser(“<body>”+“<tableid=’table1′>”
+“<tr><td>1-11</td><td>1-12</td><td>1-13</td>”
+“<tr><td>1-21</td><td>1-22</td><td>1-23</td>”
+“<tr><td>1-31</td><td>1-32</td><td>1-33</td></table>”
+“<tableid=’table2′>”
+“<tr><td>2-11</td><td>2-12</td><td>2-13</td>”
+“<tr><td>2-21</td><td>2-22</td><td>2-23</td>”
+“<tr><td>2-31</td><td>2-32</td><td>2-33</td></table>”
+“</body>”,“GBK”);
NodeFiltertableFilter=newNodeClassFilter(TableTag.class);
OrFilterlastFilter=newOrFilter();
lastFilter.setPredicates(newNodeFilter[]{tableFilter});
try{
nodeList=myParser.parse(lastFilter);
for(inti=0;i<=nodeList.size();i++){
if(nodeList.elementAt(i)instanceofTableTag){
TableTagtag=(TableTag)nodeList.elementAt(i);
TableRow[]rows=tag.getRows();
for(intj=0;j<rows.length;j++){
TableRowtr=(TableRow)rows[j];
TableColumn[]td=tr.getColumns();
for(intk=0;k<td.length;k++){
logger.fatal(“<td>”+td[k].toPlainTextString());
}
}
}
}
}catch(ParserExceptione){
e.printStackTrace();
}
}
/*
*测试NodeVisitor的用法,遍历所有节点
*/
publicvoidtestVisitorAll(){
try{
Parserparser=newParser();
parser.setURL(“http://www.google.com”);
parser.setEncoding(parser.getEncoding());
NodeVisitorvisitor=newNodeVisitor(){
publicvoidvisitTag(Tagtag){
logger.fatal(“testVisitorAll()Tagnameis:”
+tag.getTagName()+”\nClassis:”
+tag.getClass());
}
};
parser.visitAllNodesWith(visitor);
}catch(ParserExceptione){
e.printStackTrace();
}
}
/*
*测试对指定Tag的NodeVisitor的用法
*/
publicvoidtestTagVisitor(){
try{
Parserparser=newParser(
“<head><title>dddd</title>”
+“<linkhref=’/test01/css.css’text=’text/css’rel=’stylesheet’/>”
+“<linkhref=’/test02/css.css’text=’text/css’rel=’stylesheet’/>”
+“</head>”+“<body>”
+“<ahref=’http://www.yeeach.com’>yeeach.com</a>”
+“</body>”);
NodeVisitorvisitor=newNodeVisitor(){
publicvoidvisitTag(Tagtag){
if(taginstanceofHeadTag){
logger.fatal(“visitTag()HeadTag:Tagnameis:”
+tag.getTagName()+”\nClassis:”
+tag.getClass()+“\nTextis:”
+tag.getText());
}elseif(taginstanceofTitleTag){
logger.fatal(“visitTag()TitleTag:Tagnameis:”
+tag.getTagName()+”\nClassis:”
+tag.getClass()+“\nTextis:”
+tag.getText());
}elseif(taginstanceofLinkTag){
logger.fatal(“visitTag()LinkTag:Tagnameis:”
+tag.getTagName()+”\nClassis:”
+tag.getClass()+“\nTextis:”
+tag.getText()+”\ngetAttributeis:”
+tag.getAttribute(“href”));
}else{
logger.fatal(“visitTag():Tagnameis:”
+tag.getTagName()+”\nClassis:”
+tag.getClass()+“\nTextis:”
+tag.getText());
}
}
};
parser.visitAllNodesWith(visitor);
}catch(Exceptione){
e.printStackTrace();
}
}
/*
*测试HtmlPage的用法
*/
publicvoidtestHtmlPage(){
StringinputHTML=“<html>”+“<head>”
+“<title>WelcometotheHTMLParserwebsite</title>”
+“</head>”+“<body>”+“WelcometoHTMLParser”
+“<tableid=’table1′>”
+“<tr><td>1-11</td><td>1-12</td><td>1-13</td>”
+“<tr><td>1-21</td><td>1-22</td><td>1-23</td>”
+“<tr><td>1-31</td><td>1-32</td><td>1-33</td></table>”
+“<tableid=’table2′>”
+“<tr><td>2-11</td><td>2-12</td><td>2-13</td>”
+“<tr><td>2-21</td><td>2-22</td><td>2-23</td>”
+“<tr><td>2-31</td><td>2-32</td><td>2-33</td></table>”
+“</body>”+“</html>”;
Parserparser=newParser();
try{
parser.setInputHTML(inputHTML);
parser.setEncoding(parser.getURL());
HtmlPagepage=newHtmlPage(parser);
parser.visitAllNodesWith(page);
logger.fatal(“testHtmlPage-titleis:”+page.getTitle());
NodeListlist=page.getBody();
for(NodeIteratoriterator=list.elements();iterator
.hasMoreNodes();){
Nodenode=iterator.nextNode();
logger.fatal(“testHtmlPage-nodeis:”+node.toHtml());
}
}catch(ParserExceptione){
//TODOAuto-generatedcatchblock
e.printStackTrace();
}
}
/*
*测试LinkBean的用法
*/
publicvoidtestLinkBean(){
Parserparser=newParser();
LinkBeanlinkBean=newLinkBean();
linkBean.setURL(“http://www.google.com”);
URL[]urls=linkBean.getLinks();
for(inti=0;i<urls.length;i++){
URLurl=urls[i];
logger.fatal(“testLinkBean()-urlis:”+url);
}
}
}
5、相关的项目
nekohtml:评价比htmlparser好,把html正规化标准的xml文档,用xerces处理,但文档较少。
mozillahtmlparser:http://www.dapper.net/网站采用的html解析器,开源了,基于mozilla的解析器,值得研究一下。
http://jerichohtml.sourceforge.net/
http://htmlcleaner.sourceforge.net/
http://html.xamjwg.org/cobra.jsp
https://xhtmlrenderer.dev.java.net
其他一些htmlparser可以参考相关的汇总文章:
http://www.manageability.org/blog/stuff/screen-scraping-tools-written-in-java/view
http://java-source.net/open-source/html-parsers
http://www.open-open.com/30.htm
6、参考文档
http://www.blogjava.net/lostfire/archive/2006/07/02/56212.html
http://blog.csdn.net/scud/archive/2005/08/11/451397.aspx
http://chasethedevil.blogspot.com/2006/05/java-html-parsing-example-with.html
http://javaboutique.internet.com/tutorials/HTMLParser/
Technorati标签:nekohtml,htmlparser,scraping,scrape,spider,爬虫,crawler