使用HTML Agility Pack和Linq解析HTML

12

我有如下HTML代码

(..)
<tbody>
 <tr>
  <td class="name"> Test1 </td>
  <td class="data"> Data </td>
  <td class="data2"> Data 2 </td>
 </tr>
 <tr>
  <td class="name"> Test2 </td>
  <td class="data"> Data2 </td>
  <td class="data2"> Data 2 </td>
 </tr>
</tbody>
(..)

我掌握的信息只有名称 - "Test1"和"Test2"。我想知道的是,如何根据我的名称获取"data"和"data2"中的数据。

目前我正在使用:

var data =
    from
        tr in doc.DocumentNode.Descendants("tr")
    from   
        td in tr.ChildNodes.Where(x => x.Attributes["class"].Value == "name")
    where
        td.InnerText == "Test1"
    select tr;

但是,当我尝试查看data时,出现了{"对象引用未设置到对象实例。"}


你到底想做什么?而且,代码在做什么事情是你不想要的? - R. Martinho Fernandes
呵呵,抱歉我忘记添加了,我现在正在添加。 - Timo Willemsen
你能告诉我们你的错误是什么吗?或者你期望发生的事情没有发生吗? - James Walford
我已经修改了我的问题,希望能更容易理解一些。 - Timo Willemsen
1
在您的示例中,tds 中的文本具有前导和尾随空格,而您正在查找的字符串没有。 - James Walford
显示剩余2条评论
5个回答

16

关于你的尝试,你的代码有两个问题:

  1. ChildNodes 很奇怪 - 它还返回空格文本节点,这些节点没有 class 属性(当然不能有属性)。
  2. 正如 James Walford 评论所说,文本周围的空格是有意义的,你可能想要修剪它们。

经过这两个更正,以下代码可以正常工作:

var data =
      from tr in doc.DocumentNode.Descendants("tr")
      from td in tr.Descendants("td").Where(x => x.Attributes["class"].Value == "name")
     where td.InnerText.Trim() == "Test1"
    select tr;

5
这里介绍一种使用XPATH的方法——嗯...现在似乎每个人都忘记了XPATH这种强大的工具,而完全集中于使用C# XLinq来编写代码 :-)
以下是一个能够获取所有与某个名称关联的数据值的函数:
public static IEnumerable<string> GetData(HtmlDocument document, string name)
{
    return from HtmlNode node in
        document.DocumentNode.SelectNodes("//td[@class='name' and contains(text(), '" + name + "')]/following-sibling::td")
        select node.InnerText.Trim();
}

例如,该代码将会输出所有“Test2”数据:
    HtmlDocument doc = new HtmlDocument();
    doc.Load(yourHtml);

    foreach (string data in GetData(doc, "Test2"))
    {
        Console.WriteLine(data);
    }

我考虑使用带有 contains 的 xpath,但它确实存在一个主要问题:搜索 Test1 也会找到 Test10NotTest1 等等。我不太了解足够的 xpath 来解决这个问题... - Kobi
@Kobi - 如果您不想使用contains,那么可以使用=。如果空格是一个问题,可以使用normalize-space来移除它们,或者参考这个链接获取更多信息:https://dev59.com/dkrSa4cB1Zd3GeqPYKvT - Simon Mourier
3
我喜欢Linq答案胜过XPath的原因是因为后者很难阅读和理解。前者很清楚表达了意图,需要时可以将查询分解成子查询以进行调试。XPath晦涩难懂且不可能进行调试。如果没有大量测试数据,很难验证它是否在执行正确的操作。只是为了查找XPath语法的权威页面就让人感到非常痛苦。虽然我仍然喜欢HAP,但每次看到XPath语句时我都会感到不舒服。 - Dan Bailiff
2
当你不了解它时,一切都很困难。我认为在查询XML集时,XPATH更易于使用和理解。它还可以优雅地处理空值(不像Linq)。唯一的(大)缺点是它不适用于大小写不敏感的比较。另一个问题是XPATH不可移植(例如,在WinRT上不存在)。无论如何,使用您喜欢的库吧 :-) - Simon Mourier
相反,如果你理解了它,一切都似乎很容易。但这并不意味着对其他人来说也很容易。LINQ有许多应用,而XPATH则没有。就我个人而言,在这种情况下,我更愿意使用.Descendents("a").Last()而不是"//a[last()]",但由于我的需求只是这一个小事,所以我感谢您唤起我对xpath的记忆,并点赞您的回答。 - Christopher Painter

1
这里有一种方法 - 首先将所有数据解析成数据结构,然后再读取它。这有点混乱,肯定需要更多的验证,但是我们可以试试:
HtmlWeb hw = new HtmlWeb();
HtmlDocument doc = hw.Load("http://jsbin.com/ezuge4");
HtmlNodeCollection nodes = doc.DocumentNode
                              .SelectNodes("//table[@id='MyTable']//tr");
var data = nodes.Select(
    node => node.Descendants("td")
        .ToDictionary(descendant => descendant.Attributes["class"].Value,
                      descendant => descendant.InnerText.Trim())
        ).ToDictionary(dict => dict["name"]);
string test1Data = data["Test1"]["data"];

在这里,我将每个<tr>转换为一个字典,其中<td>的类是键,文本是值。接下来,我将字典列表转换为字典的字典(提示-将其抽象化),其中每个<tr>name是键。

-1

而不是

td.InnerText == "Test1"

尝试

td.InnerText == " Test1 "

或者

d.InnerText.Trim() == "Test1"

-1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接