【问题标题】:Parsing tags which are not closed from web page with HtmlAgilityPack使用 HtmlAgilityPack 解析未从网页关闭的标签
【发布时间】:2014-07-17 04:18:20
【问题描述】:

我正在尝试解析来自 NOAA 网站 (weather.noaa.gov) 的电台列表。如果您查看页面的来源,例如Belarus Stations,您可以看到可用电台列表显示为:

<select name="cccc">
    <option selected>Select a location
    <OPTION VALUE="UMBB"> Brest
    <OPTION VALUE="UMGG"> Gomel'
    <OPTION VALUE="UMMG"> Grodno
    <OPTION VALUE="UMMM"> Loshitsa / Minsk International 1
    <OPTION VALUE="UMMS"> Minsk
    <OPTION VALUE="UMII"> Vitebsk
</select>

您可以看到“OPTION”标签没有关闭。 HtmlAgilityPack 中的默认选项关闭标签,如下所示:

<select name="cccc">
    <option selected>Select a location
    <OPTION VALUE="UMBB"> Brest
    <OPTION VALUE="UMGG"> Gomel'
    <OPTION VALUE="UMMG"> Grodno
    <OPTION VALUE="UMMM"> Loshitsa / Minsk International 1
    <OPTION VALUE="UMMS"> Minsk
    <OPTION VALUE="UMII"> Vitebsk
    </OPTION></OPTION></OPTION></OPTION></OPTION></OPTION></OPTION>
</select>

这使得解析或遍历变得很痛苦。我想出了以下方法来递归每个标签,但我想知道是否有更优雅的方法,也许使用 LINQ?

我的方法:

private static void GetStations(HtmlNode node, ref Dictionary<string, string> stations)
{
    // the HTML is malformed, such that the <option> elements are
    // not properly closed, so we have to parse manually
    string name = node.GetAttributeValue("value", string.Empty).Trim();
    string value = node.InnerHtml.Substring(0, node.InnerHtml.IndexOf("\n")).Trim();

    if (!string.IsNullOrEmpty(name) &&
             name.Length == 4 &&
            char.IsUpper(name[0]))
    {
        stations.Add(name, value);
    }
    // due to not closing the <option> elements
    // we have to recurse into child nodes until
    // we get them all
    if (node.HasChildNodes)
    {
        GetStations(node.LastChild, ref stations);
    }
}

这样称呼:

Dictionary<string, string> sites = new Dictionary<string, string>();
...
foreach (HtmlNode option in select.ChildNodes)
{
    if ((option.Name == "option") && (option.HasAttributes))
    {
        GetStations(option, ref sites);
    }
}

我觉得我正在使用蛮力方法来获取电台列表,我可能会错过 HtmlAgilityPack 库的一些功能。有没有更好的办法?是否有可能使这成为非问题的设置? LINQ 可以更轻松地处理这个问题吗?

我正在尝试 XPATH,因为它似乎是获取标签子集的最简单机制。但是,由于标签没有被关闭,我得到了页面上的每个选项标签,而我只想要“选择”标签内的标签。因此,如您所见,一个限定符是我想要的“选项”标签有一个@value=“XXXX”,其中“XXXX”是一个 4 个字符的大写电台 ID。有没有办法指定我只需要文档中的选项标记,这些选项标记具有名为“值”的属性和大写的 4 字符值?我可以将比较函数传递给 xpath 语句吗?

【问题讨论】:

    标签: c# linq html-agility-pack


    【解决方案1】:

    我还没有遇到过这样的问题,但除非我弄错了,否则以下应该拉出所有选项标签,无论它们是否被错误嵌套:

    var optionNodes = htmlDoc.DocumentNode.Select("//option");
    

    现在,如果有其他您不想要的选项标签,那可能会稍微困难一些。但是,如果碰巧这些是唯一具有包含“um”的值的选项标签,您可以使用//option[contains(@value, \"UM\")],它应该将其缩小到那些选项标签。

    希望这会有所帮助!

    【讨论】:

      【解决方案2】:

      HtmlAgilityPack 可以自动修复结束标签,但可能是not exactly the way you expect:

      HtmlNode.ElementsFlags["option"] = HtmlElementFlag.Closed;
      var doc = new HtmlDocument();
      doc.LoadHtml(html);
      

      无论如何,此时您仍然可以使用 XPath following-sibling::text()[1] 选择应该在 &lt;option&gt; 标记内的文本,例如:

      var optionTexts = doc.DocumentNode.SelectNodes("//select[@name='cccc']/option/following-sibling::text()[1]");
      foreach (HtmlNode node in optionTexts)
      {
          Console.WriteLine(node.InnerText);
      }
      

      【讨论】:

        【解决方案3】:

        感谢所有的指点。我对 xpath 语法进行了更多搜索,发现它有效:

        //select[@name='cccc']/descendant::option[@value]
        

        这为我提供了 'select' 标签下的所有 'option' 标签,并带有 @name='cccc' 属性,其中 'option 标签有 @value 属性。

        工作量比我做的少得多。现在使用 HAP 重构我在 DOM 中循环的所有其他代码,看看 XPATH 如何让我的生活更轻松!

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-09-27
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-11-10
          • 1970-01-01
          相关资源
          最近更新 更多