使用HtmlAgilityPack解析未从网页关闭的标记

时间:2014-07-17 04:18:21

标签: c# linq html-agility-pack

我正在尝试解析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,因为它似乎是获取标记子集的最简单机制。但是,由于标签没有关闭,我在页面上获得了每个选项标签,而我只想要'select'标签内的标签。因此,正如您所看到的,一个限定符是我想要的'选项'标签有@ value ='XXXX',其中'XXXX'是一个4字符的大写站ID。有没有办法指定我只想要文档中的选项标签,这些标签具有名为'value'的属性,并且大写为4个字符的值?我可以将比较函数传递给xpath语句吗?

3 个答案:

答案 0 :(得分:0)

我没有遇到过这样的问题,但除非我弄错了,否则以下内容应该删除所有选项标记,而不管它们是否被错误嵌套:

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

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

希望这有帮助!

答案 1 :(得分:0)

HtmlAgilityPack可以自动修复结束标记但可能not exactly the way you expect

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

无论如何,您仍然可以使用XPath <option>选择应该在following-sibling::text()[1]标记内的文字,例如:

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

答案 2 :(得分:0)

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

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

这给了我所有的选项&#39;标签下的选择&#39;带有@name =&#39; cccc&#39;属性的标记其中&#39;选项标签具有@value属性。

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