在開發我最優惠網的過程當中,遇到一些問題和技術點,寫出來和你們分享,也是我本身對近期工做的整理和記錄,預計會有解析HTML類庫、本地緩存、連接跳轉和C#中執行js代碼技巧等方面。html
網站中首先遇到的問題是爬蟲和解析HTML的問題,通常狀況在獲取頁面少許信息的狀況下,咱們可使用正則來精確匹配目標。不過自己正則表達式就比較複雜,同時正則表達式的精確程度很難拿捏,太精確和原網頁耦合太嚴重,頁面代碼稍改動就會使正則無效;太寬泛的正則由可能會匹配目標過多。因此咱們今天介紹的是經過解析HTML結構來獲取目標的方式——HtmlAgilityPack。node
HtmlAgilityPack是一個解析HTML的類庫,支持用XPath來解析HTML,能夠像XML同樣來解析HTML。正則表達式
HtmlAgilityPack的代碼託管在codeplex上:http://htmlagilitypack.codeplex.com/,不過建議經過Nuget來獲取最新版本。緩存
XPath即爲XML路徑語言,它是一種用來肯定XML文檔中某部分位置的語言。XPath基於XML的樹狀結構,提供在數據結構樹中找尋節點的能力。下圖列舉了XPath主要的路徑表達式:網絡
這種針對XML的路徑能在解析HTML中用的緣由是HtmlAgilityPack將下載下來的HTML頁面進行規格化處理,讓本來對語義支持並很差的HTML文檔格式變爲更嚴謹的Xhtml格式,甚至能夠轉換爲XML格式;並使用XPath來選擇、處理dom中的element。下圖表示HTML格式化以後的節點示意圖:數據結構
在HtmlAgilityPack中經常使用到的類有HtmlDocument、HtmlNodeCollection、HtmlNode和HtmlWeb。app
首先是加載HTML,若是是已經存在的靜態HTML代碼,能夠用HtmlDocument的Load()或LoadHtml()來加載,若是是網絡上的URL則須要用HtmlWeb的Get()或Load()方法來加載。dom
不論是哪一種加載方式,咱們獲得的都是HtmlDocument的實例。此時咱們須要獲得的是HtmlNode或者HtmlNodeCollection對象,使用HtmlDocument的DocumentNode屬性,它整個HTML文檔的根節點,它自己也是一個HtmlNode。網站
獲得文檔根節點後便可使用前一節介紹的XPath獲得你想要的文檔中任意一個節點的信息。ui
下面是一個典型的得到有效內容的例子:
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。
基礎的都熟悉了,咱們來作練習。例子就是在開發我最優惠網中實際使用的代碼,獲取什麼值得買發現頻道的商品名稱、價格和詳情頁網址。代碼片斷以下:
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
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