HtmlParser
一,數據組織分析:
HtmlParser主要靠Node、AbstractNode和Tag來表達Html,由於Remark和Text相對簡單,此處就將其忽略了。
Node 是造成樹結構表示HTML的基礎,全部的數據表示都是接口Node的實現,Node定義了與頁面樹結構所表達的頁面Page對象,定義了獲取父、子、兄弟 節點的方法,定義了節點到對應html文本的方法,定義了該節點對應的起止位置,定義了過濾方法,定義了Visitor訪問機制。
AbstractNode 是Node的一種具體的類實現,起到構成樹形結構的做用,除了同具體Node相關的accetp方 法,toString,toHtml,toPlainTextString方法之外,AbstractNode實現了大多基本的方法,使得它的子類,不用 理會具體的樹操做。
Tag是具體分析的主要內容。Tag分紅composite的Tag和不能包含其餘Tag的簡單Tag兩類,其中前者的基 類是CompositeTag,其子類包含BodyTag,Div,FrameSetTag,OptionTag,等27個子類;而簡單Tag有 BaseHrefTag、 DoctypeTag,FrameTag,ImageTag,InputTag,JspTag,MetaTag,ProcessingInstructionTag 這八類。
Node分紅三類:
RemarkNode:表明Html中的註釋
TagNode:標籤節點,是種類最多的節點類型,上述Tag的具體節點類都是TagNode的實現。
TextNode:文本節點
二,Visitor方式訪問Html:
1,總體解析過程
用一個URL或頁面String作一個Parser
用這個Parser作一個Visitor
使用Parser.visitAllNodeWith(Visitor)來遍歷節點
獲取Visitor遍歷後獲得的數據
2,Visit過程
作解析以前作的事情:visitor.beginParsing();
每次取到一個節點Node,讓該Node接受accept該Visitor
作解析後作的事情:visitor.finishedParsing();
3,獲取節點的過程:逐步遍歷Html,分析出Node。此部分較爲複雜,且對於咱們應用來講無需不少了解,暫跳過。
4,節點訪問節點訪問採用Visitor模式,Node的accept方法和具體Visitor的visit方法是關鍵。首先三類Node來accept的方式各不相同:
對於全部TagNode都使用一個accept方法,即TagNode的accept方法。首先判斷是不是標籤結尾,若是是就visitor.visitEndTag (this);不然visitor.visitTag (this);
若是是TextNode,那就visitor.visitStringNode (this);就能夠了。
若是是RemarkNode,那就visitor.visitRemarkNode (this);就能夠了。
實 際上NodeVisitor裏邊這四種visit方法都是空的,由於在不一樣的Visitor中對於這三類節點的處理是不一樣的;對於須要處理的節點,只要重 載對應的visit方法就好了,若是不處理那就不理會就能夠了;另外,若是用戶用本身的Visitor,那麼還能夠靈活的處理不一樣類型的節點了。
系 統爲咱們實現了下面我要介紹的8種Visitor,實際上能夠看做是系統給咱們演示瞭如何作各類各樣的Visitor來訪問Html,由於實際上咱們要真 正來用HtmlParser的話,還須要特定的Visitor,而經過簡單的這些系統提供的Visitor組合是難以作成什麼事情的。
三,系統Visitor功能簡介:
ObjectFindingVisitor:用來找出全部指定類型的節點,採用getTags()來獲取結果。
StringBean: 用來從一個指定的URL獲取移除了<SCRIPT></SCRIPT>和<PRE></PRE>之間代 碼的Html代碼,也能夠用作Visitor,用來移除這兩種標籤內部的代碼,採用StringBean.getStrings()來獲取結果。
HtmlPage:提取Title,body中的節點和頁面中的TableTag節點。
LinkFindingVisitor:找出節點中包含某個連接的總個數。
StringFindingVisitor:找出遍歷的TextNode中含有指定字符串的個數。
TagFindingVisitor:找出指定Tag的全部節點,能夠指定多種類型。
TextExtractingVisitor:從網頁中把全部標籤去掉來提取文本,這個提取文本的Visitor有時是很實用的,只是注意在提取文本時將標籤的屬性也去掉了,也就是說只剩下標籤之間的文本,例如<a>中的連接也去掉了。
UrlModifyingVisitor:用來修改網頁中的連接。
四,Filter
若是說visitor是遍歷提取信息,固然這個信息能夠包括某些節點或者從節點分析出來的更有效的信息,這都取決於咱們的Visitor作成什麼樣子,那麼Filter則目標很明確,就是用來提取節點的。因此說要想用HtmlParser,首先要熟悉上面講到的數據組織。
系 統定義了17種具體的Filter,包括依據節點父子關係的Filter,鏈接Filter組合的Filter,依據網頁內容匹配狀況的filter,等 等。咱們也能夠implement Filter來作本身的Filter來提取節點。Filter的調用是同Visitor獨立的,由於也無需先filter出一些NodeList,再用 Visitor來訪問。調用Filter的方法是:NodeList nodeList = myParser.parse(someFilter);解析以後,咱們能夠採用:Node[] nodes = nodeList.toNodeArray();來獲取節點數組,也能夠直接訪問:Node node = nodeList.elementAt(i)來獲取Node。
另外,在Filter後獲得NodeList之後,咱們仍然可使用 NodeList的extractAllNodesThatMatch(someFilter)來進一步過濾,同時又能夠用NodeList的 isitAllNodesWith(someVisitor)來作進一步的訪問。這樣,咱們能夠看到HtmlParser爲咱們提供了很是方便的Html 解析方式,針對不一樣的應用能夠採用visitor來遍歷Html節點提取數據,也能夠用Filter來過濾節點,提取出咱們所關注的節點,再對節點進行處 理。經過這樣的組合,必定可以找出咱們所須要的信息。
示例代碼:import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import org.htmlparser.Node;
import org.htmlparser.NodeFilter;
import org.htmlparser.Parser;
import org.htmlparser.filters.NodeClassFilter;
import org.htmlparser.filters.OrFilter;
import org.htmlparser.nodes.TextNode;
import org.htmlparser.tags.LinkTag;
import org.htmlparser.util.NodeList;
import org.htmlparser.util.ParserException;
import org.htmlparser.visitors.HtmlPage;
import org.htmlparser.visitors.TextExtractingVisitor;
import com.jscud.util.LogMan;
//一個日誌記錄類
/**
* 演示了Html Parse的應用.
*
* @author liuql
*/
public
class ParseHtmlTest
{
public
static
void main(String[] args)
throws Exception
{
String aFile =
"e:/jscud/temp/test.htm";
String content = readTextFile(aFile,
"GBK");
test1(content);
System.out.println(
"====================================");
test2(content);
System.out.println(
"====================================");
test3(content);
System.out.println(
"====================================");
test4(content);
System.out.println(
"====================================");
test5(aFile);
System.out.println(
"====================================");
//訪問外部資源,相對慢
test5(
"http://www.163.com");
System.out.println("====================================");
}
/**
* 讀取文件的方式來分析內容.
* filePath也能夠是一個Url.
*
* @param resource 文件/Url
*/
public static void test5(String resource) throws Exception
{
Parser myParser = new Parser(resource);
//設置編碼
myParser.setEncoding("GBK");
HtmlPage visitor = new HtmlPage(myParser);
myParser.visitAllNodesWith(visitor);
String textInPage = visitor.getTitle();
System.out.println(textInPage);
}
/**
* 按頁面方式處理.對一個標準的Html頁面,推薦使用此種方式.
*/
public static void test4(String content) throws Exception
{
Parser myParser;
myParser = Parser.createParser(content, "GBK");
HtmlPage visitor = new HtmlPage(myParser);
myParser.visitAllNodesWith(visitor);
String textInPage = visitor.getTitle();
System.out.println(textInPage);
}
/**
* 利用Visitor模式解析html頁面.
*
* 小優勢:翻譯了<>等符號
* 缺點:好多空格,沒法提取link
*
*/
public static void test3(String content) throws Exception
{
Parser myParser;
myParser = Parser.createParser(content, "GBK");
TextExtractingVisitor visitor = new TextExtractingVisitor();
myParser.visitAllNodesWith(visitor);
String textInPage = visitor.getExtractedText();
System.out.println(textInPage);
}
/**
* 獲得普通文本和連接的內容.
*
* 使用了過濾條件.
*/
public static void test2(String content) throws ParserException
{
Parser myParser;
NodeList nodeList = null;
myParser = Parser.createParser(content, "GBK");
NodeFilter textFilter = new NodeClassFilter(TextNode.class);
NodeFilter linkFilter = new NodeClassFilter(LinkTag.class);
//暫時不處理 meta
//NodeFilter metaFilter = new NodeClassFilter(MetaTag.class);
OrFilter lastFilter = new OrFilter();
lastFilter.setPredicates(new NodeFilter[] { textFilter, linkFilter });
nodeList = myParser.parse(lastFilter);
Node[] nodes = nodeList.toNodeArray();
for (int i = 0; i < nodes.length; i++)
{
Node anode = (Node) nodes;
String line = "";
if (anode instanceof TextNode)
{
TextNode textnode = (TextNode) anode;
//line = textnode.toPlainTextString().trim();
line = textnode.getText();
}
else if (anode instanceof LinkTag)
{
LinkTag linknode = (LinkTag) anode;
line = linknode.getLink();
//@todo 過濾jsp標籤:能夠本身實現這個函數
//line = StringFunc.replace(line, "<%.*%>", "");
}
if (isTrimEmpty(line))
continue;
System.out.println(line);
}
}
/**
* 解析普通文本節點.
*
* @param content
* @throws ParserException
*/
public static void test1(String content) throws ParserException
{
Parser myParser;
Node[] nodes = null;
myParser = Parser.createParser(content, null);
nodes = myParser.extractAllNodesThatAre(TextNode.class); //exception could be thrown here
for (int i = 0; i < nodes.length; i++)
{
TextNode textnode = (TextNode) nodes;
String line = textnode.toPlainTextString().trim();
if (line.equals(""))
continue;
System.out.println(line);
}
}
/**
* 讀取一個文件到字符串裏.
*
* @param sFileName 文件名
* @param sEncode String
* @return 文件內容
*/
public static String readTextFile(String sFileName, String sEncode)
{
StringBuffer sbStr = new StringBuffer();
try
{
File ff = new File(sFileName);
InputStreamReader read = new InputStreamReader(new FileInputStream(ff),
sEncode);
BufferedReader ins = new BufferedReader(read);
String dataLine = "";
while (null != (dataLine = ins.readLine()))
{
sbStr.append(dataLine);
sbStr.append("\r\n");
}
ins.close();
}
catch (Exception e)
{
LogMan.error("read Text File Error", e);
}
return sbStr.toString();
}
/**
* 去掉左右空格後字符串是否爲空
* @param astr String
* @return boolean
*/
public static boolean isTrimEmpty(String astr)
{
if ((null == astr) || (astr.length() == 0))
{
return true;
}
if (isBlank(astr.trim()))
{
return true;
}
return false;
}
/**
* 字符串是否爲空:null或者長度爲0.
* @param astr 源字符串.
* @return boolean
*/
public static boolean isBlank(String astr)
{
if ((null == astr) || (astr.length() == 0))
{
return true;
}
else
{
return false; } }}