Es hat eine ganze Weile gedauert, bis ich mich an WWW::Mechanize herangetraut habe. Eigentlich ist ausreichend Dokumentation vorhanden und Perl fasse ich eh ab und zu mal an. Mir hätten ein paar mehr konkrete Beispiele, gut dokumentiert, möglichst in meiner Muttersprache gut getan. Darum – weil es anderen vielleicht auch so geht – demonstriere ich hier ein Ergebnis meiner Studien.
In meinem konkreten Beispiel geht es um das Besorgen eines persönlichen Fahrplans für Verbindungen mit der Bahn oder anderer regionaler Anbieter im öffentlichen Personennahverkehr. Ich halte das Skript auch für diejenigen für sinnvoll, die nicht WWW::Mechanize lernen wollen – um sich Fahrpläne für eigene Verbindungen erstellen zu lassen, muss man aber trotzdem ein (ganz klein) wenig in die Materie einsteigen.
Ich hoffe, das Skript hilft dem einen oder der anderen beim Einstieg in WWW::Mechanize. Trotzdem eine Warnung vorweg: ich bin nicht gut im Scripten – und ganz bestimmt nicht in Perl; ich baue mir zurecht, was mir gerade einfällt. Das Teil funktioniert hier bei mir – das heißt aber noch lange nicht, dass es keinen Verbesserungsbedarf mehr gäbe, geschweige denn ich mich an gute technische Gepflogenheiten hielte.
Über Kommentare – vor allem Verbesserungsvorschläge – freue ich mich sehr.
Für eine Fahrplanabfrage benötige ich zwei Dateien. Die erste Datei ist das Skript selber, welches die Abfrage insgesamt durchführt und die grundlegenden Einstellungen (wie zum Beispiel das gewünschte Format oder die E-Mail-Adresse) setzt. In der zweiten Datei stecken die Daten der jeweils speziellen Verbindung: Start- und Ziel-Bahnhof. Im vorgestellten Fall geht es um eine Verbindung von München nach Hamburg-Wandsbek.
Im Beispiel reicht dann auf der Kommandozeile ein besorgeFahrplan M-HH.Wandsbek um die Abfrage durchzuführen. Das kann man sich natürlich auch hübsch auf den Desktop legen – vielleicht objektorientiert, so dass man einfach die Datei mit den Verbindungsdaten per Maus auf das Skriptfile zieht und damit die Anfrage auslöst. Aber soweit bin ich noch nicht; ich benutze immer noch lieber die Kommandozeile.
Zuerst zum Skript selber, da steckt das meiste WWW::Mechanize drin. Zur Datei mit den Verbindungsdaten komme ich später, das ist dann nicht mehr aufregend.
Zur Umwandlung des Codes nach HTML habe ich source-highlight genutzt. Mit dem Aufruf source-highlight --src-lang=perl --line-number --out-format=xhtml --css=style.css besorgeFahrplan.
Zuerst konfiguriere ich mir die Anfrage zusammen:
Zumindest die Zeilen 22 bis 54 sollten einfach zu verstehen sein. Zeile 22 bestimmt die URL, an welche ich die Anfrage richte und in den Zeilen 24 bis 54 lege ich meine Formular-Eingaben in ein Hash. WWW::Mechanize kann das so direkt verarbeiten und ich finde das an Übersichtlichkeit nicht zu überbieten.
In Zeile 37 müsst Ihr Eure E-Mail-Adresse eintragen, sonst läuft das Skript nicht, das versteht sich ja hoffentlich von selbst. Wenn Ihr den Fahrplan nicht per Mail haben wollt, müsste Ihr euch das Skript – vor allem im hinteren Teil – umschreiben. In der vorliegenden Version wird es per Mail geschickt (siehe dazu Zeile 36).
Um an die Formularfelder heranzukommen hilft Euch auf der ersten Seite das WWW::Mechanize beiliegende mech-dump. Ein kurzes mech dump http://persoenlicherfahrplan.bahn.de/bin/pf/query-p2w.exe/dn zeigt Euch die Formularfelder der Seite an sowie die möglichen einzutragenden Werte. Das ist manchmal etwas frickelig, aber im Vergleich mit dem Code hier findet Ihr bestimmt schnell raus wie die Angeben zu verstehen sind.
Leider hilft mech-dump nicht über alle Hürden. Ich habe eine Beispielverbindung gewählt, deren Ziel nicht eindeutig ist und deshalb auf einer zweiten Seite nochmal spezifiziert werden muss. Da kommt Ihr nicht per mech-dump ran. Ihr müsst dann leider in den HTML-Quelltext gucken. Ein bisschen Vorbildung in diesem Bereich ist da leider unabdingbar.
Weiter im Code: Zeile 56. Mit diesem String bestätigt der Bahn-Server den Erfolg der Anfrage. Die Dauer derselben wird per regulärem Ausdruck abgegriffen. Die will ich nämlich für die Ausgabe des Skriptes (kommt später) haben.
In den Zeilen 58 bis 68 steckt ein wenig Spielerei. Ich kann die Anfrage über einen Proxy führen und den User-Agent, der an den Server übermittelt wird, faken. Über die Sinnhaftigkeit sowohl des einen als auch des anderen will ich an dieser Stelle nicht diskutieren: es geht mir lediglich um die Darstellung der Fähigkeiten von WWW::Mechanize.
Zum Nutzen bzw. Abstellen dieser Einstellungen müsst Ihr bloß die Kommentarzeichen am Zeilenanfang richtig setzen.
Zeile 71 hat weder mit der Bahn zu tun noch mit WWW::Mechanize. Ich bestimme damit lediglich die Gesprächigkeit des Skriptes. "1" informiert ein wenig über den Fortgang der Anfrage. "2" ist etwas detaillierter und mit "0" kann man die Ausgabe ganz abstellen. Dieser Wert lässt sich auf der Kommandozeile ändern.
Bevor es losgeht, noch ein wenig Kleinkram: Zeilen 77 und 78 betreffen über das ganze Skript gültige Variablen. In @AlleGewuenschtenVerbindungen stecken alle auf der Kommandozeile übergeben Verbindungsdateien (damit man sich mit einem Aufruf gleich mehrere Fahrpläne besorgen lassen kann) und in $AktuelleVerbindung steckt die, die während des Programmablaufs gerade aktuelle bearbeitet wird. @VerbindungsAngaben bekommt ein our, weil diese Variable in der externen Datei befüllt wird.
im Folgenden werde ich hier weniger die Innereien des Skriptes referieren, sondern mich hauptsächlich auf die Teile zu WWW::Mechanize beschränken. Die allgemeinen Teile sind (hoffentlich) im Quelltext ausreichend dokumentiert,
Nun endlich, in Zeile 80, kommt WWW::Mechanize zum Einsatz. Ich setze autocheck => 1. Sollten Netzzugriffe im Skript scheitern, bricht WWW::Mechanize die Verarbeitung ab. Ich muss mich im Skript also nicht selbst um die Fehlerbehandlung kümmern.
In den Zeilen 81 und 82 werden die oben gemachten Einstellungen – so es denn welche gibt – zu Proxy und User-Agent an WWW::Mechanize übergeben.
Jetzt doch noch ein wenig Skript-Interna: Zeile 154 liest die Daten zu Start- und Zielbahnhof aus der dem Skript auf der Kommandozeile übergebenen Datei aus. In Zeile 156 wird ausgelesen, wie viele Seiten dazu ausgefüllt werden müssen. In der Regel reichen dazu die Formular-Angaben auf der ersten Seite. Es kann aber durchaus vorkommen, dass sich partout nicht der exakt passende Eintrag finden lässt und der Bahn-Server eine genauer spezifizierte Angabe haben möchte und auf einer zweiten Seite ein Auswahlliste vorschlägt. Wieviele Seiten insgesamt auszufüllen sind, wird in $AnzahlSeiten gespeichert. Mehr als zwei sind mir dabei aber auch noch nicht unter gekommen.
Endlich tut sich etwas: in Zeile 160 wird die Bahn-Seite vom Server geholt und getestet, ob die Anfrage erfolgreich war. Wenn es eine Fehlermeldung gegeben hat, wird diese in Zeile 164 ausgegeben. Der else-Abschnitt ist ein Überbleibsel der ersten Version des Skriptes, als ich den Wert von autocheck noch auf "0" gesetzt hatte (siehe Zeile 80) – da in der aktuellen Fassung des Skriptes WWW::Mechanize die Fehlerbehandlung selbst übernimmt, ist der else-Fall nicht mehr von Bedeutung.
Jetzt zu dem, was WWW::Mechanize ausmacht: Formulare ausfüllen. In Zeile 176 werden die Daten der aktuellen Verbindung für die gerade zu bearbeitende Seite (also in der Regel der ersten, selten der zweiten – der mit der Auswahlliste bei mangelnder Eindeutigkeit eines Bahnhofes) im Hash %Seiteneingaben gespeichert.
Zeile 179 weist WWW::Mechanize an, die folgenden Werte in das zweite Formular der Seite einzutragen (das erste Formular – was Ihr ja mit z.B: mech-dump – herausgefunden habt) ist für die Sprachwahl der Seite zuständig.
Zeile 184 setzt die im Konfigurationsabschnitt des Skriptes festgelegten Standardwerte und Zeile 189 die für die angefragte Verbindung auf der gerade zu bearbeitenden Seite notwendigen Daten.
In Zeile 193 schließlich wird das ausgefüllte Formular an den Server abgeschickt – und auf Erfolg geprüft wird. Falls diese ausbleibt, endet die Abarbeitung des Skript mit einer Ausgabe des Fehlers in Zeile 200.
Dieser Teil steckt in einer Schleife, die dann beendet ist, wenn alle Anfrageseiten abgearbeitet sind. Üblicherweise schickt nun der Bahn-Server die Seite mit der Erfolgsmeldung zum erstellten Fahrplan zurück.
WWW::Mechanize soll sich auch um das Ergebnis kümmern und ausgeben, wie lange die Erstellung des Fahrplanes dauern soll. Dazu wird die vom Bahn-Server zurückgegebene Seite in Zeile 205 in $Seite gespeichert und in Zeile 208 auf den im Konfigurationsabschnitt des Skriptes bestimmten String getestet. Ist dieser vorhanden, wird die Dauer der Erstellung extrahiert und in Zeile 214 ausgegeben.
Ist der entsprechende String nicht vorhanden, wird die vom Bahn-Server zurückgegebene Seite in den Zeilen 218 bis 221 zur Analyse in eine Datei gespeichert (vielleicht wurde ja bloß der Satz geändert) und in Zeile 222 über den Fehlschlag auf der Kommandozeile informiert.
Damit ist das Skript beendet und die Anfrage sollte mit dem Eintreffen des Fahrplanes auf Euren Mail-Konto nach $Minuten Minuten abgeschlossen sein.
Wie oben erwähnt, benötigt das Skript eine externe Datei, in welcher die Angaben einer konkreten Verbindung gespeichert sind. Ich lasse mir in diesem Beispiel einen Fahrplan für die Strecke von München nach Hamburg erstellen. (In meiner Erinnerung ging es in unseren Schulbüchern früher in solchen Fällen immer um die Strecke München–Hamburg.) Um zu demonstrieren, wie das mit der Auswahlliste auf der Folgeseite funktioniert, weil die Bahnhofsangabe auf der ersten Seite nicht eindeutig genug für den Bahn-Server war, habe ich als Zielbahnhof »Wandsbek« ausgesucht.
Was auf den ersten Blick ganz nett aussieht, erweist sich bei näherem Hinsehen als doch etwas verwickelt. Die Klammern nämlich. Ich habe lange gedacht, dass es sich bei @VerbindungsAngaben um ein Array aus Hashes handelt. Peter wies mich aber per Mail darauf hin, dass es sich stattdessen um ein Array (eine Liste) von Hash-Referenzen handelt. Man kann – glaube ich – ganz gut sehen wie es funktioniert: die Angaben in den geschwungenen Klammern sehen genauso aus wie diejenigen im Konfigurationsteil des Skriptes. Und von diesem in geschwungenen Klammern steckenden Abschnitten gibt es genau zwei. Einen für die erste Seite der Anfrage und einen für die zweite Seite mit der Auswahlliste für die nicht eindeutige Bahnhofsangabe. Darum auch der komische Wert S-1N4 – das ist einfach der vierte Eintrag in der Liste. (Das müsst Ihr bei Euren eigenen Versuchen leider mit einem Blick auf den HTML-Quelltext der Seite herausfinden.)
Wenn – was in den meisten Fällen der Fall sein dürfte – zur Bearbeitung der Anfrage die Daten der ersten Seite reichen, entfallen ganz einfach die Zeilen 5 bis 7. (Eigentlich sind es natürlich die Zeilen 6 bis 8 – aber dann müsste ich noch extra darauf hinweisen, dass das Komma am Ende von Zeile 5 noch weg muss; und das war mir einfach zu stressig.)
Noch ganz kurz zu den Zeilen 13 bis 16, speziell Zeile 15 der Datei. Das sind Angaben für den Emacs. Zeile 15 weist diesen an, die Datei bei Speichern in ISO-8859-1 zu kodieren. Versuche haben gezeigt, dass es mit UTF-8 aber auch keine Probleme gibt.
Ich habe sowohl das Skript als auch die Datei mit den Verbindungsdaten für München–Hamburg in ein ZIP-File gepackt.
12.12.2011: Formularnummer in Zeile 179 an die Veränderungen auf der Bahnseite angepasst (jetzt: 3).
16.12.2008: Formularnummer in Zeile 179 an die Veränderungen auf der Bahnseite angepasst (jetzt: 4).
8.10.2008: Erklärung zu @VerbindungsAngaben: Es handelt sich nicht um ein Array aus Hashes, sondern um ein ein Array von Hash-Referenzen. Dank an Peter.
1.10.2008: In den
Standardangaben im Skript wurden die Keys für die Verkehrmittel
an die geänderten Bezeichnungen des Formulars angepasst.
Der autocheck-Wert wird per default
auf "1" gesetzt.
3.10.2007: Im Verbindungsdatenfile wurden die Keys für An- und Abfahrtbahnhöfe an das geänderte Formular bei der Bahn angepasst. Ebenso der Wert im Auswahlfeld auf Seite 2.