【问题标题】:HtmlAgilityPack and selecting Nodes and SubnodesHtmlAgilityPack 并选择节点和子节点
【发布时间】:2021-04-22 00:05:50
【问题描述】:

希望有人可以帮助我。

假设我有一个包含多个 divshtml 文档,如下例所示:

<div class="search_hit">
    <span prop="name">Richard Winchester</span>
    <span prop="company">Kodak</span>
    <span prop="street">Arlington Road 1</span>
</div>
<div class="search_hit">
    <span prop="name">Ted Mosby</span>
    <span prop="company">HP</span>
    <span prop="street">Arlington Road 2</span>
</div>

我正在使用HtmlAgilityPack 获取html 文档。我需要知道的是如何获得每个search_hit-div 的跨度?

我的第一个想法是这样的:

foreach (HtmlAgilityPack.HtmlNode node in
    doc.DocumentNode.SelectNodes("//div[@class='search_hit']"))
{
     foreach (HtmlAgilityPack.HtmlNode node2 in node.SelectNodes("//span[@prop]"))
     {
     }
}

每个div 都应该是一个对象,其中包含跨度作为属性:

public class Record
{
    public string Name { get; set; }
    public string company { get; set; }
    public string street { get; set; }
}

然后将填写此列表:

public List<Record> Results = new List<Record>();

但是我使用的XPATH 并没有像它应该做的那样在子节点中进行搜索。它似乎一次又一次地搜索整个文档。

我的意思是我已经以这种方式工作了,我只是获得了整个页面的跨度,但是我在 spansdivs 之间没有任何关系。意思是,我不再知道哪个span 与哪个div 相关。

有人知道解决方案吗?我已经玩了那么多,现在我完全糊涂了。 :)

感谢任何帮助!

【问题讨论】:

  • 查看我对解析代码方式的回答(完整的工作解决方案)。

标签: c# xpath html-agility-pack


【解决方案1】:

如果你使用//,它会从文档开始搜索。

使用.//从当前节点开始搜索所有

 foreach (HtmlAgilityPack.HtmlNode node2 in node.SelectNodes(".//span[@prop]"))

或完全删除前缀以仅搜索直接子代:

 foreach (HtmlAgilityPack.HtmlNode node2 in node.SelectNodes("span[@prop]"))

【讨论】:

  • 如果我这样做:foreach (HtmlAgilityPack.HtmlNode node2 in node.SelectNodes("span[@prop]")) Visual Studio 出现错误。
  • 什么样的错误?您也可以尝试使用 ./ 前缀。 (其实我只是猜测)
  • 我都试过了,结果都是:NullReferenceException: Object reference not set to an instance of an object.
  • 阅读 HtmlAgilityPack 的源代码后,我很确定它应该像那样工作。读取节点后,错误可能发生在其他地方?或者可能节点在 html 文件中不存在?
  • 确实如此。我已经单独测试过了。这样做时我得到了结果: foreach (HtmlAgilityPack.HtmlNode node in doc.DocumentNode.SelectNodes("//div[@class='search_hit']"))
【解决方案2】:

以下内容对我有用。正如 BeniBela 指出的那样,重要的一点是在第二次调用“SelectNodes”时添加一个点。

List<Record> lstRecords=new List<Record>();
foreach (HtmlNode node in doc.DocumentNode.SelectNodes("//div[@class='search_hit']"))
{
  Record record=new Record();
  foreach (HtmlNode node2 in node.SelectNodes(".//span[@prop]"))
  {
    string attributeValue = node2.GetAttributeValue("prop", "");
    if (attributeValue == "name")
    {
      record.Name = node2.InnerText;
    }
    else if (attributeValue == "company")
    {
      record.company = node2.InnerText;
    }
    else if (attributeValue == "street")
    {
      record.street = node2.InnerText;
    }
  }
  lstRecords.Add(record);
}

【讨论】:

    【解决方案3】:

    首先,看看这个:Html Agility Pack - Problem selecting subnode

    这是您问题的完整解决方案:

    IList<Record> results = new List<Record>();
    foreach (var node in doc.DocumentNode.SelectNodes("//div[@class='search_hit']")) {
        var record = new Record();
        record.Name = node.SelectSingleNode(".//span[@prop='name']").InnerText;
        record.company = node.SelectSingleNode(".//span[@prop='company']").InnerText;
        record.street = node.SelectSingleNode(".//span[@prop='street']").InnerText;
        results.Add(record);
    }
    

    如果您阅读了我指出的问题,您会发现执行 ./span[@prop='name'] 完全相同,因为那些 span 节点是 div 节点的(直接)子节点。


    如果span 节点没有那些prop 属性,并且您想根据它们出现的顺序来分配它们,您可以这样做:

    foreach (var node in doc.DocumentNode.SelectNodes("//div[@class='search_hit']")) {
        var spanNodes = node.SelectNodes("./span");
        var record = new Record();
        record.Name = spanNodes[0].InnerText;
        record.company = spanNodes[1].InnerText;
        record.street = spanNodes[2].InnerText;
        results.Add(record);
    }
    

    【讨论】:

      【解决方案4】:

      真丢脸:)

      你们都是对的。

      我发现了问题。这个 NullReferenceException 一直困扰着我,所以我花了更多时间详细查看它。 在所有这些 div 之间,有一个 div 具有相同的“class='search-hit'”属性,但内部没有跨度。这就是为什么它会在第二个循环中出现错误。

      foreach (HtmlAgilityPack.HtmlNode node in doc.DocumentNode.SelectNodes("//span[@prop]/ancestor::div[@class='search_hit']"))
         {
              Record rec = new Record();
              foreach (HtmlAgilityPack.HtmlNode node2 in node.SelectNodes(".//span[@prop]"))
                 {
                 }
                 rList.Results.Add(rec);
         }
      

      上面的代码正在运行。

      感谢你们的时间和帮助!

      【讨论】:

        【解决方案5】:

        我用过。类转换id

          HtmlNodeCollection nodes = dokuman.DocumentNode.SelectNodes("//div[@id='search_hit']//span[@prop]");
        
        
                    for (int i = 0; i < nodes .Count; i++)
                {
                    var record = new Record();
        
        
                        record.Name = links[i].InnerText;   results.Add(record);  }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-07-02
          • 1970-01-01
          • 1970-01-01
          • 2014-10-17
          • 1970-01-01
          相关资源
          最近更新 更多