/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。
刚刚学习了XPath路径表达式,主要是对XML文档中的节点进行搜索,通过XPath表达式可以对XML文档中的节点位置进行快速定位和访问,html也是也是一种类似于xml的标记语言,但是语法没有那么严谨,在codeplex里有一个开源项目HtmlAgilityPack,提供了用XPath解析HTML文件,下面掩饰如何使用该类库的使用
首先说下XPath路径表达式
XPath路径表达式
用来选取XML文档中的节点或节点集的
1、术语:节点(Node):7种类型:元素,属性,文本,命名空间,处理命令,注释,文档(根)节点
2、节点关系:父(Parent),子(Children),同胞(Sibling),先辈(Ancestor),后代(Descendant)
3、路径表达式
nodename 节点名,选取此节点的所有子节点 例: childnode 当前节点中的childnode子节点,不包含孙子及以下的节点
/ 从根节点选取 例:/root/childnode/grandsonnode
// 表示所有后代节点 例://childnode 所有名为childnode的后代节点
. 表示当前节点 例: ./childnode 表示当前节点的childnode节点
.. 表示父节点 例: ../nearnode 表示父亲节点的nearnode子节点
@ 选取属性 /root/childnode/@id 表示childnode的所有含有id属性的节点集
4、谓语(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节点集
5、通配符:XPath路径中同样支持通配符(*,@*,node(), text())
例: /bookstore/*
//title[@*]
6、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()
7、运算符
| 两个节点集的合并 例:/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