在前幾篇文章中([搜房網房產資料採集程式demo--GeckoWebBrowser例項] )都有提到一個解析html的C#類庫HtmlAgilityPack,
今天終於有時間整理一下,並把Demo分享一下。
HtmlAgilityPack是一個基於.Net的、第三方免費開源的微型類庫,主要用於在伺服器端解析html文件(在B/S結構的程式中客戶端可以用Javascript、jquery解析html)。截止到本文發表時,HtmlAgilityPack的最新版本為 1.4.6。下載地址:http://htmlagilitypack.codeplex.com/。最新版本支援Linq to Objects ( LINQ to Xml ).
準備:
如果你有安裝Nuget的話,可以直接查詢安裝即可。
下載後解壓縮後有3個檔案,這裡只需要將其中的HtmlAgilityPack.dll(程式集)、HtmlAgilityPack.xml(文件,用於Visual Studio 2008中程式碼智慧提示和幫助說明之用)引入解決方案中即可使用,無需安裝任何東西,非常好用。
在C#類檔案開頭引入using HtmlAgilityPack;就可以使用該名稱空間下的型別了。實際使用中,幾乎都是以HtmlDocument類為主線的,這一點非常類似於微軟.net framework中的XmlDocument類。XmlDocument類是操作的是xml文件,而HtmlDocument類操作的是html文件(其實也可以操作xml文件),它們的操作方式都是基於Dom,所不同的是後者取消了諸如GetElementsByTagName這樣的方法,強化了GetElementById方法(在HtmlDocument中可以直接使用,而XmlDocument則不可以)。
HtmlAgilityPack中定位節點基本上都用Xpath表示式,Xpath表示式的參考文件可見:http://www.w3school.com.cn/xpath/xpath_syntax.asp。自行學習。
不過可以先用幾個簡單就可以。比如,我們用到最多可能就是針對某個元素(div)、或者某個class屬性的div、或者某個id的div,或者以什麼開頭的div,
類似這樣的Xpath還是比較簡單的。
Xpath舉幾個例子,下面的程式碼中我們就會用到:
"//comment()"在XPath中表示“所有註釋節點” 1、獲取網頁title:doc.DocumentNode.SelectSingleNode("//title").InnerText; 解釋:XPath中“//title”表示所有title節點。SelectSingleNode用於獲取滿足條件的唯一的節點。 2、獲取所有的超連結:doc.DocumentNode.Descendants("a") 3、獲取name為kw的input,也就是相當於getElementsByName(): var kwBox = doc.DocumentNode.SelectSingleNode("//input[@name='kw']"); 解釋:"//input[@name='kw']"也是XPath的語法,表示:name屬性等於kw的input標籤。 //li/h3/a[@href]:所有li下面的h3包含a超級連結有href屬性才符合。有的a可能是支援的js事件 //div[starts-with(@class,'content_single')]:所有符合條件的div,並且它的class是由字串content_single 開頭的。 |
//標示獲取documet下的所有符合條件。/div標示從根目錄開始的符合條件的。
以上是準備工作。下面說一下HtmlAgilityPack讀取web頁面,並解析的方法步驟。
1.讀取url:
HtmlAgilityPack.HtmlWeb hw = new HtmlAgilityPack.HtmlWeb(); HtmlAgilityPack.HtmlDocument doccc = hw.Load(url);//是你需要解析的url ArrayList ImagePtahs = GetHrefs(doccc); |
這裡可能會遇到2個問題。
一個是編碼問題,一個是gzip不支援的問題。
首先編碼問題解決辦法:就是不用HtmlAgilityPack去獲取Url的data資料,自己獲取了。大家可能就問了:我自己獲取了他不給我解析那?
沒事,他不會那麼笨的。誰的肉不是吃啊?
方法如下:
WebProxy proxyObject = new WebProxy(IP, port);//這裡我是用的代理。 //向指定地址傳送請求 HttpWebRequest HttpWReq = (HttpWebRequest)WebRequest.Create(url); HttpWReq.Proxy = proxyObject; HttpWReq.Timeout = 10000; HttpWebResponse HttpWResp = (HttpWebResponse)HttpWReq.GetResponse(); StreamReader sr = new StreamReader(HttpWResp.GetResponseStream(), System.Text.Encoding.GetEncoding("UTF-8")); //注意上面的編碼了嗎?? HtmlAgilityPack.HtmlDocument doc = new HtmlAgilityPack.HtmlDocument(); doc.Load(sr); int res = CheckIsGoodProxy(doc); //這是我解析的函式,還沒到那一步。不解釋了。 sr.Close(); HttpWResp.Close(); HttpWReq.Abort(); |
另一個問題就是很奇怪了。gzip的問題開啟了gzip壓縮的網頁請求時會報錯。報錯資訊為“gzip”不是受支援的編碼名。
在谷歌上搜尋了半天,終於找到解決方案,而且不必更換HttpRequest或WebClient進行請求。同時還可以用此方法設定cookie,render偽裝等等。。。
解決後程式碼如下:在你發起請求的是修改一下。
HtmlWeb webClient = new HtmlWeb(); HtmlAgilityPack.HtmlWeb.PreRequestHandler handler = delegate(HttpWebRequest request) { request.Headers[HttpRequestHeader.AcceptEncoding] = "gzip, deflate"; request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip; request.CookieContainer = new System.Net.CookieContainer(); return true; }; webClient.PreRequest += handler; HtmlDocument doc = webClient.Load(this.getUrl()); |
可能最新版本的HtmlAgilityPack會修復這個問題吧。期待中。
2.用Xpath解析。
這一步就比較簡單了。就用Xpath選出你想要的資料,遍歷他們,取出他們的value即可。
例項程式碼:
private ArrayList GetHrefs(HtmlAgilityPack.HtmlDocument _doc) { try { Images = new ArrayList(); HtmlNodeCollection hrefs = _doc.DocumentNode.SelectNodes("//li/h3/a[@href]"); HtmlNodeCollection hrefs2 = _doc.DocumentNode.SelectNodes("//div[starts-with(@class,'content_single')]"); if (hrefs == null) return new ArrayList(); foreach (HtmlNode href in hrefs) { // Images.Add(href.Attributes["src"].Value); string hreff = href.Attributes["href"].Value;// 排除 博海拾貝第二百零二期】吃完薯條寂寞了 string title = href.Attributes["title"].Value; if (title.IndexOf("邪惡") >= 0) { continue; } if (title.IndexOf("惡搞") >= 0) { continue; } if (title.IndexOf("雷人") >= 0) { continue; } ///執行資料儲存的邏輯 } } catch (Exception ex) { ShowLogMsg("出錯了:"+ex.Message+ex.StackTrace); return new ArrayList(); } }
|
每一個Htmlnode,你要獲取他的資料用這個方法: img.Attributes["src"].Value