【问题标题】:HtmlAgilityPack NextSibling.InnerText value is blankHtmlAgilityPack NextSibling.InnerText 值为空白
【发布时间】:2014-08-27 19:17:26
【问题描述】:

我正在使用 HtmlAgilityPack 抓取一些数据。

HTML 如下所示:

<div id="id-here">
  <dl>
    <dt> Field Name </dt>
    <dd> Value for above field name </dd>
    <dt> Field Name </dt>
    <dd> Value for above field name </dd>
    <dt> Field Name </dt>
    <dd> Value for above field name </dd>
  </dl>
</div>

现在我遇到的问题是,并不总是有一定数量的字段,所以我无法可靠地访问每个字段,例如:

//*[@id="id-here"]/dl[1]/dd[1]

因为 dd[1] 可能是一个页面上的名称和另一个页面上的电话,其中用户未能填写名称,因此字段被隐藏。

所以我像这样抓取所有 DT 和 DD 节点:

//*[@id="id-here"]/dl[1]/dt | //*[@id="id-here"]/dl[1]/dd

现在我检查每个节点,看看它是否与我想要的字段匹配,然后像这样获取 NextSibling 值:

    foreach (HtmlNode node in details)
    {
        if (node.InnerText.Contains("Tel:")) telephone = node.NextSibling.InnerText;
        if (node.InnerText.Contains("Email:")) email = node.NextSibling.InnerText;
    }

这适用于电话,但由于某种原因,当“电子邮件:”节点出现时,NextSibling.InnerHTMLNextSibling.InnerText 都是空白的,尽管下一个兄弟肯定有数据。如果我真的去details 中的node 并查看它,InnerHTML 是整个格式化链接,InnerText 是电子邮件地址。

NextSibling.InnerText 不工作是因为 A 标签使它成为一个孩子还是什么?我查看了调试器,但在NextSibling 下找不到我需要的信息。

我确信答案非常简单,我就是想不通。有人让我摆脱痛苦吗?

【问题讨论】:

  • 有些单独的问题,但是如果您实际上不打算在遍历details 时使用选定的dds,为什么要选择dd 元素?
  • 这样我就可以选择下一个兄弟姐妹。如果我不选择 DD,那么他们就不会成为下一个兄弟姐妹。
  • details 的内容与节点的兄弟姐妹是什么无关,而这正是您在这里看到的。

标签: c# xpath html-agility-pack siblings


【解决方案1】:

发生这种情况的原因是,如果node 是一个dt 元素,它与相应的dd 元素之间有一些空格,那么node.NextSibling 是一个全空格文本节点( &lt;/dt&gt;&lt;dd&gt;)。如果您在调试器中查看它,您会看到node.NextSiblingNodeTypeHtmlNodeType.Text 而不是HtmlNodeType.Element

我建议创建一个方便的方法来获取dt 节点对应的dd 的文本:

internal static string GetMatchingDdValue(HtmlNode dtNode)
{
    var found = dtNode.SelectSingleNode("following-sibling::*[1][self::dd]");
    return found == null ? "" : found.InnerText;
}

那么你可以这样使用它:

if (node.InnerText.Contains("Tel:")) { telephone = GetMatchingDdValue(node); }

下面是我在上面的方法中使用的有点棘手的 XPath 的细分:

(a) following-sibling::*

^ 选择所有共享相同的元素 parent 作为当前节点并出现在它之后。

(b) following-sibling::*[1]

^ 选择集合 (a) 中的第一个节点 (如果有的话)

(c) following-sibling::*[1][self::dd] 

^ 选择集合 (b) 中的所有节点 是名称为“dd”的元素

SelectSingleNode() 选择集合 (c) 中的第一个节点,该节点应始终为 1 或 0 个节点。

您很可能只使用following-sibling::ddfollowing-sibling::*,但上述路径包含安全措施。例如,如果由于某种原因,您有以下 XML,并且您的当前节点是 Tel: 元素:

<dl>
  <dt>Tel:</dt>
  <dt>Address:</dt>
  <dd>50 Fake St.</dd>
</dl>

following-sibling::dd 会给你结果“50 Fake St.”,而following-sibling::* 会给你结果“地址:”。相反,following-sibling::*[1][self::dd] 在这种情况下会选择一个空节点集,因此该方法将正确地生成一个空字符串作为结果。

【讨论】:

  • 呸,比我快 5 秒 :)(还有代码!)
  • 谢谢,它工作得很好。让我感到困惑的是,当我在调试器中打开 details 并看到说“电子邮件:”在 [0] 处,然后电子邮件地址在 [1] 处,所以我认为 NextSibling 会得到下一个条目。我是 xpath 的新手,不完全理解你的 xpath 是如何工作的,我试图从参考中解决它,但没有完全理解它。我想我需要买一本书。
  • following-siblings = 获取该节点之后的所有 html * = 获取任何 html。 [1] = ?? [self::dd] = 选择当前节点并选择 dd?如果我删除这个位,代码仍然有效
  • @Guerrilla 在上面添加了解构。
  • @JLRishe 谢谢!非常感谢您花时间向我解释。
【解决方案2】:
var html = @"
<div id='id-here'>
  <dl>
    <dt> Field Name </dt>
    <dd> Value for above field name </dd>
    <dt> Field Name </dt>
    <dd> Value for above field name </dd>
    <dt> Field Name </dt>
    <dd> Value for above field name </dd>
  </dl>
</div>";
html = new Regex(">\r\n\\s*<").Replace(html,"><");
var doc = new HtmlAgilityPack.HtmlDocument();
doc.LoadHtml(html);
Console.Write(doc.DocumentNode.SelectNodes("//dt")[0].NextSibling.OuterHtml);

<dd> Value for above field name </dd>

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-12-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-17
    相关资源
    最近更新 更多