1. 程式人生 > >HTML 解析類庫HtmlAgilityPack

HTML 解析類庫HtmlAgilityPack

html解析 類型 dht 好的 cts 布爾 repos 通過 節點

1. HtmlAgilityPack簡介

網站中首先遇到的問題是爬蟲和解析HTML的問題,一般情況在獲取頁面少量信息的情況下,我們可以使用正則來精確匹配目標。不過本身正則表達式就比較復雜,同時正則表達式的精確程度很難拿捏,太精確和原網頁耦合太嚴重,頁面代碼稍改動就會使正則無效;太寬泛的正則由可能會匹配目標過多。所以我們今天介紹的是通過解析HTML結構來獲取目標的方式——HtmlAgilityPack。

HtmlAgilityPack是一個解析HTML的類庫,支持用XPath來解析HTML,可以像XML一樣來解析HTML。

HtmlAgilityPack的代碼托管在codeplex上:http://htmlagilitypack.codeplex.com/,不過建議通過Nuget來獲取最新版本。

技術分享圖片

2. XPath簡介

XPath即為XML路徑語言,它是一種用來確定XML文檔中某部分位置的語言。XPath基於XML的樹狀結構,提供在數據結構樹中找尋節點的能力。下圖列舉了XPath主要的路徑表達式:

技術分享圖片

這種針對XML的路徑能在解析HTML中用的原因是HtmlAgilityPack將下載下來的HTML頁面進行規格化處理,讓原本對語義支持並不好的HTML文檔格式變為更嚴謹的Xhtml格式,甚至可以轉換為XML格式;並使用XPath來選擇、處理dom中的element。下圖表示HTML格式化之後的節點示意圖:

技術分享圖片

3. HtmlAgilityPack中常用的API

在HtmlAgilityPack中常用到的類有HtmlDocument、HtmlNodeCollection、HtmlNode和HtmlWeb。

首先是加載HTML,如果是已經存在的靜態HTML代碼,可以用HtmlDocument的Load()或LoadHtml()來加載,如果是網絡上的URL則需要用HtmlWeb的Get()或Load()方法來加載。

不管是哪種加載方式,我們得到的都是HtmlDocument的實例。此時我們需要得到的是HtmlNode或者HtmlNodeCollection對象,使用HtmlDocument的DocumentNode屬性,它整個HTML文檔的根節點,它本身也是一個HtmlNode。

得到文檔根節點後即可使用前一節介紹的XPath得到你想要的文檔中任意一個節點的信息。

下面是一個典型的獲得有效內容的例子:

1 2 3 4 HtmlWeb htmlWeb = new HtmlWeb(); HtmlDocument htmlDoc = htmlWeb.Load("http://www.baidu.com"); HtmlNode htmlNode = htmlDoc.DocumentNode.SelectSingleNode("//title"); string title = htmlNode.InnerText;

  

由於使用最多的類是HtmlNode,這裏將它常用的屬性和方法列在下面,方便各位查閱。

屬性:

Attributes              獲取節點的屬性集合
ChildNodes             獲取子節點集合(包括文本節點)
FirstChild             獲取第一個子節點
HasAttributes           判斷該節點是否含有屬性
HasChildNodes          判斷該節點是否含有子節點
Id                 獲取該節點的Id屬性
InnerHtml             獲取該節點的Html代碼
InnerText             獲取該節點的內容,與InnerHtml不同的地方在於它會過濾掉Html代碼,而InnerHtml是連Html代碼一起輸出
LastChild             獲取最後一個子節點
Name               Html元素名
NextSibling            獲取下一個兄弟節點
ParentNode            獲取該節點的父節點
PreviousSibling          獲取前一個兄弟節點
XPath               根據節點返回該節點的XPath

方法:

HtmlNode AppendChild(HtmlNode newChild);             將參數元素追加到為調用元素的子元素(追加在最後)
void AppendChildren(HtmlNodeCollection newChildren);       將參數集合中的元素追加為調用元素的子元素(追加在最後)
HtmlNode PrependChild(HtmlNode newChild);            將參數中的元素作為子元素,放在調用元素的最前面
void PrependChildren(HtmlNodeCollection newChildren);       將參數集合中的所有元素作為子元素,放在調用元素前面
HtmlNode Clone();                           本節點克隆到一個新的節點
HtmlNode CloneNode(bool deep);                   節點克隆到一個新的幾點,參數確定是否連子元素一起克隆
HtmlNode CloneNode(string newName);                克隆的同時更改元素名
HtmlNode CloneNode(string newName, bool deep);          克隆的同時更改元素名。參數確定是否連子元素一起克隆
void CopyFrom(HtmlNode node);                    創建重復的節點和其下的子樹。
void CopyFrom(HtmlNode node, bool deep);              創建節點的副本。
static HtmlNode CreateNode(string html);               靜態方法,允許用字符串創建一個新節點
IEnumerable<HtmlNode> DescendantNodes();             獲取所有子代節點
IEnumerable<HtmlNode> DescendantNodesAndSelf();         獲取所有的子代節點以及自身
IEnumerable<HtmlNode> Descendants();               獲取枚舉列表中的所有子代節點
IEnumerable<HtmlNode> Descendants(string name);         獲取枚舉列表中的所有子代節點,註意元素名要與參數匹配
IEnumerable<HtmlNode> DescendantsAndSelf();           獲取枚舉列表中的所有子代節點以及自身
IEnumerable<HtmlNode> DescendantsAndSelf(string name);     獲取枚舉列表中的所有子代節點以及自身,註意元素名要與參數匹配
HtmlNode Element(string name);                   根據參數名獲取一個元素
IEnumerable<HtmlNode> Elements(string name);          根據參數名獲取匹配的元素集合
bool GetAttributeValue(string name, bool def);             幫助方法,用來獲取此節點的屬性的值(布爾類型)。如果未找到該屬性,則將返回默認值。
int GetAttributeValue(string name, int def);              幫助方法,用來獲取此節點的屬性的值(整型)。如果未找到該屬性,則將返回默認值。
string GetAttributeValue(string name, string def);          幫助方法,用來獲取此節點的屬性的值(字符串類型)。如果未找到該屬性,則將返回默認值。
HtmlNode InsertAfter(HtmlNode newChild, HtmlNode refChild);    將一個節點插入到第二個參數節點的後面,與第二個參數是兄弟關系
HtmlNode InsertBefore(HtmlNode newChild, HtmlNode refChild);   講一個節點插入到第二個參數節點的後面,與第二個參數是兄弟關系
static bool IsCDataElement(string name);                確定是否一個元素節點是一個 CDATA 元素節點。
static bool IsClosedElement(string name);               確定是否封閉的元素節點
static bool IsEmptyElement(string name);               確定是否一個空的元素節點。
static bool IsOverlappedClosingElement(string text);           確定是否文本對應於一個節點可以保留重疊的結束標記。
void Remove();                              從父集合中移除調用節點
void RemoveAll();                            移除調用節點的所有子節點以及屬性
void RemoveAllChildren();                        移除調用節點的所有子節點
HtmlNode RemoveChild(HtmlNode oldChild);              移除調用節點的指定名字的子節點
HtmlNode RemoveChild(HtmlNode oldChild, bool keepGrandChildren);移除調用節點調用名字的子節點,第二個參數確定是否連孫子節點一起移除
HtmlNode ReplaceChild(HtmlNode newChild, HtmlNode oldChild);   將調用節點原有的一個子節點替換為一個新的節點,第二個參數是舊節點
HtmlNodeCollection SelectNodes(string xpath);            根據XPath獲取一個節點集合
HtmlNode SelectSingleNode(string xpath);               根據XPath獲取唯一的一個節點
HtmlAttribute SetAttributeValue(string name, string value);       設置調用節點的屬性
string WriteContentTo();                        將該節點的所有子級都保存到一個字符串中。
void WriteContentTo(TextWriter outText);                將該節點的所有子級都保存到指定的 TextWriter。
string WriteTo();                            將當前節點保存到一個字符串中。
void WriteTo(TextWriter outText);                   將當前節點保存到指定的 TextWriter。
void WriteTo(XmlWriter writer);                    將當前節點保存到指定的則 XmlWriter。

4. 實戰

基礎的都熟悉了,我們來做練習。代碼片段如下:

 1  HtmlDocument doc = new HtmlDocument();
 2                 doc.LoadHtml(html);//加載html
 3                 string pageNumberPath = @"//*[@id=‘J_topPage‘]/span/i";
 4                 HtmlNode pageNumberNode = doc.DocumentNode.SelectSingleNode(pageNumberPath);
 5                 if (pageNumberNode != null)
 6                 {
 7                     string sNumber = pageNumberNode.InnerText;
 8                     for (int i = 1; i < int.Parse(sNumber) + 1; i++)
 9                     {
10                         string pageUrl = string.Format("{0}&page={1}", category.Url, i);
11                         try
12                         {
13                             List<Commodity> commodityList = GetCommodityList(category, pageUrl.Replace("&page=1&", string.Format("&page={0}&", i)));
14                             commodityRepository.SaveList(commodityList);
15                         }
16                         catch (Exception ex)//保證一頁的錯誤不影響另外一頁
17                         {
18                             logger.Error("Crawler的commodityRepository.SaveList(commodityList)出現異常", ex);
19                         }
20                     }

  

5. 後記

HtmlAgilityPack 的確是一個功能強大的HTML解析類庫,我目前僅僅使用了它的一小部分功能,但是已經能完全滿足我的需求。如果童鞋們有類似需求,可以試試。

HTML 解析類庫HtmlAgilityPack