本文介紹了.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標籤,並且可以通過標籤類別、屬性或Regex來搜尋標籤。有些甚至在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);
靈活使用以上三種模式的結合,相信就可以提取到任何我們所需要的資訊了。