Glengamoi (Forum) · AspHeute · .NET Heute (RSS-Suche) · AspxFiles (Wiki) · .NET Blogs

Scrapen von Webseiten

Geschrieben von: Christoph Wille
Kategorie: ASP.NET

This printed page brought to you by AlphaSierraPapa

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 Ergebnis

Die 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 Ziel

Unser 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ßbemerkung

In 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 ;-).

This printed page brought to you by AlphaSierraPapa

Download des Codes

Klicken Sie hier, um den Download zu starten.
http://www.aspheute.com/code/20000824.zip

Verwandte Artikel

.NET Komponenten in COM+ Clients einsetzen
http:/www.aspheute.com/artikel/20020702.htm
Amazon.com Web Services 2.0
http:/www.aspheute.com/artikel/20021029.htm
Erstellen eines HTTP Test Tools
http:/www.aspheute.com/artikel/20000508.htm
Exception Handling in C#
http:/www.aspheute.com/artikel/20000724.htm
Laden von Dateien aus dem Web mit ASP
http:/www.aspheute.com/artikel/20000519.htm
SMS versenden in .NET
http:/www.aspheute.com/artikel/20010912.htm
Sonderzeichen korrekt grabben mit XmlHttp
http:/www.aspheute.com/artikel/20011113.htm
Webpage-Grabbing mit dem XML Parser
http:/www.aspheute.com/artikel/20010328.htm
Webseiten automatisiert scrapen
http:/www.aspheute.com/artikel/20010910.htm
Webseiten automatisiert scrapen, Teil 2
http:/www.aspheute.com/artikel/20010911.htm
WHOIS Abfragen a la .NET
http:/www.aspheute.com/artikel/20000825.htm

Links zu anderen Sites

AspHTTP
http://www.serverobjects.com/
AspTear
http://www.alphasierrapapa.com/componentcenter/asptear/

 

©2000-2006 AspHeute.com
Alle Rechte vorbehalten. Der Inhalt dieser Seiten ist urheberrechtlich geschützt.
Eine Übernahme von Texten (auch nur auszugsweise) oder Graphiken bedarf unserer schriftlichen Zustimmung.