Scrapen von Webseiten
Geschrieben von: Christoph Wille In Webanwendungen ist es mittlerweile immer öfter nötig, während der Abarbeitung einer Seite auf Informationen von anderen Sites zuzugreifen: zum Beispiel für Kreditkartenautorisierung, aktuelle Wetterwerte abholen (und in das eigene Seitendesign einbauen), und vieles mehr. In ASP war das nur mit Zusatzkomponenten möglich (zB AspTear oder AspHTTP). In ASP.NET im Gegensatz kann man mit den Bordmitteln des .NET Frameworks sehr wohl selbst Hand anlegen. In diesem Artikel zeige ich den ersten Schritt: das einfache Aufrufen und Auslesen eines URL's. Voraussetzung um den Sourcecode dieses Artikels verwenden zu können ist eine Installation des Microsoft .NET Framework SDK's auf einem Webserver. Weiters setze ich voraus, daß der Leser die Programmiersprache C# zu einem gewissen Grad beherrscht - es finden sich etliche Artikel auf diesem Server, um das notwendige Wissen zu erlernen. Der schnelle Weg zu einem ErgebnisDie Klassen, die wir benötigen, finden sich in drei verschiedenen Namespaces:
Der System.Net Namespace beinhaltet die Klassen, die die Web Requests (und deren Responses) kapseln. Das Auslesen der Response wird über die verschiedenen Stream Klassen erledigt, die sich im System.IO Namespace befinden. Und schließlich müssen wir uns auch noch um das Encoding des Contents (ASCII, ANSI, etc) annehmen - und dies wird uns durch Klassen im System.Text Namespace abgenommen. Da wir nun im Groben geklärt haben was wir ungefähr brauchen, schauen wir uns den Sourcecode (scrap.aspx) an: <% @Page Language="C#" %> <% @Import Namespace="System.Net" %> <% @Import Namespace="System.Text" %> <% @Import Namespace="System.IO" %> <% WebRequest wrq = WebRequest.Create("http://www.aspheute.com/default.asp"); WebResponse wrp = wrq.GetResponse(); StreamReader sr = new StreamReader(wrp.GetResponseStream(), Encoding.ASCII); while (-1 != sr.Peek()) { Response.Write(sr.ReadLine()); } %> Die "angedrohten" Namespace Imports stehen ganz oben in der ASP.NET Datei. Die erste Anwendung erhält der System.Net Namespace in der ersten Zeile des Codes: dort verwende ich die statische Methode Create um eine WebRequest Instanz für die angegebene URL zu erzeugen. Dieser URL-Request wird sofort durchgeführt, und ich kann mir den Response abholen (mittels dem WebResponse Objekt). Und jetzt kommen wir zum System.IO Namespace: die Methode GetResponseStream liefert uns ein Stream Objekt, das man zum Auslesen der Daten verwenden kann. Allerdings hat das Stream Objekt einen "Nachteil": man müßte mit Byte Arrays arbeiten. Da mir das nicht wirklich zusagt, habe ich einen StreamReader herum"gewrapt". Der StreamReader hat noch weitere Vorteile: ich kann ihm sagen, wie die Daten umzuwandeln sind. Diese Umwandlung übernimmt die Encoding.ASCII Klasse, die im System.Text Namespace zu Hause ist. Damit hätten wir alle Namespaces auch schon hinter uns gebracht. Apropos, noch einen weiteren Vorteil hat die StreamReader Klasse: man kann zeilenweise auslesen. Weil wir gerade beim Auslesen sind: wie weiß ich, daß keine Daten mehr abzuholen sind? Dazu kann man die Peek Methode verwenden - diese liefert -1, wenn keine Daten mehr da sind, löscht aber während des Vorgangs des "Peekens" das gepeekte Zeichen nicht aus dem Puffer. Das Auslesen besorgt die ReadLine Methode - deren String Output ich aber sofort an Response.Write übergebe, damit der User auch was sieht. Man bekommt als Output also wieder ein vollständiges HTML Dokument von der Gegenstelle, allerdings werden die abhängigen Objekte - Style Sheets, Grafiken, etc. - nicht korrekt angezeigt, da die Relativpfade nicht mehr stimmen. Der elegante Weg zum ZielUnser Beispiel funktioniert zwar, allerdings hat es so einige Mäkel: kein Userinterface in dem man die URL eingeben könnte, und ich schreibe den Response sofort zum Client. Das folgende Beispiel räumt mit beiden Punkten auf, und sieht wie folgt aus: Die Hauptänderungen am Sourcecode sind daß ich jetzt ein Web Form verwende, und das "Scrapen" der Seite in einer Funktion namens ScrapPage abarbeite. Sonst ist der Sourcecode im Grunde von der Funktion her gleich geblieben (scrap2.aspx). <% @Page Language="C#" %> <% @Import Namespace="System.Net" %> <% @Import Namespace="System.Text" %> <% @Import Namespace="System.IO" %> <SCRIPT LANGUAGE="C#" RUNAT="SERVER"> public void ScrapPage(String strPage, out String strContent) { WebRequest wrq = WebRequest.Create(strPage); WebResponse wrp = wrq.GetResponse(); StreamReader sr = new StreamReader(wrp.GetResponseStream(), Encoding.ASCII); StringBuilder strBuildContent = new StringBuilder(); while (-1 != sr.Peek()) { strBuildContent.Append(sr.ReadLine()); } strContent = strBuildContent.ToString(); } void btnClick_Event(Object sender, EventArgs e) { String strContent; ScrapPage(theURL.Text, out strContent); RetVal.Text = strContent; } </SCRIPT> <html> <head> <title>Scraping Web Pages</title> </head> <body> <form method="post" runat="server"> Webadresse: <asp:textbox id="theURL" size="40" runat="server" value="http://www.aspheute.com/default.asp" /> <asp:button id="btnSubmit" runat="server" text="Scrap it!" OnClick="btnClick_Event" /> <BR><HR width="100%"><BR> <asp:label id="RetVal" runat="server" /> </form> </body> </html> Die Funktion ScrapPage nimmt 2 Parameter: den URL der Seite, die geholt werden soll, sowie einen out Parameter, über den der Inhalt der geholten Seite an den Aufrufer zurückgegeben wird. Eine weitere Änderung in der Funktion ist daß die Daten zuerst in einem StringBuilder Objekt zwischengespeichert werden, und erst zum Schluß auf die String Variable zugewiesen wird. Der Grund ist, daß das StringBuilder Objekt speichereffizienter arbeitet, wenn man mehrere Zuweisungen durchführt. Das Web Form hat eine TextBox (für die Eingabe der URL), einen Submit-Button (der am Server das Event btnClick_Event auslöst), sowie eine Label Control. Diese Label Control dient mir in diesem Beispiel als Ausgabeplatz für das abgeholte HTML. Dies ist zwar nicht 100%ig korrekt (wegen der doppelten BODY und HTML Tags), funktioniert aber. Eine Sache gehört aber noch geändert - wenn die URL nicht existiert, oder beim Lesen der Daten Fehler auftreten, dann setzt es eine Exception. Man sollte den entsprechenden Code also in einen try-catch Block einbauen: public void ScrapPage(String strPage, out String strContent) { try { WebRequest wrq = WebRequest.Create(strPage); WebResponse wrp = wrq.GetResponse(); StreamReader sr = new StreamReader(wrp.GetResponseStream(), Encoding.ASCII); StringBuilder strBuildContent = new StringBuilder(); while (-1 != sr.Peek()) { strBuildContent.Append(sr.ReadLine()); } strContent = strBuildContent.ToString(); } catch(Exception e) { strContent = e.ToString(); return; } } SchlußbemerkungIn diesem Artikel habe ich nur an der Oberfläche des Scrapings gekratzt - ich habe keine Daten gePOSTet, keine Binärdaten abgeholt, keine Proxies überwunden und schon gar nicht Inhalte aus den abgeholten Daten herausgeparst. Aber schließlich muß mir ja noch Stoff für weitere Artikel übrigbleiben ;-). Download des CodesKlicken Sie hier, um den Download zu starten. Verwandte Artikel
.NET Komponenten in COM+ Clients einsetzen Links zu anderen SitesWenn Sie jetzt Fragen haben...Wenn Sie Fragen rund um die in diesem Artikel vorgestellte Technologie haben, dann schauen Sie einfach bei uns in den Community Foren der deutschen .NET Community vorbei. Die Teilnehmer helfen Ihnen gerne, wenn Sie sich zur im Artikel vorgestellten Technologie weiterbilden möchten. Eine weitere sehr hilfreiche Resource ist das deutsche ASP.NET Wiki, das als zentrale Anlaufstelle für Tips, Tricks, Know How und alles Nützliche was man in seinem Alltag als (ASP).NET-Entwickler so braucht und entdeckt gedacht ist. Haben Sie Fragen die sich direkt auf den Inhalt des Artikels beziehen, dann schreiben Sie dem Autor! Unsere Autoren freuen sich über Feedback zu ihren Artikeln. Ein einfacher Klick auf die Autor kontaktieren Schaltfläche (weiter unten) und schon haben Sie ein für diesen Artikel personalisiertes Anfrageformular.
Und zu guter Letzt möchten wir Sie bitten, den Artikel zu bewerten. Damit helfen Sie uns, die Qualität der Artikel zu verbessern - und anderen Lesern bei der Auswahl der Artikel, die sie lesen sollten.
©2000-2006 AspHeute.com |