使用 HtmlAgility pack 和 CssSelectors






3.78/5 (9投票s)
解析 HTML 文件过程中的观察。
引言
首先,我并不声称是 XPath 或正则表达式方面的专家,但以下是我在为客户项目解析 HTML 文档时的一些观察。
在以下示例中,我使用 HtmlAgility Pack (HAP) 将 HTML 加载到文档对象模型 (DOM) 中并解析为节点。此外,有时我需要解析文档中并非真正节点的元素,例如注释。
除了对 HAP 的一般性观察外,我还将介绍 HAP.CSSSelectors
包提供的扩展方法,这些方法可以大大简化选择操作。
背景
我一直在成功地为一名客户使用 HTMLAgility
Pack,解析 HTML 文档以提取相关信息。CssSelector
扩展将添加一个更强大的抽象级别,用于收集所需数据。
Using the Code
示例所需的程序包需要通过 NuGet 导入。程序包描述将加载到项目中,但您需要将 NuGet 程序包管理器设置为恢复库。
在项目中,我包含了一个非常简单的 HTML 文件,其中包含我在项目中需要解决的问题示例。
为了在不进行任何修改的情况下进行测试,您需要将 HTML 文件复制到以下驱动器和目录 – C:\testdata。
HtmlAgility
提供了许多类,包括表示 DOM 各个部分的类和枚举,这些类包括 HtmlAttribute
、HtmlAttributeCollection
、HtmlCommentNode
等等。
我们要检查的第一个类是 HtmlDocument
类。此类包含加载和解析文档到其各自部分的方法。
在附加的源代码中,我使用 (Part X) 的命名法来标识代码的每个部分,其中 X 是一个数字。
要使用,需要实现以下行
第一部分
HtmlAgilityPack.agpack = new HtmlAgilityPack.HtmlDocument();
要调用的下一个方法是加载文档的方法。您可以从 string
加载
agpack.LoadHtml(Html string)
//or from a resource -
agpack.Load(@"c:\testdata\testdat.htm");
与网页浏览器一样,HAP 对提供的 Html
具有容错性。您可以查询错误,但它不会中断。
包含的文件在第二个 font 标签上缺少关闭标签,并且 end 标签位置错误。在浏览器中运行正常,在 HAP 中也不会抛出错误,但可以进行检查。
第二部分
var errors = agpack.ParseErrors;
ParseErrors
将返回一个错误集合和错误计数。有趣的是,关闭的 font 标签并没有引发错误。但错位的 </tr>
却引发了错误。
文档加载后,用于搜索的两个主要方法是
SelectNodes(string XPath) // from the DocumentNode
GetElementbyId(string Id) // from the HtmlDocument
由于 ID 只能有一个,getElementById
将返回单个节点,而 SelectNodes
将返回节点集合,因为使用 XPath
,您可能会匹配一个或多个项目。
我的客户有一个应用程序,它会将多个文件连接在一起,并用开始和结束注释分隔每个文档。以下是我如何处理将此文档分割回其组成部分的方法。我包含的文件有一个由注释分隔的部分,注释形式为
<!-- Start Table: 1234 --> HTML Body <!-- End Table -->
其中 1234
可能代表我们需要处理的某种类型的账户号码。
第三部分
您可以使用以下方法来获取注释
var comment = agpack.DocumentNode.SelectNodes("//comment()[contains(., 'Start Table:')]");
这表示从整个文档(“//
”)中选择包含从当前位置(.)开始的单词 Start Table
的注释。
由于这是一个注释,它没有子节点,其内部文本就是注释本身的文本。如果您想解析注释以确定注释中的某个值(例如本例中的账户号码),这将很有用,但如果您想要注释之间的文本,则帮助不大。为了实现这一点,我求助于正则表达式和分组。
第四部分
var html = Regex.Match(agpack.DocumentNode.InnerHtml,@"
<!-- Start Table: \d* -->(?<one>.*)<!-- End Table -->",RegexOptions.Singleline).Groups[1];
现在,在 html.Value
中,我们得到了两个标签之间的文本。
接下来是查找 DOM 中的元素,第一个示例是使用 getElementById
查找节点。有三个表格,但只有一个有 ID。一个是 ID=”abc”
,另一个是 ID=”table3”
。
让我们开始查看 id 为 id=”abc”
的表格
第五部分
var node = agpack.GetElementbyId("abc");
这将返回一个表示该表格的单个节点。InnerHtml
将包含 <table></table>
标签之间的所有文本。它还将包含表示表格 DOM 结构的节点集合。
第 6 部分
获取行节点的其中一种方法是使用 Linq 来发现它们,例如
var rownodes = node.ChildNodes.Where(w => w.OriginalName == "tr");
这有点效果,如果您检查计数,您会看到有三行。但实际上有四行,第一个被 <thead></thead>
包裹的行找不到。
另一种方法是在节点上使用 SelectNodes
来发现 tr
元素。
rownodes = node.SelectNodes("tr");
但这仍然找不到所有行,只找到其直接子节点。
那么 node.SelectNodes("/tr");
呢?这什么也没返回。
那么 node.SelectNodes("//tr");
呢?好消息是它找到了丢失的行以及文档中的所有行(12 行)。
经过一番研究,我发现以下两种解决方案有效
rownodes = node.SelectNodes(node.XPath + "//tr");
//or
// https://w3schools.org.cn/xsl/xpath_axes.asp
rownodes = node.SelectNodes("descendant::tr");
这返回了所有四行,这对我来说很有趣。我认为我曾假设 HAP 会从当前节点进行 SelectNodes
,并且“//tr
”会起作用,但“//
”表示从文档根目录开始搜索。而第二个选项确实可以作为当前选定节点的后代。
类似地,我们可以使用相同的方法查找 tr
元素的所有 td
元素。请注意,对于 table 3,我们返回了十二个 td
元素,即使它们是 <tr>
、<font>
和 <span>
元素的子元素。
第 7 部分
node = null;
node = agpack.GetElementbyId("table3")
nodes = node.SelectNodes("descendant::td");
让我们转向 HAP.CssSelectors
它建立在 HtmlAgility
Pack 之上,并且实际上会确保它作为 NuGet 包的一部分被安装。
它允许您使用 CSS 选择器而不是 XPath
来选择元素。例如
第 8 部分
rownodes = agpack.QuerySelectorAll("#abc tr");
在这种情况下,我不需要从节点开始查找,只需从整个文档选择,它就返回了预期的 4 行。
listTDNodes = agpack.QuerySelectorAll("#table3 td");
这是一个获取第二行中仅有的 <td>
(三个)的示例。
listTDNodes = agpack.QuerySelectorAll("#table3 tr:nth-child(2) td");
这返回了十二个项目,四行三列。有一点需要注意。QuerySelectorAll
方法返回的是 List<node>
而不是节点集合。如果您打算混合使用,了解这一点很重要。
除了按 ID(#
)选择外,您还可以按类(.
)选择,这比使用 XPath 查找带有类的属性要容易得多。
listTDNodes = agpack.QuerySelectorAll(".table");
返回类为 table 的第一个和第三个表格。
关注点
总之,CssSelectors
扩展是另一个有用的工具,可以轻松选择元素,而无需深入研究 XPath 或遍历集合。我知道我将期待将这些发现中的一些应用到我自己的工作中。
历史
- 2015 年 10 月 12 日:初始版本