做開發的,可能都做過信息采集相關的程序,史林楓也經常做一些數據采集或某些網站的業務辦理自動化操作軟件。
獲取目標網頁的信息很簡單,使用網絡編程,利用HttpWebResponse、HttpWebRequest和WebClient就可以了。
復雜的是獲取網頁內容後,需要對關鍵信息進行過濾,最初史林楓主要使用正則表達式來匹配目標數據。
這樣的匹配也能達到目的,但對於不熟悉正則表達式的開發者或初學者就比較吃力了,尤其是比較復雜的正則。
最好要有專門的工具先測試,再把正則放到程序中測試。這裡推薦RegexTester.exe。
後來,一次偶然的機會接觸到HtmlAgilityPack。這是個開源的類庫。想研究源碼的可以移步這裡:HtmlAgilityPack源碼
剛開始使用是比較隨性的,需要用了就開始new 然後找跟節點,找目標節點,取屬性或取文本。使用的多了,就有封裝類庫的想法,然後在使用過程中不斷改進,更新,目前使用還是比較穩定的。
使用的時候需要引用HtmlAgilityPack.dll Visual Studio中的NuGet可以獲取到
先上類庫源碼:
1 /// <summary> 2 /// html文檔解析輔助類庫 3 /// </summary> 4 public class HtmlParse { 5 private readonly HtmlDocument doc = new HtmlDocument(); 6 7 /// <summary> 8 /// 構造函數 初始化文檔並解析 默認utf-8模式 9 /// </summary> 10 /// <param name="htmlOrUrl">獲取的html字符串或url鏈接</param> 11 public HtmlParse(string htmlOrUrl) { 12 InitDoc(htmlOrUrl); 13 } 14 15 /// <summary> 16 /// 構造函數 初始化文檔並解析 默認utf-8模式 17 /// </summary> 18 /// <param name="htmlOrUrl">獲取的html字符串或url鏈接</param> 19 /// <param name="encode">字符編碼</param> 20 public HtmlParse(string htmlOrUrl, string encode) { 21 InitDoc(htmlOrUrl, encode); 22 } 23 24 25 /// <summary> 26 /// 根據url或html字符串獲取文檔並解析 27 /// </summary> 28 /// <param name="htmlOrUrl">html字符串或url</param> 29 /// <param name="encode">網站編碼</param> 30 /// <returns></returns> 31 public HtmlDocument InitDoc(string htmlOrUrl, string encode = "utf-8") { 32 if (htmlOrUrl.Trim().StartsWith("http")) { 33 htmlOrUrl = NetHelper.GetPageStr(htmlOrUrl, "", encode); 34 } 35 doc.LoadHtml(htmlOrUrl); 36 return doc; 37 } 38 39 /// <summary> 40 /// 獲取節點集合 41 /// </summary> 42 /// <param name="xPath"></param> 43 /// <returns></returns> 44 public HtmlNodeCollection GetNodes(string xPath) { 45 return doc.DocumentNode.SelectNodes(xPath); 46 } 47 48 49 /// <summary> 50 /// 獲取單個節點 51 /// </summary> 52 /// <param name="xPath"></param> 53 /// <returns></returns> 54 public HtmlNode GetNode(string xPath) { 55 return doc.DocumentNode.SelectSingleNode(xPath); 56 } 57 58 /// <summary> 59 /// 獲取節點的屬性值 60 /// </summary> 61 /// <param name="node">節點</param> 62 /// <param name="attrName">屬性名稱</param> 63 /// <returns></returns> 64 public string GetNodeAttr(HtmlNode node, string attrName) { 65 if (node == null || node.Attributes[attrName] == null) { 66 return string.Empty; 67 } 68 return node.Attributes[attrName].Value; 69 } 70 71 /// <summary> 72 /// 獲取節點的InnerText的值 73 /// </summary> 74 /// <param name="node"></param> 75 /// <returns></returns> 76 public string GetNodeText(HtmlNode node) { 77 if (node == null) { 78 return string.Empty; 79 } 80 return node.InnerText; 81 } 82 83 /// <summary> 84 /// 獲取節點的InnerHtml或OuterHtml值 85 /// </summary> 86 /// <param name="node">節點</param> 87 /// <param name="isOuter">是否要獲取OuterHtml</param> 88 /// <returns></returns> 89 public string GetNodeHtml(HtmlNode node, bool isOuter = false) { 90 if (node == null) { 91 return string.Empty; 92 } 93 if (isOuter) { 94 return node.OuterHtml; 95 } 96 return node.InnerHtml; 97 } 98 99 /// <summary> 100 /// 根據Xpath和屬性名稱獲取屬性值 101 /// </summary> 102 /// <param name="xPath"></param> 103 /// <param name="attrName"></param> 104 /// <returns></returns> 105 public string GetNodeAttr(string xPath, string attrName) { 106 var node = GetNode(xPath); 107 return GetNodeAttr(node, attrName); 108 } 109 110 /// <summary> 111 /// 根據XPath獲取節點的InnerText 112 /// </summary> 113 /// <param name="xPath"></param> 114 /// <returns></returns> 115 public string GetNodeText(string xPath) { 116 var node = GetNode(xPath); 117 return GetNodeText(node); 118 } 119 120 /// <summary> 121 /// 根據XPath獲取節點的InnerHtml或OuterHtml值 122 /// </summary> 123 /// <param name="xPath"></param> 124 /// <param name="isOuter"></param> 125 /// <returns></returns> 126 public string GetNodeHtml(string xPath, bool isOuter = false) { 127 var node = GetNode(xPath); 128 return GetNodeHtml(node); 129 } 130 }
提示:想要熟練的使用HtmlAgilityPack,必須要了解XPath的相關知識。不懂的可以移步這裡:XPath入門教程
實際上XPath主要注意幾個要點就可以解決80%的問題。
1.以/開頭的是從根節點開始選取,以//開頭的是模糊選取,而不考慮它們的位置
2.可以使用屬性來定位要選取的節點或節點集合 比如//span[@class="time"] 就是選擇文檔中所有class="time"的span元素。
3.節點集合中的某一個使用[i]的方式選取 比如 //span[@class="time"][1] 就是選擇文檔中所有class="time"的span元素中的第一個span。注意在這裡選擇節點的索引是從1開始的,而不是0
4.使用| 來做容錯選擇,比如一個網頁中某個數據可能在<div class="a1"></div>中 也可能在<div class="a2"></div> 這時就可以用 //div[@class="a1"]|//div[@class="a2"] 作為XPath
5.XPath中需要用到的引號 可以使用單引號 因為C#中字符串需要用雙引號,XPath中需要引號的使用單引號即可,這樣不用轉義了。
上面還用到了一個NetHelper。主要用於獲取Url的內容。這東西網上一大堆,這裡就不獻丑了。自行結合即可。
使用方法也很簡單:
// 比如這裡獲取我的博客首頁內容 並解析當前文章列表
var doc = new HtmlParse("http://www.cnblogs.com/jayshsoft/"); var nodeList = doc.GetNodes("//div[@class='post post-list-item']"); foreach (var node in nodeList) { //這裡寫自己的邏輯 }
自從封裝好類庫後,采集內容就變得非常Easy了,只要把流程分析好即可,Html文章中的元素任你宰割,蹂躏。。。
另外,推薦一個FireFox浏覽器插件:XPath Checker
有了它 你就可以在浏覽器中直接寫好XPath,直接看到結果 一目了然
直接上圖
右鍵點擊網頁空白處 選擇View XPath
輸入XPath 就可以得到你想要的數據了。是不是很直觀?是就推薦一下 嘿嘿。。。