Oder wie analysiere ich eine gerenderte Seite?
Im letzten Artikel habe ich bereits erwähnt, dass in diesem Artikel die Analyse der einzelnen Seiten mit der Klasse SiteAnalyzer näher beleuchtet wird. Wie das folgende Klassendiagramm bereits erahnen lässt, ist die Verwendung denkbar einfach.
Der parameterlose Konstruktor ist nur enthalten um bei einem Type-Cast keine Probleme zu verursachen. Verwendet wird eigentlich nur die Überladung, bei der die Uri der zu analysierenden Seite mit angegeben wird. In der Eigenschaft TitleDelimeter kann ein Zeichen angegeben werden, welches den Titel trennen kann, da in vielen Blogs und Websites der Name des Blog oder der Site dem eigentlichen Titel vorangestellt wird.
Die eigentliche Analyse wird mit der Methode AnalyzeSite ausgeführt.
Nach erfolgter Analyse enthalten die Eigenschaften SiteTitle und SiteContent den Titel der Seite bzw. den Inhalt ohne jegliches HTML. Falls die Seite über keinen Titel verfügt, oder dieser nicht ermittelt werden kann, wird die Uri der Seite als Titel zurückgegeben.
Die beiden privaten Felder body und title, vom Typ RegEx, halten die Instanzen der regulären Ausdrücke um den Inhalt und den Titel-Tag der Seite zu finden. Die privaten Methoden GetContent und GetTitle extrahieren sowohl den Titel als auch den Inhalt der Seite mit Hilfe der beiden RegEx-Instanzen und werden ihrerseits von der öffentlichen Methode AnalyzeSite verwendet. Soweit zur Theorie.
Zur Definition des RegEx für den Title-Tag dürfte nicht sehr viel zu sagen sein. Nur soviel, alle Tags in einer Seite können sowohl auf eine einzelne Zeile begrenzt, als auch über mehrere Zeilen erstreckt sein. Das sollte bei der Initialisierung der RegEx-Instanzen beachten werden. Genauso sollte auf die Beachtung der Groß- und Kleinschreibung verzichtet werden.
Für die Bestimmung des Content der Seite, habe ich mich allerdings eines kleinen Kniffs bedient. Da die Suche in einem Web läuft welches ich selbst erstellt habe, kann ich auch im Markup mittels eines Kommentar diesen als solchen kennzeichnen.
<!-- begin SiteContent -->
<asp:ContentPlaceHolder ID="ContentBody" runat="server" />
<!-- end SiteContent -->
Wie im obigen Listing zu sehen ist, wird in der MasterPage der ContentPlaceHolder zwischen zwei Kommentare gesetzt und ist so im gerenderten HTML sehr leicht zu finden. Diese Vorgehensweise funktioniert natürlich nur, wenn man auch Zugriff auf den Quelltext der Webanwendung hat. Mit diesem Vorgehen, lässt sich der eigentliche Inhalt der Seite mit einem einfachen RegEx finden.
private static Regex body = new Regex(
"<!-- begin SiteContent -->(.*)<!-- end SiteContent -->",
RegexOptions.IgnoreCase |
RegexOptions.Multiline |
RegexOptions.Singleline |
RegexOptions.CultureInvariant);Bevor aber der Inhalt gesucht werden kann wird erst einmal eine Text benötigt, in dem gesucht wird. Hierzu wird einfach die Seite in einem Stream von einem HttpWebResponse-Objekt gelesen und in einem string gespeichert.
public void AnalyzeSite()
{
var request = (HttpWebRequest)WebRequest.Create(this.SiteUrl);
request.UserAgent = "klaus_b@.NET SiteAnalyzer";
string rawContent = null;
var response = (HttpWebResponse)request.GetResponse();
var responseStream = response.GetResponseStream();
using (var reader = new StreamReader(responseStream, Encoding.UTF8))
{
rawContent = reader.ReadToEnd();
}
response.Close();
responseStream.Dispose();
if (string.IsNullOrEmpty(rawContent))
{
return;
}
string title = SiteAnalyzer.GetTitle(
rawContent,
this.TitleDelimeter);
this.SiteTitle = title != null ? title : this.SiteUrl.ToString();
this.SiteContent = SiteAnalyzer.GetContent(rawContent);
}Wenn der String rawContent nichts enthält wird die Methode beendet, da eine weitere Verarbeitung sinnlos währe. Wenn Daten enthalten sind, liegt der komplette Inhalt der Seite in einem sehr rohen Format vor. Soll heißen: alle HTML-Tags sind noch enthalten. Jetzt wird als erstes versucht, den Title-Tag zu finden. Dies geschieht mit der privaten statischen Methode GetTitle, die den Inhalt einer Seite und ein eventuelles Trennzeichen als Parameter erwartet. Das Trennzeichen kann auch null sein, wenn keines verwendet wird.
private static string GetTitle(string siteContent, string delimeter)
{
string value = null;
Match match = SiteAnalyzer.title.Match(siteContent);
if (!match.Success)
{
// kein Titel gefunden
return value;
}
// Titel gefunden value = match.Groups[1].Value;
if (!string.IsNullOrEmpty(delimeter))
{
int index = value.IndexOf(delimeter, StringComparison.OrdinalIgnoreCase) + 1;
if (index >= 0)
{ value = value.Substring(index, value.Length - index);
}
} value = value.Replace("\r\n", string.Empty) .Replace("\t", string.Empty);
return HttpUtility.HtmlDecode(value).Trim(); ;
}Dann wird der zurückgegebene Wert auf null geprüft. Wenn der Wert null ist, wird statt des Titel die URL der Seite zurückgegeben. Zu sehen in Zeile 107.
Nachdem der Titel verarbeitet wurde, kommt jetzt der Inhalt an die Reihe.
private static string GetContent(string siteContent)
{
string value = null;
Match match = SiteAnalyzer.body.Match(siteContent);
if (!match.Success)
{
return value;
} value = match.Groups[1].Value; value = Utils.StripHtml(value); value = value.Replace("\r\n", string.Empty) .Replace("\t", string.Empty) .Replace(" ", " ") .Replace(" ", " ") .Replace(" ", " ") .Replace(" ", " ") .Replace(" ", " "); value = HttpUtility.HtmlDecode(value).Trim();
return value;
}Zunächst wird der Bereich des relevanten Inhalt mittels des weiter oben beschrieben regulären Ausdrucks ermittelt und in der Variablen value als String gespeichert. Das entfernen der HTML-Tags geschieht, in Zeile 124 zu sehen, mit Hilfe der Methode StripHtml aus der Hilfsklasse Utils. In dieser Klasse sind mehrere Methoden und Eigenschaften enthalten, die ich immer wieder an den verschiedenen Stellen benötige. Die Methode StripHtml entfernt die Tags mittels eines einfachen RegEx.
/// <summary>
/// Hält eine Regex-Instanz zum entfernen von jeglichem Html
/// aus einer Zeichenfolge.
/// </summary>
/// <remarks>n/a</remarks>
private static readonly Regex htmlStrip = new Regex(
"<[^>]*>",
RegexOptions.Multiline |
RegexOptions.Singleline |
RegexOptions.Compiled);Dabei werden einfach alle möglichen Tags durch das Feld Empty der String-Klasse ersetzt.
/// <summary>
/// Entfernt jegliches Html aus einer Zeichenfolge.
/// </summary>
/// <param name="html">
/// Die zu bereinigende Zeichenfolge.
/// </param>
/// <returns>
/// Eine Zeichenfolge ohne jegliches Html.
/// </returns>
/// <remarks>n/a</remarks>
public static string StripHtml(string html)
{
if (string.IsNullOrEmpty(html))
{
return string.Empty;
}
return Utils.htmlStrip.Replace(html, string.Empty);
}Anschließend werden noch Zeilenumbrüche, Tabulatorzeichen und überzählige Leerzeichen entfernt. Jetzt braucht die Zeichenfolge nur noch HTML-dekodiert zu werden und es liegt ein sauber lesbarer Text vor.
Soweit ist die Erstellung des Katalog mit lesbaren Abbildungen der Seiten des Web abgeschlossen. Im nächsten Artikel werde ich mich mit der Ausführung einer Suche und der Bewertung des Ergebnis der Suche befassen.
Wenn ihnen der Artikel gefallen hat oder er für sie hilfreich war, bitte "kicken" sie ihn.
