本文介紹了.net 版的一個HTMLParser網頁解析開源類庫(Winista.HTMLParser)的功能特性、工作原理和使用方法。對於使用.net進行Web信息提取的開發人員進行了一次HTMLParser的初步講解。應用實例將會在日后的文中介紹,敬請關注。
一、背景知識
HTMLParser原本是一個在sourceforge上的一個Java開源項目,使用這個Java類庫可以用來線性地或嵌套地解析HTML文本。他的功能強大和開源等特性吸引了大量Web信息提取的工作者。然而,許多.net開發者朋友一直在尋找一種能在.net中使用的HTMLParser類庫,筆者將介紹Winista.HTMLParser類庫,對比於其他原本數量就非常少的.net版HTMLParser類庫,Winista的版本的類庫結構可以說更接近於原始Java版本。
該類庫目前分為Utltimate、Pro、Lite和Community四個版本,前三個版本都是收費的。只有Community版本可以免費下載並查看所有的源碼。
當前Community最新版本1.8 下載。
該版本的類庫文檔下載。
二、功能和特性
1.可以在任何.net語言中使用(C#,VB.net,J#等)
2.可以解析幾乎所有的Html標簽,並且可以通過標簽類別、屬性或正則表達式來搜索標簽。有些甚至在Java版本中無法支持的標簽也在這個版本中得到了支持。
3.設置可擴展的過濾器來過濾結果集中不需要的標簽。
4.高性能的API接口使得你能處理許多常見的問題,如:哪些是頁面中的外部鏈接?哪些是圖片?哪些是不同的表格?頁面中有錯誤的鏈接嗎等等問題。
5.一個基於Http協議引擎的配置文件使得你能通過一個指定的URL地址來獲得該頁面內容。該爬蟲可以遵循robot.txt協議文件來獲得組織和允許訪問的列表。
6.Http協議引擎能夠完整地處理來自任何站點的反饋。
三、詞法分析的工作原理
HTMLParser的詞法分析器對HTML進行了4級封裝,從低級到高級的順序為:ParserStream、Source、Page、Lexer。ParserStream負責從文件中獲取二進制數據,但不做任何處理。Source把二進制文件轉換成相應的字符序列,存儲一組未加工的字符序列。Page可以看成是一個string數組,按行存儲一個Source文本的每一行第一個字符開始的位置索引。Lexer包含了詞法分析的代碼,從Page里讀取字符串,用Cursor記錄當前字符所在位置,通過狀態機來生成Nodes節點。
Lexer中真正執行詞法分析的是NextCode()方法,它每次詞都查找返回下一個Node節點,直到Page結束。算法描述如下:
1.讀入一個字符,判斷是否已是頁尾,是則返回null。
2.判斷是否是"
3.如果都不是,ParserString狀態機開始解析一個StringNode,如果是"
4.判斷是否到頁尾,是則產生一個StringNode返回。
5.如果讀取到"%",則說明是JSP標簽,進入JSP狀態機去解析。
6.如果讀取到"?",則說明是XML標簽,進入XML狀態機去解析。
7.如果讀取到"/"或任何字母,說明是Tag標簽,進入Tag標簽狀態機去解析。
8.如果讀取到"!",則說明進入了一個注釋標簽,需要再讀取一個字符,如果到頁尾,則產生一個StringNode返回,如果字符為">"則生成一個RemarkNode返回,否則 回退一個字符,再判斷字符如果是"-"則回退一個字符,進入Remark狀態機去解析,如果不是,則回退一個字符進入Tag狀態機去解析。
四、三種使用方法的比較
1.使用Lexer詞法分析器直接解析HTML。
這樣的方法較為底層,只能返回一個線性的Node節點序列,通過Lexer.NextNode()方法獲得下一個Node的信息。雖然不夠方便,但有時可完成一些較為靈活的工作。
調用的方法是(傳入string類型的html代碼):
Lexer lexer = new Lexer(htmlcode);
INode node = lexer.NextNode();
Console.Write(node.ToString());
返回結果是該頁面的第一個標簽"html"的Node結點信息。
2.使用Filter結點過濾模式。
如果你有一些很明確的結點需要提取,那么就該使用Filter結點過濾模式。系統定義了17種具體的Filter,根據不同的過濾條件來獲得需要的結點。包括依據結點父子關系的Filter,連接Filter組合的Filter,依據網頁內容匹配情況的filter,等等。我們也可以繼承 Filter做自己的Filter來提取節點。
NodeList nodeList = myParser.parse(someFilter);
解析之后,我們可以采用:
INode[] nodes = nodeList.toNodeArray();
來獲取節點數組,也可以直接訪問:
INode node = nodeList.elementAt(i);
來獲取Node。
另外,在Filter后得到NodeList以后,我們仍然可以使用nodeList.extractAllNodesThatMatch(someFilter)來進一步過濾,同時又可以用nodeList.visitAllNodesWith(someVisitor)來做進一步的訪問。
3.使用Visitor結點訪問模式
如果你希望HTMLParser遍歷所有的結點,並按結點的不同類型(StringNode、RemarkNode、TagNode)和不同的訪問過程來進行不同操作的話,可以使用Visitor模式。NodeVisitor是一個抽象類,分別定義了如下方法:
BeginParsing():解析前進行的操作
VitisTag():訪問到開始標簽時的操作
VisitEndTag():訪問到結束標簽時的操作
VisitStringNode():訪問到文本結點時的操作
VisitRemarkNode():訪問注釋結點時的操作
自己定義一個類並繼承NodeVisitor類,實現以上幾個方法,即完成Visitor模式的訪問類。系統也提供了7個具體的結點訪問類,具體見上文提供的類庫文檔。不過這7個類並不實用,大多數的功能還需要自己來擴充定義。調用方法:
Parser parser = Parser.CreateParser((htmlcode), "GBK");//傳入string類型的html代碼
NodeVisitor visitor = new LinkFindingVisitor(linktext); //以鏈接查找的Visitor舉例
parser.VisitAllNodesWith(visirot);
靈活使用以上三種模式的結合,相信就可以提取到任何我們所需要的信息了。