Viele Daten-Fundgruben im Web oder im Intranet bereiten ihre Inhalte vor allem optisch auf - hübsche Seiten, die dem Auge des Betrachters schmeicheln. Für automatische Auswertungen braucht ein Programm die relevanten Informationen aber ohne Schnörkel und sauber in Häppchen zerlegt.
Idealerweise stellen Webserver dazu ihre Daten zusätzlich per SOAP bereit ([1], Simple Object Access Protocol). Wer diese glückliche Lage nicht vorfindet, schreibt kurzerhand eigene Tools, um die relevanten Daten aus der Seite zu extrahieren. Zwei Beispiele zeigen im Folgenden den kompletten Prozess vom Laden der Seite (was bei interaktiven Anwendungen schon eine Herausforderung ist) bis zum gezielten Aufspüren der gesuchten Daten (bei komplexen HTML-Layouts keine einfache Aufgabe).
Als Datenquelle für die Tcl-Skripte dienen die Pegelstände der Elbe bei Hamburg St. Pauli (Abbildung 1) sowie die Suchseite des Tclers Wiki (Abbildung 2).
Abbildung 1: Auf seiner Webseite veröffentlicht das BSH (Bundesamt für Seeschifffahrt und Hydrographie) unter anderem die Pegelstände der Messstation Hamburg St. Pauli. Ein Tcl-Skript extrahiert daraus die einzelnen Messwerte.
Abbildung 2: Auf der Eingangsseite des Tclers Wiki darf der Besucher nach beliebigen Stichworten suchen (rot umrahmt). Um diesen Ablauf nachzubilden, muss ein Skript die passende HTTP-Anfrage stellen.
Hergezaubert
Es gibt viele Möglichkeiten, Webseiten vom Server zu erhalten. Angefangen beim Abspeichern aus dem Browser über spezielle Programme wie Wget [2] bis zur direkten Kommunikation mit Tcl-Bordmitteln. Die Seite per Browser speichern ist sinnvoll, wenn der Anwender den Inhalt der Seite nur einmal benötigt. Erwartet die Webseite jedoch Parameter von dem Programm oder ändert sich der Inhalt häufig, dann ist das Abholen per Tcl bei weitem bequemer. Das klappt für einfache statische Seiten ebenso wie für dynamische Anfragen an einen Server, bei denen gewöhnlich der User ein Formular ausfüllt.
Auf seiner Webseite [3] stellt das Bundesamt für Seeschifffahrt und Hydrographie (BSH) die aktuellen Pegelstände der Elbe für die Station Hamburg St. Pauli bereit. Wie in Listing 1 zu sehen, ist das Laden einer Seite mit fester Adresse sehr einfach. Das Tcl-Skript verwendet dazu das »http«-Paket (Zeile 3), es gehört zum normalen Lieferumfang.
Der Befehl »http::geturl« lädt Dokumente von einem Webserver. Im einfachsten Fall genügt es, die URL anzugeben. Damit bei langsamen Netzverbindungen und überlasteten Servern das Programm nicht beliebig lange wartet, sorgt die »timeout«-Option (Zeile 10) dafür, dass der Befehl nach spätestens 10 Sekunden abbricht. Das Resultat ist in beiden Fällen ein Array mit den Server-Antworten. Es enthält neben dem Inhalt die Statusinformationen der Anfrage.
01 #!/usr/bin/tclsh
02
03 package require http
04 package require dom
05
06 ### Seite herunterladen
07 set url "http://www.bsh.de/aktdat/wvd/StPauli_pgl.htm"
08
09 # Timeout in Millisekunden
10 set abfrageID [::http::geturl $url -timeout 10000]
11
12 if [string match ok [http::status $abfrageID]] {
13 set inhalt [::http::data $abfrageID]
14 ::http::cleanup $abfrageID
15 } else {
16 puts stderr "Laden der Seite $url fehlgeschlagen"
17 puts stderr [http::status $abfrageID] [http::error $abfrageID]
18 ::http::cleanup $abfrageID
19 exit 1
20 }
21
22 ### Messwerte auslesen
23 # Überflüssiges »</form>«-Element löschen
24 set zeilen [split $inhalt "n"]
25 set out [open pegel.html w]
26 foreach zeile $zeilen {
27 if [regexp {^</form>} $zeile] {
28 puts stderr "Lösche $zeile"
29 } else {
30 puts $out $zeile
31 }
32 }
33
34 # Tidy aufrufen, um sauberes XML zu erhalten
35 if [catch {exec tidy -utf8 -i -o sauber.xml -asxhtml pegel.html} res] {
36 #puts $res
37 }
38
39 # In DOM einlesen
40 set fd [open sauber.xml]
41 set inhalt [read $fd]
42 close $fd
43 set doc [dom::parse $inhalt]
44
45 # Die Tabelle mit Pegelständen findet sich unter XPath:
46 # html/body/table/tr[5]/td/table/tr/td[2]/table/tr/td/table/tr[3]/td[2]/table[5]
47
48 # Teil 1: html/body/table/tr[5]
49 set trs [dom::selectNode $doc //html/body/table/tr]
50 set tr [lindex $trs 4]
51 # Teil 2: td/table/tr/td[2]
52 set tds [dom::selectNode $tr td/table/tr/td]
53 set td [lindex $tds 1]
54 # Teil 3: table/tr/td/table/tr[3]
55 set trs [dom::selectNode $td table/tr/td/table/tr ]
56 set tr [lindex $trs 2]
57 # Teil 3: td[2]
58 set tds [dom::selectNode $tr td]
59 set td [lindex $tds 1]
60 # Teil 4: table[5]
61 set tables [dom::selectNode $td table]
62 set table [lindex $tables 4]
63
64 ### In CSV abspeichern
65 set fd [open pegelstand.csv w]
66 puts $fd "# Tag; Monat; Zeit; Pegelnull; Seekartennull"
67 foreach zeile [lrange [dom::selectNode $table tr] 2 end] {
68 foreach zelle [dom::node children $zeile] {
69 switch [dom::node cget $zelle -nodeName] {
70 th -
71 td {
72 set text [string trim [dom::node stringValue $zelle]]
73 puts -nonewline $fd "$text;"
74 }
75 default {# andere Knoten ignorieren}
76 }
77 }
78 puts $fd ""
79 }
80 close $fd
|
Internetverbindungen arbeiten nicht immer zuverlässig, deshalb prüft in Zeile 12 der Aufruf »http::status« erst einmal den Zustand der Abfrage. Ist er okay, gibt »http::data« die vom Webserver gelieferten Daten aus. Das Programm speichert sie in der Variablen »inhalt« (Zeile 13). Im Fall eines Fehlers schreibt das Skript die Gründe für den Fehlschlag auf Stderr (falsche URL, Timeout und so weiter) und beendet sich. Nach dem Auswerten des Array löscht »http::cleanup« dessen Daten, um Speicher zu sparen. Insbesondere bei großen Dateien ist dieser Schritt ratsam.
Formularfrage
Statische Webseiten sind der einfachste Fall. Häufig präsentieren die Server ihre Informationen aber erst, nachdem der Besucher Daten in ein Formular eingetippt hat. Ein Beispiel hierfür ist das Tclers Wiki. Wie auf modernen Webseiten üblich enthält es eine Suchfunktion. Damit ein Tcl-Skript wie ein Webbrowser seine Anfrage an den Server stellen kann, muss der Programmierer die Zieladresse und die benötigten Parameter kennen. Am einfachsten erfährt er dies aus dem HTML-Code der Formularseite. Gerade bei Formularen verstecken sich die Parameter allerdings oft tief in der Formatierung.
Eingabefelder liegen bei HTML innerhalb eines »form«-Tag. Dies enthält die Definition von Eingabefeldern für Text (»input«-Tag), Comboboxen (»select«-Tag) oder Checkboxen (»input«-Tag vom Typ »radio«). Neben den Eingaben tragen manche Formulare zusätzliche Parameter, die der Benutzer nicht sieht (»input«-Tag, Typ »hidden«), der Browser aber unverändert an den Server übergibt. Eine gute Einführung in die genaue Definition von Formularen findet sich in der HTML-Bibel Selfhtml [4].