使用HtmlAgilityPack解析未從網頁關閉的標記

c# html-agility-pack linq

我試圖解析NOAA網站(weather.noaa.gov)的電台列表。如果您查看白俄羅斯電台等頁面的來源,您可以看到可用電台列表顯示為:

<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'標籤內的標籤。因此,正如您所看到的,一個限定符是我想要的'選項'標籤具有@值='XXXX',其中'XXXX'是4個字符的大寫站ID。有沒有辦法指定我只想要文檔中的選項標籤,這些標籤具有名為'value'的屬性,並且大寫為4個字符的值?我可以將比較函數傳遞給xpath語句嗎?

一般承認的答案

感謝所有指針。我對xpath語法進行了更多搜索,發現它有效:

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

這為'select'標籤下的所有'option'標籤提供了一個屬性@ name ='cccc',其中'option標籤具有@value屬性。

比我正在做的工作少得多。現在重構我使用HAP遍歷DOM的所有其他代碼,看看XPATH如何讓我的生活更輕鬆!


熱門答案

HtmlAgilityPack可以自動修復結束標記,但可能不完全符合您的預期

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

無論如何,您仍然可以使用XPath follow following-sibling::text()[1]選擇應該在<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);
}



許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
這個KB合法嗎? 是的,了解原因
許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
這個KB合法嗎? 是的,了解原因