有時候我們會需要開發一些App是需要擷取網路上的一些資料,並且那些資料可能沒有提供方便的XML或Json的格式資料,這個時候我們便需要去解析(Parsing)網頁上HTML結構來取得HTML中的資料
在Java上有方便的Jsoup或apacheAPI提供的HTML Parser等工具方便我們快速完成取得想一的資料;那麼在C#上呢? 有沒有類似這樣專門提供、協助我們做HTML Parsing的工具?
有的。 它叫做HTML Agility Pack (可以點入此連結有相關詳細範例教學^.<)
而這邊我們以Windows Phone8( 以下簡稱WP8 )為範例,如果有打算在WP8上使用,但是不知道怎麼做的人,可以看看這篇^.<
前言
有時候我們會需要開發一些App是需要擷取網路上的一些資料,並且那些資料可能沒有提供方便的XML或Json的格式資料,這個時候我們便需要去解析(Parsing)網頁上HTML標籤與結構來取得HTML中的資料
在Java上有方便的Jsoup或apacheAPI提供的HTML Parser等工具方便我們快速完成取得想一的資料;那麼在C#上呢? 有沒有類似這樣專門提供、協助我們做HTML Parsing的工具?
有的。 它叫做HTML Agility Pack (可以點入此連結有相關詳細範例教學^.<)
而這邊我們以Windows Phone8( 以下簡稱WP8 )為範例,如果有打算在WP8上使用,但是不知道怎麼做的人,可以看看這篇^.<
加入HTML Agility Pack到專案
可以選擇使用NuGet的方式或是手動下載API的dll檔
1﹒去HTML Agility Pack 下載HAP 1.4.6
下載下來後解壓縮,並在專案中參考sl4-windowsphone71 (開發WP7也是選擇sl4-windowsphone71,但是完成下述的介紹後,仍是行不通,試試看使用sl3-wp資料夾中的dll檔,不過程式碼的部分可能會有點不一樣)
2﹒使用NuGet套件安裝
VS中工具->程式庫套件管理員->管理NuGet套件,會出現如下圖的視窗,此時在右邊的搜尋框打入Html就會出現下面的套件
HtmlAgilityPack,點選並安裝
如果你再開發WP8的時候,透過上述的方式安裝會顯示以下畫面:
則可以透過方法一來完成加入參考
此時透過上述的兩種方法之一,在完成後就會在你的專案參考中看到HtmlAgilityPack dll檔案
由於HtmlAgilityPack 是使用一種XPath的路徑語言方式來取得HTML中的階層標籤中的元素,所以在這邊我們需要引入XPath的dll檔
好家在,在.Net中的Silverlight中有提供此dll檔,於是我們可以在此參考她,在C槽路徑下的
ProgramFiles(x86)%\Microsoft SDKs\Microsoft SDKs\Silverlight\v4.0\Libraries\Client
或是
ProgramFiles%\Microsoft SDKs\Microsoft SDKs\Silverlight\v4.0\Libraries\Client
可以找到
這樣就準備完成囉!
這邊稍微提醒一下,如果你使用NuGet的方式安裝時,上述的做法雖然完成但是開始編寫執行後,若還是有出現問題,可以改用sl3-wp試試看(本人沒遇到這個問題,不過網路上以些人是使用sl3-wp才可以,但是程式語法可能會不太一樣唷!)
請手動移掉所參考的HtmlAgilityPack ,然後重新手動加入試試看sl3-wp的HtmlAgilityPack 的dll檔案(在專案底下會多出一個Package資料夾)
使用HTML Agility Pack Parsing
基本上不同的開發平台下,API中的使用會有稍微不同(例如官方範例是WPF,與在WP手機上的使用或是某些方法的命名就不一樣)
在這邊我們是以WP8為例,我要解析的網站範例的Yahoo的星座運勢(Yahoo->星座算命->星座),如下圖:
例如我要取得整體運中的這串文字,在他的標籤層級很多,如果要一個一個下會很麻煩,這個時候應用XPath的方式來解析的HtmlAgilityPack ,處理就很方便了。
先看我們如何取得這個網頁:
HtmlWeb client = new HtmlWeb();
client.LoadCompleted += client_LoadCompleted;
client.LoadAsync("http://astro.click108.com.tw/daily_10.php?iAstro=10");
上述的處理方式是先初始化HtmlAgilityPack 的HtmlWeb 類別(這邊在WP7的使用方式就有些不同,不過你也可以使用.Net提供的HttpWebRequest來取得網頁)
如果要同時處理多個網頁連結,因為是使用非同步的方式在處理,所以也可以塞入多個連結給他
HtmlWeb client = new HtmlWeb();
client.LoadCompleted += client_LoadCompleted;
client.LoadAsync("http://astro.click108.com.tw/daily_10.php?iAstro=10");
client.LoadAsync("http://astro.click108.com.tw/daily_9.php?iAstro=9");
client.LoadAsync("http://astro.click108.com.tw/daily_8.php?iAstro=8");
然後透過註冊事件,並使用非同步的方式把你要解析的網頁填入,之後當他找到網頁時,便會執行你註冊的事件處理方法
private void client_LoadCompleted(object sender, HtmlDocumentLoadCompleted e)
{
//Do parsing
}
現在我只要在此事件處理方法中寫上我要解析的程式碼即可,這邊我要解析上面那篇連結中的星座->水瓶座運勢中的星座名稱(也就是取得水瓶座名稱)與整體運勢
首先我們要先確定連線是否成功,在LoadCompleted方法中寫上下面此行
try
{
if (e.Error == null)
{
HtmlDocument doc = e.Document;
if (doc != null)
{
//Parsing
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
做好Try Catch,並檢查連線是否Error,若沒有問題,我們再取得此網頁的Document做後續的解析
Parsing
如果上述的部分沒有問題的話,再來就是拿到前面的Document開始解析HTML囉
首先是擷取星座的名稱(這邊也就是水瓶座)
HtmlNodeCollection nameNodes = doc.DocumentNode.SelectNodes(@"//div[@class='ROOT']/p/a[2]");
foreach (HtmlNode node in nameNodes)
{
string strValue = (node.InnerText).Substring(5); //擷取字串
Debug.WriteLine(strValue);
}
上述中唯一的重點的就SelectNodes方法中帶的語法-XPath路徑語法,這邊解釋一下(用name當作節點)
//name
表示我要選擇「所有」是name的節點 ->所以//div表示我要選擇所有帶有div的節點
name[@key='value']
指定name中帶有key = value的節點->//div[@class='ROOT'] 表示說我要找出div中,屬性帶有class='ROOT'的節點
name1/name2
代表name2是name1中的子節點,所以 -> //div[@class='ROOT']/p/a 表示我要找出所有div中屬性帶有class='ROOT'且存在子節點有p,p的子節點有a的位置
name[number]
number代表同階層中的第幾個name節點,如上面的例子,p/a[2] 表示我要尋找p的子節點中的第2個a的節點,我想要拿到第2個a節點的資料,此時我便可以打上a[2]來鎖定到位置(number是從1開始,不要想成陣列index從0開始唷!~)
如下圖是HTML的節點階層
因為前面提到//會拿取所有對應到我們下的XPath路徑的資料,所以可能會有多筆Node被我們找到,因此需要透過HtmlNodeCollection 類別來記錄所有找到的節點
然後一個個取出來。
再來我們要取得一開始提到的整體運勢(比對一開始的圖):
HtmlNodeCollection allFortuneContentNodes = doc.DocumentNode.SelectNodes(@"//div[@class='TODAY_CONTENT']/dt[@btype='all']/p");
foreach (HtmlNode node in allFortuneContentNodes)
{
string strValue = (node.InnerText);
Debug.WriteLine(strValue);
}
最後印出來的結果就會如下
Parsing的程式碼:
private void client_LoadCompleted(object sender, HtmlDocumentLoadCompleted e)
{
try
{
if (e.Error == null)
{
HtmlDocument doc = e.Document;
if (doc != null)
{
Debug.WriteLine("connect ok");
//Get Name
Debug.WriteLine("Name:");
HtmlNodeCollection nameNodes = doc.DocumentNode.SelectNodes(@"//div[@class='ROOT']/p/a[2]");
foreach (HtmlNode node in nameNodes)
{
string strValue = (node.InnerText).Substring(5);
Debug.WriteLine(strValue);
}
Debug.WriteLine("All:");
HtmlNodeCollection allFortuneContentNodes = doc.DocumentNode.SelectNodes(@"//div[@class='TODAY_CONTENT']/dt[@btype='all']/p");
foreach (HtmlNode node in allFortuneContentNodes)
{
string strValue = (node.InnerText);
Debug.WriteLine(strValue);
}
}
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
結論
基本上之前在使用時,不同平台用的,裡面的一些功能可能不存在,或者是說名稱有換掉,所以各位在使用時可能要多研究研究
這邊是一個使用在WP上的範例,也是給若要使用在WP上的人可以快速了解的一篇文章,希望有幫助到~
參考資料
Why can't I use htmlagilitypack with windows phone 8? What else can I use to Parse HTML in WP8?
Xpath 語法 - 使用 HtmlAgilityPack 於 C#
Windows Phone 開發 - 使用 Html Agility Pack 取得統一發票資訊
文章中的敘述如有觀念不正確錯誤的部分,歡迎告知指正 謝謝 =)
另外要轉載請附上出處 感謝