我正在尝试解析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语句吗?
答案 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如何让我的生活更轻松!