【转】 HtmlAgilityPack使用——XPath注意事项

在使用HtmlAgilityPack这个开源的类库进行网页内容解析的时候是非常的方便(使用方法见另一篇博客《HTML解析:基于XPath的C#类库HtmlAgiliytyPack》),其基于XPath路径语法进行高效的选择文档节点,当发起请求获取了网页html文件的时候,解析的大部分工作量就落到了XPath路径表达式的书写了。本文测试在VS2010开发环境,.NetFramework
4.0 C#语言,使用的html如下:

<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<title>test</title>
</head>
<body>
<div id="content">
<div>
<a href="http://www.google.com">google</a>
<div>gmail</div>
<div>google earth</div>
</div>
<div>
<a href="http://www.baidu.com">baidu</a>
</div>
<div>
<a href="http://www.tmall.com">tmall</a>
</div>
</div>
</body>
</html>

1、HtmlAgilityPack节点类型

在使用XPath表达式选择文档特定节点的时候,我发现有时候按照上下文写出的路径表达式就是失效,或者选择出错到了错误的内容,或者由于SelectSingleNode或者SelectNodes这个两个函数使用相应的XPath表达式查询的时候找不到结果而跑出异常。后来发现,HtmlAgilityPack对节点的选择严格按照XPath的规范来实现,XPath规范中严格定义了七种类型的节点(http://www.w3school.com.cn/xpath/xpath_nodes.asp):元素(Element),属性(Attribute),文本(Test),命名空间,处理指令,注释,文档根节点。基本值是无父或无子的节点,项目(Item)是基本值或者节点,然后又父子、同胞、先辈和后辈这些关系。HtmlAgilityPack的每个HtmlNode对象就封装好了上述所有规范定义的项目,下图是一个节点对象包含的内容。

正是因为如此,在书写XPath路径表达式的时候需要考虑到HtmlAgilityPack将text也作为了一个node,因此对于我们一般意义上看到的html结构就要多考虑一次text节点,空的文本节点也算再内,这在javascript的IE下的一个特例就是如此,为此需要特别为IE浏览器而书写兼容的js代码。使用如下的C#代码输出的是空字符串,也就是空的text节点。

    HtmlDocument doc = new HtmlDocument();
doc.Load(@"C:\test.html");
HtmlNode main = doc.GetElementbyId("content");
HtmlNode child = main.FirstChild;
Console.WriteLine(child.InnerText);

以上结果输出为空:

这也就验证了,选取的为id为content的div节点的FirstChild节点是空的文本节点。因此对于FirstChild、LastChild、NextSibling、PrevSibling等关系表示的节点需要慎用,需要考虑空的text节点。

2、深刻理解“//”和“./”

XPath路径表达式中最关键的选择如下

表达式 描述
nodename 选取此节点的所有子节点。
/ 从根节点选取。
// 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。
. 选取当前节点。
.. 选取当前节点的父节点。
@ 选取属性。

这也是书写路径表达式的基础,其中对于选择了一个节点之后,使用“//”和“./”这个两个语法总是会让人迷惑。本文实际测试了两者的区别。

  • “//”:从当前选择的节点开始寻找,对于后面的表达式是在当前节点中的任意位置寻找,只要符合的就加入到选择结果中。
  • “./”:也是从当前选择的节点开始选择,但是仅仅是寻找当前节点的直系子元素,而对于孙子及以后的节点都不考虑。
区别使用了如下的测试代码:
            HtmlDocument doc = new HtmlDocument();
doc.Load(@"C:\test.html");
HtmlNode main = doc.GetElementbyId("content");
HtmlNodeCollection nodes = main.SelectNodes("./div");
foreach (HtmlNode node in nodes)
{
Console.WriteLine("=============start=============");
Console.WriteLine(node.InnerText);
Console.WriteLine("=============end===============");
}

上面输入结果如下:


使用如下代码继续测试:
            HtmlDocument doc = new HtmlDocument();
doc.Load(@"C:\test.html");
HtmlNode main = doc.GetElementbyId("content");
HtmlNodeCollection nodes = main.SelectNodes("//div");
foreach (HtmlNode node in nodes)
{
Console.WriteLine("=============start=============");
Console.WriteLine(node.InnerText);
Console.WriteLine("=============end===============");
}


通过以上测试可以看出,根据路径表达式进行选择的时候需要进行特别区分出上述两者的区别,这样才能兼顾取得的结果是准确无误的。

            HtmlDocument doc = new HtmlDocument();
doc.Load(@"C:\test.html");
HtmlNode main = doc.GetElementbyId("content");
HtmlNode node1 = main.SelectSingleNode("//div[1]/div[2]");
HtmlNode node2 = main.SelectSingleNode("./div[1]/div[2]");
Console.WriteLine(node1.InnerText);
Console.WriteLine(node2.InnerText);

上述结果可以看出根据路径表达式选择的时候结果可能是一样的,因此需要依据具体情况对待。

以上是本人使用HtmlAgilityPack解析html过程中书写XPath表达式得出的一些认识,希望供有用过的朋友可以一起探讨交流。

最新文章

  1. persistence.xml文件的妙处
  2. iOS开发 代码 或 &lt;Home+Power&gt;截屏
  3. 基于bshare分享平台,在一个页面上实现多个不同内容的web分享
  4. redisb并发访问慢出现的问题
  5. Effective C++ 总结(二)
  6. Jndi and c3p0 in Tomcat
  7. Jquery基础之动画操作
  8. 使用rem设计移动端自适应页面二(转载)
  9. C# 语言规范_版本5.0 (第1章 介绍)
  10. Spring 之 示例(Java之负基础实战)
  11. AsciidocFX编辑器小贴士
  12. MIUI12系统怎么样开启Root超级权限的流程
  13. Could not HEAD &#39;https://dl.google.com/dl/android/maven2/com/android/tools/build/gradle/3.2.0/gradle-3.2.0.pom&#39;.
  14. 一个简单的makefile文件
  15. Linux 防火墙iptables开放特定端口
  16. 订单BOM、销售BOM、标准BOM
  17. Luogu 2154 [SDOI2009]虔诚的墓主人
  18. Python概念-反射之文化底蕴版:反正射了
  19. 总结day5 ---- ,字典的学习,增删改查,以及字典的嵌套, 赋值运算
  20. bnu 4060 奇偶性,异或运算

热门文章

  1. [SDOI2008]沙拉公主的困惑 线性筛_欧拉函数_逆元_快速幂
  2. Vue export和import
  3. CF1000G Two-Paths (树形DP)
  4. nginx编译支持HTTP2.0
  5. c++ 子类构造函数初始化及父类构造初始化
  6. ElementUi rules表单验证
  7. Web长连接推送
  8. Git(三):加入与提交
  9. QtWebkit里RenderLayer树的绘制具体流程分析
  10. 全栈JavaScript之路(十一)学习 Attr 类型 节点