Ich scrapping einige Daten mit HtmlAgilityPack.
Der HTML-Code sieht so aus:
<div id="id-here">
<dl>
<dt> Field Name </dt>
<dd> Value for above field name </dd>
<dt> Field Name </dt>
<dd> Value for above field name </dd>
<dt> Field Name </dt>
<dd> Value for above field name </dd>
</dl>
</div>
Jetzt ist das Problem, das ich habe, dass es nicht immer eine bestimmte Anzahl von Feldern gibt, so kann ich nicht zuverlässig auf sie zugreifen wie:
//*[@id="id-here"]/dl[1]/dd[1]
als dd [1] kann ein Name auf einer Seite und ein Telefon auf einem anderen sein, wo der Benutzer einen Namen nicht ausfüllen konnte, so dass das Feld ausgeblendet ist.
also greife ich alle DT- und DD-Knoten so an:
//*[@id="id-here"]/dl[1]/dt | //*[@id="id-here"]/dl[1]/dd
Jetzt überprüfe ich jeden Knoten, um zu sehen, ob er mit dem gewünschten Feld übereinstimmt, und nehme den NextSibling-Wert wie folgt:
foreach (HtmlNode node in details)
{
if (node.InnerText.Contains("Tel:")) telephone = node.NextSibling.InnerText;
if (node.InnerText.Contains("Email:")) email = node.NextSibling.InnerText;
}
Dies funktioniert gut für Telefon, aber aus irgendeinem Grund, wenn der "E-Mail:" Knoten erscheint, sind sowohl NextSibling.InnerHTML
& NextSibling.InnerText
leer, obwohl das nächste Geschwister definitiv die Daten hat. Wenn ich in details
zu diesem node
gehe und ihn InnerHTML
ist InnerHTML
der vollständig formatierte Link und InnerText
die E-Mail-Adresse.
Funktioniert der NextSibling.InnerText
nicht, weil das A-Tag es zu einem Kind oder etwas macht? Ich habe einen Blick in den Debugger NextSibling
und kann nicht die Informationen finden, die ich unter NextSibling
.
Ich bin mir sicher, die Antwort ist lächerlich einfach, ich kann es einfach nicht herausfinden. Jemand hat mich aus meinem Elend vertrieben?
Der Grund dafür ist, dass, wenn der node
ein dt
Element ist, das durch ein Leerzeichen von seinem entsprechenden dd
Element node.NextSibling
ist, node.NextSibling
ein node.NextSibling
mit ganz aus Leerzeichen ist (der Abstand zwischen dem </dt>
und dem <dd>
). Wenn Sie es im Debugger betrachten, sehen Sie, dass der node.NextSibling
NodeType
HtmlNodeType.Text
und nicht HtmlNodeType.Element
.
Ich schlage vor, eine Bequemlichkeitsmethode zu erstellen, um den Text eines dt
Knoten entsprechenden dd
:
internal static string GetMatchingDdValue(HtmlNode dtNode)
{
var found = dtNode.SelectSingleNode("following-sibling::*[1][self::dd]");
return found == null ? "" : found.InnerText;
}
Dann kannst du es so benutzen:
if (node.InnerText.Contains("Tel:")) { telephone = GetMatchingDdValue(node); }
Hier ist eine Zusammenfassung des etwas schwierigen XPath, der in meiner Methode oben verwendet wurde:
(a) following-sibling::*
^ Wählen Sie alle Elemente aus, die das gleiche Elternteil wie der aktuelle Knoten haben und danach auftreten.
(b) following-sibling::*[1]
^ Wählen Sie den ersten Knoten im Set (a) (falls vorhanden)
(c) following-sibling::*[1][self::dd]
^ Wählen Sie alle Knoten in Menge (b), die Elemente mit dem Namen "dd" sind
SelectSingleNode()
wählt den ersten Knoten in Menge (c) aus, der immer entweder 1 oder 0 Knoten sein sollte.
Sie könnten höchstwahrscheinlich mit nur following-sibling::dd
oder following-sibling::*
auskommen, aber der obige Pfad enthält Sicherheitsvorkehrungen. Zum Beispiel, wenn Sie aus irgendeinem Grund die folgende XML hatten und Ihr aktueller Knoten das Element Tel:
:
<dl>
<dt>Tel:</dt>
<dt>Address:</dt>
<dd>50 Fake St.</dd>
</dl>
following-sibling::dd
würde Ihnen das Ergebnis "50 Fake St." geben, während following-sibling::*
Ihnen das Ergebnis "Address:" geben würde. Stattdessen würde following-sibling::*[1][self::dd]
in diesem Fall einen leeren Knotensatz auswählen, sodass die Methode als Ergebnis eine leere Zeichenfolge erzeugen würde.
var html = @"
<div id='id-here'>
<dl>
<dt> Field Name </dt>
<dd> Value for above field name </dd>
<dt> Field Name </dt>
<dd> Value for above field name </dd>
<dt> Field Name </dt>
<dd> Value for above field name </dd>
</dl>
</div>";
html = new Regex(">\r\n\\s*<").Replace(html,"><");
var doc = new HtmlAgilityPack.HtmlDocument();
doc.LoadHtml(html);
Console.Write(doc.DocumentNode.SelectNodes("//dt")[0].NextSibling.OuterHtml);
<dd> Value for above field name </dd>