Die WWW::Mechanize-Shell hilft, sogenannte Screen-Scraper zu schreiben, die wie Browser agieren und den Zugriff auf Webseiten automatisieren.
In Kalifornien darf man sich das Nummernschild für's Auto gegen eine Gebühr selbst aussuchen. Aus naheliegenden Gründen führt mein 13 Jahre alter Acura Integra das Kennzeichen ``PERL MAN'' (Abbildung 1). Die Kraftfahrzeugbehörde gibt sich fortschrittlich und bietet eine Webseite an, die prüft, ob das gewünschte Kennzeichen verfügbar ist -- allerdings geht mir das viel zu langsam. Heute untersuchen wir mal, wie man einen doch recht komplexen Web-Flow in Perl-Geschwindigkeit nachbildet.
Abbildung 1: Der erstaunliche PERL MAN: Schon 13 Jahre alt, kann es aber nicht lassen, die Sportwagen an den Ampeln San Franciscos zu provozieren. |
Für solche Screen-Scraper-Projekte hat Andy Lester das Modul
WWW::Mechanize
entwickelt. Neuerdings steht mit WWW::Mechanize::Shell
von Max Maischein sogar eine Shell zur Verfügung, mit der man eine
Browser-Session sehr schnell in ein beliebig oft reproduzierbares
Skript umwandelt. Die Beschreibung der Session erfolgt auf logischer
Ebene (z.B. ``springe zum einem Link auf der Seite, der den
Text 'xxx' enthält''),
sodass das Skript auch dann noch funktioniert, wenn das Format der
Webseite sich ändert.
Eine CPAN-Shell installiert schnell die Module WWW::Mechanize und WWW::Mechanize::Shell, die ihrerseits von weiteren Modulen abhängen, was allerdings dank der Shell-Automatik komfortabel von statten geht. Die Browser-Emulator-Shell startet dann mit
perl -MWWW::Mechanize::Shell -eshell
und wartet mit einem Prompt auf interaktive Benutzereingaben. Bei der Installation zieht das Modul noch einige Abhängigkeiten herein und benötigt IO::Socket::SSL, um mit https-Seiten zurechtzukommen.
Um die Seite der kalifornischen Kraftfahrzeugstelle Department of Motor Vehicles zu laden, tippt man einfach
>get http://www.dmv.ca.gov
ein und die Shell antwortet
Retrieving http://www.dmv.ca.gov(200)
um zu signalisieren, dass die Startseite mit Code 200 (OK) erfolgreich geladen wurde.
Abbildung 2: Die Eingangsseite des kalifornischen Department of Motor Vehicles mit dem Link auf die personalisierten Nummernschilder. |
Die Browser-Darstellung (Abbildung 2) zeigt den gesuchten Link zu Personalized Plates. Um herauszufinden, welche Links die Shell auf der Seite erkannt hat, genügt das Kommando
>links
das alle verfügbaren Links anzeigt:
... [14] Vehicle Industry & Commercial Permits [15] Personalized Plates [16] Disabled Placards ...
Nun könnten wir einfach Link Nummer 15 mit
open 15
folgen, aber das wäre nur begrenzt gültig, da die Anzahl und
Reihenfolge der Links auf einer regelmäßig gewarteten Seite sich
stetig ändern. Um den Vorgang zu generalisieren und auch dann noch
funktionsfähig zu
halten, falls die Behörde neue Links in die Seite einfügt, sucht
folgender Befehl einfach nach einem Link, dessen Text-Beschreibung
Personlized
enthält und ``klickt'' auf ihn:
open /Personalized/
Passen auf den angegebenen regulären Ausdruck mehrere Links der Seite, stellt die Shell ein Auswahlmenü dar. Im vorliegenden Fall passt nur einer, die Shell quittiert dies mit
Found 15 (200)
und befindet sich nun auf einer Seite, die weitere Links anzeigt, unter anderem einen, der den Text ``order Special Interest Plates'' enthält. Die Suche nach dem regulären Ausdruck (wichtig: Anführungszeichen wegen der Leerzeichen):
open "/order Special Interest/"
führt zu einer weiteren Seite, auf der sich mehrere HTML-Formulare befinden, die sich einfach durch den Befehl
forms
... Form [2] POST https://vrir.dmv.ca.gov/ipp/PerLicensePlateServlet [personalized] page=Select (hidden) Submit2=Order Personalized (submit) ...
anzeigen lassen. Das zweite (mit der Bezeichnung ``personalized'') ist das interessante, das wir dem Namen nach auswählen und auf den Submit-Knopf drücken:
form "personalized" submit
Nun zeigt die Behörde ein weiteres Formular, um mit Drop-Down-Menüs und
Radio-Buttons Fahrzeugtyp, Nummernschildmuster und ähnliches auszuwählen.
Für solche komplizierten Formulare eignet sich der fillout
-Modus der
Shell, der die Werte für die einzelnen Formularfelder interaktiv vom
Benutzer entgegennimmt, mit dem Kommando fillout
startet, und einen
Dialog nach Abbildung 3 führt. Der Radio-Button bleibt deaktiviert,
weil einfach die Enter-Taste ohne weitere Eingabe gedrückt wird.
Ein abschließender submit
-Befehl
veranlasst die Shell, die eingestellten Werte an den Server zu schicken.
Abbildung 3: Der Befehl fillout in der WWW::Mechanize::Shell nimmt vom Benutzer Werte für die einzelnen Felder entgegen |
Endlich befinden wir uns nun auf der Seite, die die Auswahl des Nummernschilds
zulässt. Wie die Browser-Darstellung in Abbildung 4 zeigt, erfolgt dies
wiederum über ein HTML-Formular, das die Buchstaben des Nummernschildes
einzeln (!) in Auswahlboxen abfragt. Wiederum erfüllt ein fillout
seinen
Zweck und ein submit
schickt die Daten.
Abbildung 4: Auf der Webseite des DMV kann man sein zukünftiges Nummernschild begutachten. |
Und nun kommt der Clou: Um aus dieser ``Session'' ein Skript zu generieren, das man an die privaten Erfordernisse anpassen und beliebig oft ablaufen lassen kann, genügt der Befehl
script
in der Shell! Kaum eingetippt, schon spuckt die treue Seele ein Perl-Skript aus, das genau das reproduziert, was bisher in Handarbeit eingegeben wurde.
Listing dmv
ist so entstanden. Freilich wurde es noch angepasst: So
kann man nun das zu untersuchende Nummernschild auf der Kommandozeile
mit etwa
dmv PERLMAN
angeben. Zeile 12 bricht ab, falls dort nichts vorliegt. Der Konstruktor
des WWW::Mechanize-Objekts mit gesetzter autocheck
-Option betreibt den
Browser-Simulator in einem Modus, der sofort das Programm abbricht, falls
eine Webseite nicht gefunden wird oder sonst etwas schiefgeht.
Zeile 19 dirigiert den Simulator zur Eingangsseite. Die follow()
-Methode
in Zeile 20 springt wegen des angegebenen regulären Ausdrucks
auf einen Link, der den Text Personalized
enthält.
Zeile 21 führt einen weiteren, regex-gesteuerten Sprung durch.
Zeile 23 wählt das Formular
mit dem Bezeichner personalized
aus und die darauffolgende
submit()
-Methode drückt auf den Submit-Knopf des sonst leeren Formulars.
Das in Zeile 17 erzeugte WWW::Mechanize::FormFiller
-Objekt übernimmt
das Ausfüllen des nun erscheinenden Formulars. Die add_filler()
-Methode
spezifiziert jeweils den Namen eines Formularfeldes, das Eingabeverfahren
und den Wert:
$fi->add_filler('leased' => Fixed => 'N' );
Für hartkodierte Werte findet Fixed
Anwendung, Interactive
weist den FormFiller, den Benutzer zur Laufzeit
nach dem entsprechenden Wert zu fragen.
Weiss der FormFiller, woher er alle Werte für die Formularvariablen
herbekommt, kann's losgehen und die fill_form
-Methode startet mit dem
gegenwärtig bearbeiteten HTML::Form
-Objekt des WWW::Mechanize
-Agenten
den Ausfüllvorgang:
$fi->fill_form($agent->current_form);
Die anschließend abgeschickte submit
-Methode sendet die Werte
an den Server.
Das Skript wird die Eingangsseite des DMV ansteuern, sich geduldig durch die ganzen Links und Formulare wühlen, schließlich in der for-Schleife ab Zeile 39 die angegebene Zeichenkette in die Auswahlboxen eintragen. Enthält das vorgeschlagene Nummernschild weniger als 7 Buchstaben, setzt die Logik ab Zeile 42 einfach leere Felder ein. Der simuliert-gedrückte Submit-Button in Zeile 54 schickt die Daten per SSL an den Server und die Shell nimmt das die Ergebnisseite als HTML entgegen.
Steht dort etwas wie ``not available'', ist
die Kombination entweder schon vergeben oder wegen anstößigen
Inhalts nicht verfügbar und das Skript gibt eine entsprechende Kurzmeldung
aus. Wurde das Kennzeichen genehmigt, erscheint ein Bestellformular, welches
das Skript anhand der Zeichenkette Complete Order Form
erkennt und
deswegen die Meldung XXX available
ausspuckt. Die Ausgabe
PERLMAN: not available
bestätigt, dass PERL MAN
nun leider schon vergeben und wohl auf
lange Sicht nicht verfügbar ist -- aber nutzt die Funktionalität der Shell
ruhig, um mühelos Perl-Skripts zu erzeugen, die langwierige Zugriffe
auf Webseiten automatisieren!
01 #!/usr/bin/perl 02 ########################################### 03 # dmv -- Automate checking CA plates 04 # Mike Schilli, 2003 (m@perlmeister.com) 05 ########################################### 06 use strict; 07 use warnings; 08 09 use WWW::Mechanize; 10 use WWW::Mechanize::FormFiller; 11 12 die "usage: $0 XXXXXXX" unless 13 defined $ARGV[0]; 14 $ARGV[0] =~ s/\s+//g; 15 my $agent = WWW::Mechanize->new( 16 autocheck => 1); 17 my $fi = WWW::Mechanize::FormFiller->new(); 18 19 $agent->get('http://www.dmv.ca.gov'); 20 $agent->follow(qr(Personalized)); 21 $agent->follow( 22 qr(order Special Interest)); 23 $agent->form("personalized"); 24 $agent->submit(); 25 26 $fi->add_filler('vehicletype' => 27 Fixed => 'AUTO' ); 28 $fi->add_filler('leased' => 29 Fixed => 'N' ); 30 $fi->add_filler('platetype' => 31 Fixed => 'R' ); 32 $fi->add_filler('kidpic' => 33 Fixed => '' ); 34 $fi->add_filler('Submit2' => 35 Fixed => '' ); 36 $fi->fill_form($agent->current_form); 37 $agent->submit(); 38 39 for(0..6) { 40 $fi->add_filler("LicPltCharAry$_" => 41 Fixed => 42 $_ > length $ARGV[0] ? 43 "" : substr($ARGV[0], $_, 1)); 44 } 45 46 for(0..6) { 47 $fi->add_filler("HalfSpace$_" => 48 Fixed => ''); 49 } 50 51 $fi->add_filler('Submit2' => Fixed => ''); 52 $fi->fill_form($agent->current_form); 53 54 $agent->submit(); 55 56 if($agent->content() =~ /not available/) { 57 print "$ARGV[0]: not available\n"; 58 } elsif($agent->content() =~ 59 /Complete Order Form/) { 60 print "$ARGV[0]: available\n"; 61 } else { 62 print "Unexpected response", 63 $agent->content(), "\n"; 64 }
Michael Schilliarbeitet als Software-Engineer bei Yahoo! in Sunnyvale, Kalifornien. Er hat "Goto Perl 5" (deutsch) und "Perl Power" (englisch) für Addison-Wesley geschrieben und ist unter mschilli@perlmeister.com zu erreichen. Seine Homepage: http://perlmeister.com. |