/Articles/Article[last()]:選取屬於Articles子元素的最後一個Article元素。
/Articles/Article[last()-1]:選取屬於Articles子元素的倒數第二個Article元素。
/Articles/Article[position()<3]:選取最前面的兩個屬於 bookstore 元素的子元素的Article元素。
//title[@lang]:選取全部擁有名爲lang的屬性的title元素。
//CreateAt[@type='zh-cn']:選取全部CreateAt元素,且這些元素擁有值爲zh-cn的type屬性。
/Articles/Article[Order>2]:選取Articles元素的全部Article元素,且其中的Order元素的值須大於2。
/Articles/Article[Order<3]/Title:選取Articles元素中的Article元素的全部Title元素,且其中的Order元素的值須小於3。javascript
剛剛學習了XPath路徑表達式,主要是對XML文檔中的節點進行搜索,經過XPath表達式能夠對XML文檔中的節點位置進行快速定位和訪問,html也是也是一種相似於xml的標記語言,可是語法沒有那麼嚴謹,在codeplex裏有一個開源項目HtmlAgilityPack,提供了用XPath解析HTML文件,下面掩飾如何使用該類庫的使用
css
首先說下XPath路徑表達式html
XPath路徑表達式java
用來選取XML文檔中的節點或節點集的node
一、術語:節點(Node):7種類型:元素,屬性,文本,命名空間,處理命令,註釋,文檔(根)節點app
二、節點關係:父(Parent),子(Children),同胞(Sibling),先輩(Ancestor),後代(Descendant)函數
三、路徑表達式post
nodename 節點名,選取此節點的全部子節點 例: childnode 當前節點中的childnode子節點,不包含孫子及如下的節點學習
/ 從根節點選取 例:/root/childnode/grandsonnode 測試
// 表示全部後代節點 例://childnode 全部名爲childnode的後代節點
. 表示當前節點 例: ./childnode 表示當前節點的childnode節點
.. 表示父節點 例: ../nearnode 表示父親節點的nearnode子節點
@ 選取屬性 /root/childnode/@id 表示childnode的全部含有id屬性的節點集
四、謂語(Predicates)
謂語能夠對節點集進行一些限制,使選擇更精確
/root/book[1] 節點集中的第一個節點
/root/book[last()] 節點集中最後一個節點
/root/book[position() - 1] 節點集中倒數第二個節點集
/root/book[position() < 5] 節點集中前五個節點集
/root/book[@id] 節點集中含有屬性id的節點集
/root/book[@id='chinese'] 節點集中id屬性值爲chinese的節點集
/root/book[price > 35]/title 節點集中book的price元素值大於35的title節點集
五、通配符:XPath路徑中一樣支持通配符(*,@*,node(), text())
例: /bookstore/*
//title[@*]
六、XPath軸
定義相對於當前節點的節點集
ancestor 全部祖先節點
//刪除註釋,script,style node.Descendants() .Where(n => n.Name == "script" || n.Name == "style" || n.Name=="#comment") .ToList().ForEach(n => n.Remove()); //遍歷node節點的全部後代節點 foreach(var HtmlNode in node.Descendants()) { }
attribute 全部屬性節點
child 全部子元素
descendant 全部後代節點(子,孫。。。)
following 結束標記後的全部節點 preceding 開始標記前的全部節點
following-sibling 結束標記後的全部同胞節點
preceding-sibling 開始標記前的全部同胞節點
namespace 當前命名空間的全部節點
parent 父節點
self 當前節點
用法:軸名稱::節點測試[謂語]
例: ancestor::book
child::text()
七、運算符
| 兩個節點集的合併 例:/root/book[1] | /root/book[3]
+,-,*,dev,mod
=,!=,<,>,<=,>=
or,and 或和與
補充:
多個屬性條件查詢 //div[@align='center' and @height='24']
不存在class屬性 //div[not(@class)]
static void Main(string[] args) { //<ul class="user_match clear"> // <li>年齡:21~30之間</li> // <li>婚史:未婚</li> // <li>地區:不限</li> // <li>身高:175~185釐米之間</li> // <li>學歷:不限</li> // <li>職業:不限</li> // <li>月薪:不限</li> // <li>住房:不限</li> // <li>購車:不限</li> //</ul> WebClient wc = new WebClient(); wc.BaseAddress = "http://www.juedui100.com/"; wc.Encoding = Encoding.UTF8; HtmlDocument doc = new HtmlDocument(); string html = wc.DownloadString("user/6971070.html"); doc.LoadHtml(html); HtmlNode node = doc.DocumentNode.SelectSingleNode("/html/body/div[4]/div[1]/div[2]/ul[1]"); //根據XPath查找節點,跟XmlNode差很少 IEnumerable<HtmlNode> nodeList = node.Ancestors(); //獲取該元素全部的父節點的集合 foreach (HtmlNode item in nodeList) { Console.Write(item.Name + " "); //輸出 div div body html #document } Console.WriteLine(); IEnumerable<HtmlNode> nodeList1 = node.Ancestors("body"); //獲取名字匹配的該元素的父集合,其實參數就是一個篩選的功能 foreach (HtmlNode item in nodeList1) { Console.Write(item.Name + " "); //輸出 body } Console.WriteLine(); IEnumerable<HtmlNode> nodeList2 = node.AncestorsAndSelf(); //獲取全部的父節點和自身 foreach (HtmlNode item in nodeList2) { Console.Write(item.Name + " "); //輸出 ul div div div body html #document } Console.WriteLine(); IEnumerable<HtmlNode> nodeList3 = node.AncestorsAndSelf("div"); //獲取父節點和自身,參數用於篩選 foreach (HtmlNode item in nodeList3) { Console.Write(item.Name + " "); //輸出 div div div } Console.WriteLine(); HtmlNode node1 = doc.CreateElement("li"); node1.InnerHtml = "我是附加的li元素"; node.AppendChild(node1); //...<li>購車:不限</li> 後面加了一個<li>我是附加的li元素</li> Console.WriteLine(node.InnerHtml); HtmlNode node2 = doc.CreateElement("li"); node2.InnerHtml = "新li一"; HtmlNode node3 = doc.CreateElement("li"); node3.InnerHtml = "新li二"; HtmlNodeCollection nc = new HtmlNodeCollection(node2); nc.Add(node2); nc.Add(node3); node.AppendChildren(nc); //一次過追加多個元素 Console.WriteLine(node.InnerHtml); //...<li>我是附加的li元素</li><li>新li一</li><li>新li二</li> Console.WriteLine(HtmlNode.CanOverlapElement("node2")); //輸出False 肯定是否能夠保存一個重複的元素 IEnumerable<HtmlAttribute> attrs = node.ChildAttributes("class"); //獲取子節點與自身的全部名爲class的屬性集合 foreach (HtmlAttribute attr in attrs) { Console.Write(attr.Value); //輸出 user_match clear } HtmlNode node4 = node.Clone(); Console.WriteLine(node4.InnerHtml); //輸出node的代碼,node已被複制到了node HtmlNode node5 = node.CloneNode(false); //參數決定是否複製子節點,與XmlNode同樣 Console.WriteLine(node5.OuterHtml); //<ul class="user_match clear"></ul> 由於參數設爲了false子節點沒有被複制 HtmlNode node6 = node.CloneNode("div"); //複製節點的同時,更更名字 Console.WriteLine(node6.OuterHtml); //輸出 <div class="user_match clear"><li>年齡:21~30之間</li>...</div> ul已被改成了div HtmlNode node7 = node.CloneNode("table",false); Console.WriteLine(node7.OuterHtml); //輸出<table class="user_match clear"></table> 參數爲false因此沒有複製子節點 HtmlNode node8 = node.SelectSingleNode("child::li[1]"); node.CopyFrom(node); Console.WriteLine(node.OuterHtml); Console.WriteLine("========================"); //public void CopyFrom(HtmlNode node); //public void CopyFrom(HtmlNode node, bool deep); //public XPathNavigator CreateNavigator(); //public XPathNavigator CreateRootNavigator(); HtmlNode node9 = HtmlNode.CreateNode("<li>新節點</li>"); //直接用字符串建立節點,仍是挺好用的 Console.WriteLine(node9.OuterHtml); //輸出 <li>新節點</li> IEnumerable<HtmlNode> nodeList4 = node.DescendantNodes(); //獲取全部的子節點集合 foreach (HtmlNode item in nodeList4) { Console.Write(item.OuterHtml); //輸出 node的每一個子li節點 } Console.WriteLine("==================="); IEnumerable<HtmlNode> nodeList5 = node.DescendantNodesAndSelf(); foreach (HtmlNode item in nodeList5) { Console.Write(item.OuterHtml); //輸出自身<ul>..包括子節點<li>...</li></ul> 再輸出全部的子li節點 } Console.WriteLine(); IEnumerable<HtmlNode> nodeList6 = node.DescendantNodes(); //獲取枚舉列表中的全部子代節 foreach (HtmlNode item in nodeList6) { Console.Write(item.InnerText); //輸出全部的li節點的內容 } Console.WriteLine("---------------"); IEnumerable<HtmlNode> nodeList7 = node.Descendants("li"); //獲取全部的子後代元素 //文本節點不在此範圍內 foreach(HtmlNode item in nodeList7) { Console.Write(item.InnerText); } IEnumerable<HtmlNode> nodeList8 = node.DescendantsAndSelf("ul"); //獲取全部的子後代元素 //文本節點不在此範圍內 foreach (HtmlNode item in nodeList8) { Console.Write(item.Name); //輸出 ul 參數實際上只至關於過濾的做用 } HtmlNode node10 = node.Element("li"); //獲取第一個子節點名稱匹配的元素 Console.WriteLine(node10.InnerText); //輸出 年齡:年齡:21~30之間 Console.WriteLine("----------------------------------------"); IEnumerable<HtmlNode> nodeList9 = node.Elements("li"); foreach (HtmlNode item in nodeList9) { Console.Write(item.InnerText); //輸出 全部的li節點內容 } Console.WriteLine(); //換一個新的,好像有點亂了 HtmlNode newnode = doc.DocumentNode.SelectSingleNode("/html/body/div[4]/div[1]/div[3]"); //<div class="col say"> // <h3>愛情獨白</h3> // <p>願得一心人,白首不相離。我一直相信個人另外一半就在茫茫人海中,有一天必定會與我相遇。</p> //</div> //bool b = newnode.GetAttributeValue("class", false); //獲取一個布爾值的屬性,沒有找到則返回第二個參數的默認值 //Console.WriteLine(b); //int i = newnode.GetAttributeValue("class", 0); //獲取一個整形的屬性,沒有找到則返回第二個參數的默認值 //Console.WriteLine(i); string str = newnode.GetAttributeValue("class", ""); //獲取一個字符串屬性 Console.WriteLine(str); //輸出 col say HtmlNode node11 = HtmlNode.CreateNode("<b>我是加粗節點</b>"); HtmlNode node12 = newnode.SelectSingleNode("h3"); newnode.InsertAfter(node11, node12); //意思是在node12表明的h3節點後面插入node11節點 Console.WriteLine(newnode.InnerHtml); //h3>愛情獨白</h3><b>我是加粗節點</b><p>願得一心人... 留意到b節點已經被插入到h3後面 newnode.InsertBefore(node11, node12); //再插入多一次,方法不一樣罷了,此次是在node12帶包的h3前面插入 Console.WriteLine(newnode.InnerHtml); //<b>我是加粗節點</b><h3>愛情獨白</h3><b>我是加粗節點</b><p>願得一心人 Console.WriteLine("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); newnode.RemoveChild(node11); //移除了第一個<b>我是加粗節點</b> 此方法的重載,第二個參數決定是否移除孫子節點 Console.WriteLine(newnode.InnerHtml); //<h3>愛情獨白</h3><b>我是加粗節點</b><p>願得一心人.... newnode.RemoveAllChildren(); //移除全部子節點 Console.WriteLine(newnode.OuterHtml); //<div class="col say"></div> 全部子節點都被移除了 newnode.RemoveAll(); //移除全部的屬性和子節點,因爲子節點已經被上個方法移除了,所以此次連屬性也移除了 Console.WriteLine(newnode.OuterHtml); //輸出 <div></div> 注意到屬性也被移除了。 //都移除光了,再來一個,仍是剛纔那個 HtmlNode newnode1 = doc.DocumentNode.SelectSingleNode("/html/body/div[4]/div[1]/div[3]"); Console.WriteLine("==================="); Console.WriteLine(newnode1.OuterHtml); //輸出 <div></div> 注意 移除是從HtmlDocument中移除的,再次獲取獲取不到了 HtmlNode newnode2 = doc.DocumentNode.SelectSingleNode("/html/body/div[4]/div[1]/div[2]/div[2]/p"); Console.WriteLine(newnode2.OuterHtml); //<p class="no_tip">她尚未設置不能忍受清單 // <a href="javascript:invite(5971070,8,'邀請設置不能忍受');" class="link_b needlogin">邀請她設置</a> //</p> newnode2.Remove(); //從文檔樹中移除newnode2節點 HtmlNode newnode3 = doc.DocumentNode.SelectSingleNode("/html/body/div[4]/div[1]/div[2]/div[2]/p"); //再次獲取該節點 //Console.WriteLine(newnode3.OuterHtml); //報未將對象引用到對象的實例異常,明顯是找不到了, HtmlNode newnode4 = doc.DocumentNode.SelectSingleNode("/html/body/div[4]/div[1]/div[1]/div/div[1]/p[2]/b[1]"); Console.WriteLine(newnode4.OuterHtml); //<b>相冊: // <a href="/photo/6971070.html" class="red">4</a>張 //</b> HtmlNode node17 = HtmlNode.CreateNode("<div>再次建立一個節點</div>"); newnode4.PrependChild(node17); //跟AppengChild相似,只是插入位置不一樣PrependChildren接受一個節點集合,一次過插入多個節點而已 Console.WriteLine(newnode4.OuterHtml); //輸出 //<b>相冊: // <div>再次建立一個節點</div> // <a href="/photo/6971070.html" class="red">4</a>張 //</b> HtmlNode node16 = newnode4.SelectSingleNode("child::a[1]"); HtmlNode node18 = HtmlNode.CreateNode("<p>新建一行</p>"); newnode4.ReplaceChild(node18, node16); Console.WriteLine(newnode4.OuterHtml); //輸出 //<b>相冊: // <div>再次建立一個節點</div> // <p>新建一行</p>張 //留意到node16表明得節點已經被替換掉了 //</b> HtmlNode node19 = newnode4.SelectSingleNode("child::p[1]"); node19.SetAttributeValue("class","class1"); Console.WriteLine(node19.OuterHtml); //輸出 <p class="class1">新建一行</p> Console.WriteLine(HtmlNode.IsOverlappedClosingElement("<a>我愛你</a>")); //輸出 False Console.WriteLine(HtmlNode.IsCDataElement("<a>我愛你</a>")); //輸出 False Console.WriteLine(HtmlNode.IsClosedElement("<a>我愛你</a>")); //輸出 False Console.WriteLine(HtmlNode.IsEmptyElement("<a>我愛你</a>")); //輸出 False Console.WriteLine(newnode4.OuterHtml); HtmlNode node20 = HtmlNode.CreateNode("<p>新的第二行</p>"); newnode4.AppendChild(node20); HtmlNodeCollection hnc = newnode4.SelectNodes("//p"); //根據XPath一次過獲取多個Node Console.WriteLine(hnc.Count); //輸出29 string str1 = node20.WriteContentTo(); Console.WriteLine(str1); //輸出 新的第二行 將節點內容寫入字符串 //public void WriteContentTo(TextWriter outText); //public string WriteTo(); //public void WriteTo(TextWriter outText); //public void WriteTo(XmlWriter writer); Console.ReadKey(); }
var divs = html.CssSelect("div"); //all div elements
var nodes = html.CssSelect("div.content"); //all div elements with css class ‘content’
var nodes = html.CssSelect("div.widget.monthlist"); //all div elements with the both css class
var nodes = html.CssSelect("#postPaging"); //all HTML elements with the id postPaging
var nodes = html.CssSelect("div#postPaging.testClass"); // all HTML elements with the id postPaging and css class testClass
var nodes = html.CssSelect("div.content > p.para"); //p elements who are direct children of div elements with css class ‘content’
var nodes = html.CssSelect("input[type=text].login"); // textbox with css class login
We can also select ancestors of elements:
var nodes = html.CssSelect("p.para").CssSelectAncestors("div.content > div.widget");
經常使用函數
xpath的經常使用函數主要包含節點集函數,字符串函數,布爾函數,數字函數,網上的資料較多,在此就再也不累述,可參考如下資料:
[a] XPath, XQuery, and XSLT Functions http://www.w3schools.com/xpath/xpath_functions.asp
[b] XPath Functions http://www.caucho.com/resin-3.0/xml/xpath-fun.xtp
[c] XPath Functions(MSDN) http://msdn2.microsoft.com/en-us/library/ms256138.aspx
經常使用定位語句實例
1. //NODE[not(@class)] 全部節點名爲node,且不包含class屬性的節點
2. //NODE[@class and @id] 全部節點名爲node,且同時包含class屬性和id屬性的節點
3. //NODE[contains(text(),substring] 全部節點名爲node,且其文本中包含substring的節點
//A[contains(text(),\"下一頁\")] 全部包含「下一頁」字符串的超連接節點
//A[contains(@title,"文章標題")] 全部其title屬性中包含「文章標題」字符串的超連接節點
4. //NODE[@id="myid"]/text() 節點名爲node,且屬性id爲myid的節點的全部直接text子節點
5. BOOK[author/degree] 全部包含author節點同時該author節點至少含有一個的degree孩子節點的book節點
6. AUTHOR[.="Matthew Bob"] 全部值爲「Matthew Bob」的author節點
7. //*[count(BBB)=2] 全部包含兩個BBB孩子節點的節點
8. //*[count(*)=2] 全部包含兩個孩子節點的節點
9. //*[name()='BBB'] 全部名字爲BBB的節點,等同於//BBB
10. //*[starts-with(name(),'B')] 全部名字開頭爲字母B的節點
11. //*[contains(name(),'C')] 全部名字中包含字母C的節點
12. //*[string-length(name()) = 3] 名字長度爲3個字母的節點
13. //CCC | //BBB 全部CCC節點或BBB節點
14. /child::AAA 等價於/AAA
15. //CCC/descendant::* 全部以CCC爲其祖先的節點
16. //DDD/parent::* DDD節點的全部父節點
17. //BBB[position() mod 2 = 0] 偶數位置的BBB節點
18. AUTHOR[not(last-name = "Bob")] 全部不包含元素last-name的值爲Bob的節點
19. P/text()[2] 當前上下文節點中的P節點的第二個文本節點
20. ancestor::BOOK[1] 離當前上下文節點最近的book祖先節點
21. //A[text()="next"] 錨文本內容等於next的A節點
最後推薦一款在Firefox中用的XPath插件:
XPath Checker
https://addons.mozilla.org/en-US/firefox/addon/1095
這個插件能夠方便查看網頁中任意元素的XPath路徑,但其自動生成的XPath路徑一般不是最簡路徑。
參考資料:
[1]XPath Examples. http://msdn2.microsoft.com/en-us/library/ms256086.aspx
[2]XPath Tutorial http://www.zvon.org/xxl/XPathTutorial/Output/example1.html
[3]XPath介紹 http://www.xml.org.cn/dispbbs.asp?boardID=14&ID=35493
[4]XPath reference http://msdn2.microsoft.com/en-us/library/ms256115.aspx
[5]XML Path Language (XPath)Version 1.0 http://www.w3.org/TR/xpath
[6]XPath Tutorial http://www.w3schools.com/xpath/default.asp