2013年9月17日 星期二

[轉貼] HTML Agility Pack:簡單好用的快速 HTML Parser

出處:http://msdn.microsoft.com/zh-tw/ee787055.aspx

HTML Agility Pack:簡單好用的快速 HTML Parser

Codeplex 軟體套件(Package)資訊
套件名稱HTML Agility Pack
作者Simon Mourier
目前版本1.4.0 Beta 2
URLhttp://htmlagilitypack.codeplex.com/
使用難易度
使用此套件時可用的輔助工具HAP Explorer(可在上述 URL 找到)
Internet Explorer 8 開發者工具
基礎知識HTML
XML 和 XPath
最好有使用過 System.Xml 命名空間中的 XmlDocument 類別,以及其 SelectNodes() 或 SelectSingleNode() 方法。

解析 HTML:Web 開發人員心中的痛

自從 Web 應用程式自 1993 年 W3C 設立以來就開始發展,而且 HTML 也歷經了數個版本的演化(1.0 – 2.0 – 3.0 – 3.2 – 4.0 – 4.01),現在也已經成為Web網頁或應用程式的最基礎,想要學習如何設計 Web 網頁或開發 Web 應用程式,這已經是絕對必須要學的東西了,就算是方便的控制項充斥(例如 ASP.NET),但 HTML 仍然有學習它的必要性,因此如果不會 HTML,就等於沒學過 Web 網頁般。
拜 HTML 與 Web 瀏覽器蓬勃發展之賜,各式各樣的應用都在網路上迅速發展,舉凡電子商務、企業入口、線上下單、企業間協同應用等,乃至於社群、個人化、Web 2.0 等商務與組織運用等能力,而在資訊爆炸的時代,很多資訊整合的應用也隨之出爐,而這些資訊整合的應用程式都會連接到不同的網站下載其資訊,並且在重重的 HTML 中剖析出想要的資料(例如每股價格、漲跌幅、成交量等)。
但是 HTML 本身並不是一個結構嚴謹的語言,它允許標籤(tag)可以在不 close 的情況下繼續使用。這也是因為瀏覽器設計的高容錯性(Fault Tolerance)所致,如此一來,想要依照規則來剖析 HTML 文件幾乎變得不可能,而且對方的網站的 HTML 結構也可能會隨時變化,在這種情況下,剖析 HTML 變得非常辛苦,雖然 W3C 有另外推展 XHTML(遵守 XML 嚴謹格式的 HTML),但使用它來設計網頁的案例仍為少數,大多數的網站仍然是使用 HTML。因此我們會需要一個工具,能夠有方法快速的解析 HTML 以取出我們需要的資料。

傳統解析 HTML 的方法

大家都知道,HTML 本身其實只是一個 HTML 標記的字串而已,因此一般說到要解析 HTML,第一個會想到的大概就是字串比對(string comparison),自己針對 HTML 的結構寫一個 pattern,然後由函式去做逐一的比對,例如:
[C#]
  1. string pattern = "<td id='stockPrice'>";
  2. html.IndexOf(pattern);
不過傳統的字串比對效能太差,也沒有一個規則性,因而才發展出規則運算式(Regular Expression)技術,例如下列這樣的語法:
[Regular Expression]
  1. </?\w+((\s+\w+(\s*=\s*(?:".*?"|'.*?'|[^'">\s]+))?)+\s*|\s*)/?>
但 Regular Expression 的學習曲線很高,若要使用它來解析 HTML,並且再加以客制化(Customization)的話,對於一般開發人員來說,實在沒有什麼親和力。
HTML 還有一個特色,就是它是具階層性(Hierarchy)的,因此瀏覽器在解譯它的時候都會以文件樹(document tree)的方式,再用遞迴(recursive)的方法來處理它,但 Regular Expression 沒有支援階層性的剖析,而最接近階層剖析又好用的工具,莫過於 XML Parser 了,它的 DOM 以及 XPath 的特性,都可以讓解析 XML 的工作變得輕鬆,然而 XML Parser 無法讀取一般的 HTML(XHTML 可以),因為一般的 HTML 是結構鬆散的類型,XML Parser 會在讀入時檢查語法結構是否完整(也就是 Well-known 的結構),若讀入的是結構鬆散的內容的話會擲出例外訊息,因此無法直接使用 XML Parser 來輔助。

HTML 文件樹(IE8 開發者工具)
不過,現在已經有人發展出可以在 HTML 上面使用類似於 XPath 的方式來存取鬆散結構的 HTML 的工具,並且在 Codeplex 上以開放原始碼的方式公開給外界使用,這個工具就是本文所要介紹的 HTML Agility Pack。

HTML Agility Pack 簡介

HTML Agility Pack 是由法國的一位軟體架構師 Simon Mourier 所發展,並且由 DarthObiwan 以及 Jessynoo 輔助開發出來的一個軟體工具,它可以讓剖析鬆散格式 HTML 的工作就像剖析 XML 一樣簡單,它也有類似於 System.Xml 命名空間中的 XML DOM 的許多類別,除了可以使用階層的方式存取 HTML 以外,它也支援使用 XPath 的方式來搜尋 HTML,這會較以往使用文字比對或是 Regular Expression 的比對方式來得更明確,例如:
上圖中以藍色方框框住的是 W3C 的最新消息公告區,而它的 HTML 階層樹是這個樣子:
以往要使用 Regular Expression 剖析時可能要走很多步驟(Match 會回傳很多資料,除非寫的夠精準),才會到達方框所在的位置,但使用 HTML Agility Pack 元件時,我們能用這樣的語法:
[XPath]
  1. /html[1]/body[1]/div[1]/div[2]/div[3]/div[2]/div[1]/div[1]/div[1]
就到達我們想要的地點,這個語法和 XPath 相當類似,對於熟悉 XPath 或是 DOM 的開發人員會比較有利。HTML Agility Pack 元件的類別階層和 XML DOM Parser 其實蠻像的,若先前有用過 XML DOM 的開發人員會覺得很熟悉:

HTML Agility Pack 元件的類別階層
如上面的說明,我們可以撰寫這樣的程式碼來讀取 W3C 首頁公布的最新消息的清單:
[C#]
  1. using HtmlAgilityPack;
  2.  
  3. public static void Main(string[] args)
  4. {
  5.     HtmlWeb webClient = new HtmlWeb();
  6.     HtmlDocument doc = webClient.Load("http://www.w3.org/");
  7.  
  8.     HtmlNodeCollection nodes = doc.DocumentNode.SelectNodes("/html[1]/body[1]/div[1]/div[2]/div[3]/div[2]/div[1]/div[1]/div[1]/div");
  9.  
  10.     foreach (HtmlNode node in nodes)
  11.     {
  12.         Console.WriteLine(node.InnerText.Trim());
  13.     }
  14.  
  15.     doc = null;
  16.     nodes = null;
  17.     webClient = null;
  18.  
  19.     Console.WriteLine("Completed.");
  20.     Console.ReadLine();
  21. }
讀取 W3C 首頁中最新公告的程式碼(專案類型:主控台應用程式)
HTML Agility Pack 只相依於 .NET Framework,因此不需要其他各種 HTML 剖析器的任何元件,只要有 .NET Framework 即可執行。

使用方式

若要使用 HTML Agility Pack 元件,可先上 Codeplex 的 HTML Agility Pack 網站下載二進位檔(同時也提供原始程式碼、說明檔以及 HAP Explorer 工具程式可下載),並解壓縮後,在專案加入對 HtmlAgilityPack.dll 的參考:
然後在程式宣告加入:
[C#]
  1. using HtmlAgilityPack;
即可在程式中使用 HTML Agility Pack 的功能。

應用範例:解析 Yahoo 奇摩股市的各檔股票資訊。

筆者認為這應該是很多撰寫股市資料收集的應用程式的主要標的,若是要由證交所取得資料授權可能要一筆費用,但是由 Yahoo 奇摩股市中解析並讀取資料是免費的,只是 Yahoo 奇摩股市的 HTML 結構長久以來都是鬆散型的,不像 W3C 是 XHTML(之前使用 HTML Agility Pack 讀取是要展示它的功能),所以要剖析它需要花不少腦力,現在我們可以使用 HTML Agility Pack 來將這個工作簡化。
在 Yahoo 奇摩股市中,一個個股的資訊是這樣:
而它的 HTML 結構則是:
因此如果我們要使用 XPath 來解讀它,則需要先使用下列 XPath 先到達上層 table 的位置,再往下取得 HTML 中的內容:
[XPath]
  1. /html[1]/body[1]/center[1]/table[2]/tr[1]/td[1]/table[1]
因此我們可以撰寫下列的程式碼:
[C#]
  1. using System.Net;
  2. using System.IO;
  3. using HtmlAgilityPack;
  4.  
  5. public static void Main(string[] args)
  6. {
  7.     // 下載 Yahoo 奇摩股市資料 (範例為 2317 鴻海)
  8.     WebClient client = new WebClient();
  9.     MemoryStream ms = new MemoryStream(client.DownloadData(
  10. "http://tw.stock.yahoo.com/q/q?s=2317"));
  11.  
  12.     // 使用預設編碼讀入 HTML
  13.     HtmlDocument doc = new HtmlDocument();
  14.     doc.Load(ms, Encoding.Default);
  15.  
  16.     // 裝載第一層查詢結果
  17.     HtmlDocument docStockContext = new HtmlDocument();
  18.  
  19.     docStockContext.LoadHtml(doc.DocumentNode.SelectSingleNode(
  20. "/html[1]/body[1]/center[1]/table[2]/tr[1]/td[1]/table[1]").InnerHtml);
  21.  
  22.     // 取得個股標頭
  23.     HtmlNodeCollection nodeHeaders =
  24.  docStockContext.DocumentNode.SelectNodes("./tr[1]/th");
  25.     // 取得個股數值
  26.     string[] values = docStockContext.DocumentNode.SelectSingleNode(
  27. "./tr[2]").InnerText.Trim().Split('\n');
  28.     int i = 0;
  29.  
  30.     // 輸出資料
  31.     foreach (HtmlNode nodeHeader in nodeHeaders)
  32.     {
  33.         Console.WriteLine("Header: {0}, Value: {1}"
  34. nodeHeader.InnerText, values[i].Trim());
  35.         i++;
  36.     }
  37.  
  38.     doc = null;
  39.     docStockContext = null;
  40.     client = null;
  41.     ms.Close();
  42.  
  43.     Console.WriteLine("Completed.");
  44.     Console.ReadLine();
  45. }
讀取 Yahoo 奇摩個股資料的程式碼(專案類型:主控台應用程式)

NOTE
目前 HTML Agility Pack 預設編碼應是法文編碼,所以如果是讀取中文 HTML 內容的話,無法直接使用 HtmlDocument.LoadHtml() 方法,而要透過 MemoryStream 使用 HtmlDocument.Load() 方法,才可以指定中文的編碼。

此範例的結果輸出為:
有了這個讀取程式後,筆者認為可以做的事就很多了,像是將它以 XSLT 轉換成不同的 HTML 來顯示,或是存到資料庫中做其他的工作(分析或是圖表輸出等都可以做到)。
由上列程式可以看出,HTML Agility Pack 的使用其實就和 XML DOM 的使用差不了多少,而且也不用再去寫不具親和力的 Regular Expression 的指令,就可以輕鬆的使用 XPath 來解析 HTML,就算是鬆散的 HTML 也可以解析。

NOTE
雖然 HTML Agility Pack 可以解析成功鬆散的 HTML 文件內容,但是請注意,鬆散的 HTML 內容可能會造成 HtmlNode 不是預期的結果,或是在解析表格時,檔頭和內容不同步的現象,這個部份開發人員可能需要多加注意。

NOTE
HTML Agility Pack 也可以用來動態產生 HTML 的內容,就如同 XmlDocument 產生 XML 文件內容一樣,它也有像是 CreateElement()、CreateAttribute()、CreateComments() 與 CreateTextNode() 等方法,其使用方式與 XmlDocument 不多,因此筆者在此就不贅述,請參考: http://msdn.microsoft.com/zh-tw/library/t058x2df.aspx

WARNING
本範例程式僅以 Yahoo 奇摩作為範例標的,實際上若要應用時請注意其內容授權合約與免責條款,筆者僅以此範例展示 HTML Agility Pack 的功能,並不代表使用此範例即等同獲得 Yahoo 奇摩的內容授權,若要引用則請注意是否有合法的內容授權,如因引用範例程式而導致侵權問題,一切責任由引用者自負

輔助工具

HTML Agility Pack 本身有提供一個 HAP Explorer 的工具,可以讓開發人員可以很快的得知要使用的 XPath 語法,以利開發人員利用它來解析 HTML 的位置資訊。
不過筆者認為這套工具太過於陽春(也許 Simon Mourier 對 Windows Forms 或 WPF 不熟),因此筆者建議配合像 FireFox 或是 IE8 所提供的開發者工具(Developer Tools)來自行建構 XPath 會比較適合,而且它們也都提供可以圈選網頁的某一個部份並標示其 HTML 位置的能力,這樣要建構 XPath 也會比較快。

沒有留言:

張貼留言