我最優惠網系列(1)——HTML 解析類庫HtmlAgilityPack


0. 序言

在開發我最優惠網的過程中,遇到一些問題和技術點,寫出來和大家分享,也是我自己對近期工作的整理和記錄,預計會有解析HTML類庫、本地緩存、鏈接跳轉和C#中執行js代碼技巧等方面。

 

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得到你想要的文檔中任意一個節點的信息。

 

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

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. 實戰

 基礎的都熟悉了,我們來做練習。例子就是在開發我最優惠網中實際使用的代碼,獲取什么值得買發現頻道的商品名稱、價格和詳情頁網址。代碼片段如下:

        static void Main(string[] args)
        {
            HtmlWeb htmlWeb = new HtmlWeb();
            HtmlDocument htmlDoc = htmlWeb.Load("http://faxian.smzdm.com");
            HtmlNode htmlNode = htmlDoc.DocumentNode.SelectSingleNode("//ul[@class='leftWrap discovery_list']");

            foreach (var li in htmlNode.SelectNodes("child::li"))
            {
                Console.WriteLine("名稱:" + li.SelectSingleNode("child::div[@class='listItem']/h2/a/span[1]").InnerText);
                Console.WriteLine("價格:" + li.SelectSingleNode("child::div[@class='listItem']/h2/a/span[2]").InnerText);
                Console.WriteLine("網址:" + li.SelectSingleNode("child::div[@class='listItem']/h2/a").GetAttributeValue("href", ""));
                Console.WriteLine("---------------------------------------------------------------------------");
                Thread.Sleep(1000);
            }
            Console.ReadLine();
        }    

  

代碼下載:http://pan.baidu.com/s/1pLkq6E3

 

5. 后記

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

最后再次打個廣告:網購前記得去我最優惠網查查最低價哦^_^

 

 

參考文檔:

http://www.cnblogs.com/oec2003/p/3322956.html

http://zhoufoxcn.blog.51cto.com/792419/595344/

http://www.cnblogs.com/kissdodog/archive/2013/02/28/2936950.html

http://www.tuicool.com/articles/YZ3uau

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM