Ich versuche, die Liste der Stationen von der NOAA-Website zu analysieren (weather.noaa.gov). Wenn Sie sich die Quelle einer Seite wie Belarus Stations ansehen, können Sie sehen, dass die Liste der verfügbaren Sender wie folgt dargestellt wird:
<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>
Sie können sehen, dass die 'OPTION' Tags nicht geschlossen sind. Die Standardoptionen in HtmlAgilityPack schließen die Tags wie folgt:
<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>
Was macht es zu einem Schmerz zu analysieren oder zu überqueren. Ich habe die folgende Methode entwickelt, um jeden Tag zu rekursiv zu machen, aber ich frage mich, ob es einen eleganteren Weg gibt, vielleicht mit LINQ?
Meine Methode:
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);
}
}
Das heißt so:
Dictionary<string, string> sites = new Dictionary<string, string>();
...
foreach (HtmlNode option in select.ChildNodes)
{
if ((option.Name == "option") && (option.HasAttributes))
{
GetStations(option, ref sites);
}
}
Ich fühle mich, als würde ich eine Brute-Force-Methode verwenden, um die Liste der Stationen zu erhalten, und mir könnte etwas von der Leistungsfähigkeit der HtmlAgilityPack-Bibliothek fehlen. Gibt es einen besseren Weg? Gibt es Einstellungen, die dazu führen, dass dies kein Problem ist? Kann LINQ das einfacher handhaben?
Ich versuche XPATH, da es der einfachste Mechanismus ist, um eine Teilmenge von Tags zu erhalten. Da die Tags jedoch nicht geschlossen sind, erhalte ich jedes Options-Tag auf der Seite, während ich nur die Tags im 'select'-Tag verwenden möchte. Wie Sie sehen können, besteht ein Qualifikationsmerkmal darin, dass die 'Option'-Tags, die ich haben möchte, einen @ Wert =' XXXX 'haben, wobei' XXXX 'eine 4-stellige, großgeschriebene Stations-ID ist. Gibt es eine Möglichkeit, anzugeben, dass ich nur die Optionstags im Dokument haben möchte, die ein Attribut namens "Wert" mit einem 4-stelligen Großbuchstaben haben? Kann ich eine Vergleichsfunktion an eine xpath-Anweisung übergeben?
Danke für alle Hinweise. Ich habe mehr nach der xpath-Syntax gesucht und gefunden, dass das funktioniert:
//select[@name='cccc']/descendant::option[@value]
Dies gibt mir alle 'Option'-Tags unter dem' select'-Tag mit einem Attribut @ name = 'cccc', wobei das 'option tag' ein @ value-Attribut hat.
Viel weniger Arbeit als das, was ich tat. Nun, um meinen ganzen anderen Code zu refactorisieren, der das DOM mit Hilfe von HAP durchläuft, und zu sehen, wie XPATH mir das Leben erleichtern kann!
HtmlAgilityPack kann das schließende Tag automatisch korrigieren, aber vielleicht nicht genau so, wie Sie es erwarten :
HtmlNode.ElementsFlags["option"] = HtmlElementFlag.Closed;
var doc = new HtmlDocument();
doc.LoadHtml(html);
An dieser Stelle können Sie jedoch immer noch Text auswählen, der sich innerhalb des <option>
-Tags befinden soll, indem Sie XPath following-sibling::text()[1]
, zum Beispiel:
var optionTexts = doc.DocumentNode.SelectNodes("//select[@name='cccc']/option/following-sibling::text()[1]");
foreach (HtmlNode node in optionTexts)
{
Console.WriteLine(node.InnerText);
}