![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
||||||||
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
|||||||
![]() |
|||||||
![]() |
|
||||||
![]() |
Eigentlich war ich ja gegenüber diesem ganzen drahtlosen Gesummse eher skeptisch eingestellt. Aber seit man mir von Firmenseite einen Pager angedreht hat, schleppe ich ihn auch brav mit mir herum. Das Teil kann E-Mails empfangen und senden -- und so dachte ich mir: Warum nicht das Angenehme mit dem Nützlichen verbinden?
Ein Aktienkursservice wär's doch: Der Pager schickt eine E-Mail mit dem Kürzel der gesuchten Aktie im Mailrumpf los. Der Aktienkursservice, der auf einem Server mit Internetanschluss als Perlskript läuft, fängt die Mail ab, extrahiert das Kürzel und schickt, genau wie ein Browser, einen Web-Request an die Yahoo-Finanzseite aus. Aus dem zurückkommenden HTML extrahiert das Skript anschließend den Aktienkurs als relevante Information -- und schickt sie an den Absender der E-Mail, den Pager, zurück. Na, da weiß selbst der Almödi, was an den Börsen der Welt abgeht, wenn der Pager heftig in der Hosentasche vibriert.
Sendet also der 2-Way-Pager etwa eine E-Mail mit dem Inhalt "aol"
an die Adresse benutzer@irgendwo.com, sorgt dort eine .forward
-Datei im Home-Verzeichnis des Benutzers benutzer
dafür, dass der Sendmail-Dämon die Mail nicht in die normale
Eingangsschlange einreiht, sondern einem Skript nach Listing getquote.pl
zu Fressen gibt. Dieses extrahiert das Kürzel der gesuchten Aktie,
kontaktiert de.finance.yahoo.com, tippt den Namen in das Suchfeld in Abbildung 2, stellt am Auswahlschalter
den Börsenplatz
Frankfurt ein und simuliert schließlich einen gedrückten ``Kurse
abfragen''-Submit-Knopf. Es liest die zurückkommende HTML-Seite und
fieselt den gesuchten Kurs der AOL-Aktie aus einer der Tabellen, die Yahoo
im Ergebnis nach Abbildung 3 unterbringt. Aus der ursprünglich empfangenen
Email geht entweder aus dem Reply-To:
oder From:
-Header hervor, wer sie abschickte, und an genau diese Adresse schickt das
Skript anschließend die extrahierte Information ab.
Das klingt nach verhältnismäßig viel Arbeit, geht aber wie geschmiert, weil einige schlaue Module vom CPAN die Hauptarbeit erledigen -- nur 69 Zeilen hat das gesamte Skript!
Die Zeilen 3 und 4 definieren zwei Parameter: $MAIL_INPUT
gibt an, woher die ankommende Email stammt. Legt $MAIL_INPUT
den Namen einer Datei fest, liest das Skript diese ein, was besonders zum
Testen des Skripts nützlich ist. Unter Produktionsbedingungen steht $MAIL_INPUT
selbstverständlich auf "-"
, was den
open
-Befehl in Zeile 15 dazu veranlasst, auf der Standardeingabe zu lauschen,
wo der sendmail
-Dämon die E-Mail auch hinleiten wird. Der andere Parameter, $MAIL_HOST
, gibt den Server des Internetproviders für ausgehende Mails an, der später
dazu verwendet wird, Antworten auf Anfragen zurückzuschicken.
Zeile 6 weist das Skript an, streng zu sein und darauf zu achten, dass
nicht unabsichtlich globale Variablen, symbolische Referenzen oder sonstige
schmutzige Tricks einfließen, die Perl in seiner unendlichen Güte sonst
durchgehen lässt. Zeile 7 schaltet den Warnungsmodus ein, der vor Perl 5.6
noch mit dem Kommandozeilenschalter -w
aktiviert werden musste.
Die Zeilen 9 bis 12 ziehen die praktischen Module vom CPAN herein, die die
Arbeit erledigen: Mail::Internet
von Graham Barr
erleichtert das Analysieren und Schreiben von Email in Perl.
LWP::UserAgent
und HTTP::Request::Common
von
Gisle Aas wickeln den Web-Verkehr ab und HTML::TableExtract
von Matt Sisk
ist ein neuer Parser zum komfortablen Durchforsten von HTML-Texten nach
Tabellen mit bestimmten Kriterien.
Listing getquote.pl | ||
01 #!/usr/bin/perl 02 03 my $MAIL_INPUT = "-"; 04 my $MAIL_HOST = "mail.subbrhosting.net"; 05 06 use strict; 07 use warnings; 08 09 use Mail::Internet; 10 use LWP::UserAgent; 11 use HTTP::Request::Common; 12 use HTML::TableExtract; 13 14 # Mail einlesen 15 open MAILIN, "<$MAIL_INPUT" or 16 die "Cannot open $MAIL_INPUT"; 17 my @data = <MAILIN>; 18 close MAILIN; 19 20 # Mail analysieren 21 my $mail = Mail::Internet->new(\@data); 22 my $stock = $mail->body()->[0]; 23 chomp $stock; 24 25 # Antwort zusammenbasteln 26 my $reply = $mail->reply(); 27 my $quote = get_quote($stock); 28 $reply->body( [$quote] ); 29 30 # ... und absenden 31 $reply->smtpsend(Host => $MAIL_HOST) or 32 die "Reply mail failed"; 33 34 ################################################## 35 sub get_quote { 36 ################################################## 37 my $stock = shift; 38 39 my $URL = "http://de.finance.yahoo.com/q?m=F&" . 40 "s=$stock" . "&d=v1"; 41 42 my $result = ""; 43 44 my $ua = LWP::UserAgent->new(); 45 my $resp = $ua->request(GET $URL); 46 47 if($resp->is_error()) { 48 # Fehler beim Holen der Webseite? 49 my $error = sprintf "Error: %s", 50 $resp->message(); 51 return $error; 52 } 53 54 my $data = $resp->content(); 55 56 my $te = new HTML::TableExtract( depth => 0, 57 count => 4 ); 58 $te->parse($data); 59 60 # Alle Tabellenreihen zusammen darstellen 61 my @rows = $te->rows; 62 shift @rows; 63 foreach my $row (@rows) { 64 $result .= join(' ', $row->[0], $row->[4]); 65 $result .= " ** "; 66 } 67 68 return $result; 69 } |
Die Zeilen 15 bis 18 lesen die ankommende E-Mail auf einen Rutsch in den
Array @data
ein, wobei jedes Element einer Zeile der ursprünglichen Nachricht
entspricht. Zeile 21 kreiiert ein neues
Mail::Internet
-Objekt aus einer Referenz auf den Mailzeilen-Array. Dieses Objekt wird uns
die lästige Header-Setzerei und -Leserei ersparen, da wir zukünftig nur
noch mit Methoden auf das Mailobjekt zugreifen.
Zeile 22 liest die erste Zeile des Mailtexts. Die body
-Methode liefert laut Mail::Internet
-Dokumentation eine Referenz auf einen Array, der die Zeilen des Mailtexts
als Elemente enthält. $mail->body()->[0]
liefert also genau die erste Zeile des Mailtexts -- wo wir den vom Benutzer
angegebenen Namen der gesuchten Aktie erwarten. Zeile 23 schneidet noch den
Zeilenumbruch ab, falls einer vorhanden ist.
Zeile 26 erzeugt in $reply
eine Referenz auf ein neues
Mail::Internet
-Objekt für die Rückantwort. Wie ein guter Mail-Client stellt Mail::Internet
hierzu ein
Re:
mit dem ursprünglichen Betreff in die Subject-Zeile und besetzt den Body
mit dem eingerückten und markierten Text der vorausgegangenen Mail vor. Der
Adressat ist der Absender der ursprünglichen Nachricht. Die reply
-Methode des Mail::Internet
-Pakets sucht zunächst nach einem Reply-to
-Header in der ursprünglichen Mail und fällt auf den From
-Header zurück, falls jener nicht vorhanden ist, um den neuen Adressaten zu
ermitteln.
Zeile 28 überschreibt mit der body
-Methode den Nachrichtentext der ausgehenden Mail mit dem
Aktienkursergebnis, das vorher die get_quote
-Methode mit dem Aktiennamen als Argument vom Internet eingeholt hat. Die body()
-Methode in Zeile 28 nimmt eine Referenz auf einen Array entgegen, dessen
Elemente die Zeilen der Nachricht enthalten. Im Fall der Funktion get_quote()
, die nur einen einzigen Skalar zurückliefert, enthält der frisch erzeugte
anonyme Array den Ergebnisstring als einziges Element.
Zeile 31 schließlich sendet die Email zurück. Neben der smtpsend
-Methode mit dem Mailhost des Internetproviders als Argument böte Mail::Internet
außerdem noch die
send
-Methode, die sich auf den unter Unix herumlungernden mail
-Client stützt. Aber das nur, falls man den Sendmail-Dämon nicht direkt
ansprechen möchte.
get_quote
ab Zeile 35 nimmt das auf yahoo.de übliche Aktienkürzel entgegen (bmw
, aol
oder z.B. auch 575800
für die Höchst AG
) und nutzt das Modul LWP::UserAgent
, um einen Agenten zu erzeugen, der sich benimmt wie ein Browser und die
Yahoo-Seite aus Abbildung 2 befrägt.
Welche Formularparameter Yahoo erwartet, lässt sich einfach anhand der
HTML-Codes der Seite aus Abbildung 2 ableiten -- oder auch gerne mit dem loggenden Proxy aus [1]. Es zeigt sich, dass das Skript auf der Yahoo-Seite
http://de.finance.yahoo.com/q
heisst, und drei Query-Parameter erwartet: Mit m=F
steht der Börsenplatz auf Frankfurt, mit s=aol
wird das Aktienkürzel auf aol
gesetzt und mit d=v1
kommt noch etwas Geheimnisvolles hinzu, das offensichtlich die Darstellung
beeinflusst.
Zeile 39 in getquote.pl
jedenfalls baut diesen URL zusammen, Zeile 44 erzeugt einen neuen
Useragenten und Zeile 45 feuert den Request ab. Zeile 47 überprüft mit der is_error
-Methode, ob bei der Übertragung ein Fehler aufgetreten ist und der
zugehörige if
-Rumpf setzt im Fehlerfall eine Fehlermeldung und gibt sie als Ergebnis
zurück. Geht hingegen alles gut, legt Zeile 54 den HTML-Inhalt von
Abbildung 3 in der Variablen $data
ab.
HTML::TableExtract
HTML::TableExtract
ist nun ein ganz ausgefuchstes: Es extrahiert die Daten aus Tabellen in
HTML-Dokumenten. Dabei reicht es, die Tabellen symbolisch anzugeben. Man
sagt: ``Ich hätte gerne die Tabelle mit den Spaltenüberschriften Kurs und Datum`` und schon schlängelt sich der Parser durch das HTML-Dokument, sucht die
richtige Tabelle heraus und extrahiert daraus die angegebenen Spalten. In
unserem Fall geht es darum, aus dem wilden HTML-Code der Firma Yahoo die in
Abbildung 4 optisch hervorgehobenen Daten zu holen -- da kommt HTML::TableExtract
gerade recht.
Nun enthält die Yahoo-Tabelle eine Besonderheit, nämlich eine
Spaltenüberschrift, die sich mit COLSPAN
über mehrere Kolumnen erstreckt, was HTML::TableExtract
zur Drucklegung dieses Artikels noch ins Schleudern brachte. Statt dessen
fischt getquote.pl
in Zeile 56 die Tabelle Nummer 4 (count => 4
) im Dokument (depth
ist 0
, weil es sich um keine Untertabelle, sondern um eine Tabelle auf höchster
Ebene im Dokument handelt) heraus, Zeile 58 wirft den Parser an und Zeile
61 liest alle vom HTML befreiten Zeilen der gesuchten Tabelle zeilenweise
in den Array @rows
ein. Zeile 62 entfernt die Spaltenüberschriften und die foreach
-Schleife ab Zeile 63 iteriert über alle Tabellenreihen, wobei Zeile 64
jeweils die erste und die fünfte Spalte extrahiert, die den Aktiennamen und
den aktuellen Kurs in Euro beinhalten. Abschließend steht etwa folgendes in
der Email an den Pager:
AOL.F 60,70 **
Wurde in Zeile 39 nicht F
als Börsenplatz eingestellt, sondern etwa *
, antwortet Yahoo nicht nur mit einer, sondern mit vielen Zeilen, die die
Kurse an den verschiedenen Börsen angeben. In diesem Fall schnappt sich die foreach
-Schleife eine Zeile nach der anderen und gibt die Ergebnisse durch **
-Zeichen getrennt in einem String zurück -- in Pagern muss Platz gespart
werden.
LWP::UserAgent
und HTTP::Request::Common
sind Teile der bekannten Bibliothek libwww, das
Mail::Internet
-Modul ist Teil der MailTools, für die verwendete SMTP-Methode zum Mailen ist außerdem das Net::SMTP
-Modul aus dem libnet-Bundle notwendig. Am schnellsten wird alles wie immer mit Andreas Königs
CPAN
-Modul installiert:
perl -MCPAN -eshell cpan> install Net::SMTP cpan> install HTTP::Request::Common cpan> install Mail::Internet cpan> install HTML::Parser cpan> install HTML::TableExtract
Soweit die Module. Damit das Ganze funktioniert, brauchen wir einen
Rechner, der 24 Stunden am Tag am Internet hängt und Emails empfangen und
senden kann. Außerdem muss dort perl
installiert sein und ein sendmail
-ähnlicher Mail-Daemon hausen.
Wer über keinen eigenen Rechner am Internet verfügt, kann sich mit einem Hosting-Service behelfen. Die besseren Services lassen nicht nur statische Webseiten und CGIs zu, sondern erlauben auch Shell-Zugriff auf Linux-Servern.
Damit das Skript bei eintreffender E-Mail sofort reagiert, muss ein Eintrag
in die .forward
-Datei unter dem Heimatverzeichnis des entsprechenden Benutzers her:
$ cat /home/benutzer/.forward | /home/benutzer/bin/getquote.pl
Kommt E-Mail, die an
benutzer@irgendwo.com
gerichtet ist, für Benutzer
benutzer
auf dem entsprechenden Rechner an, leitet sendmail
die Mail wegen der .forward
-Datei an das Skript getquote.pl
weiter, welches die notwendigen Aktionen wie oben beschrieben einleitet.
get_quote
-Funktion in
getquote.pl
umschreiben -- aber da wir nicht direkt auf buchstabengetreue Wiedergabe
angewiesen sind, sondern logische Angaben machen (z.B. ``vierte Tabelle auf
der Seite''), macht es auch nichts aus, wenn Yahoo hin und wieder die
Anzeigen wechselt. Das vorgestellte Verfahren ist relativ robust, aber, für
alle Fälle: Wie immer wird die Online-Version des Skripts auf
www.linux-magazin.de stets auf dem neuesten Stand gehalten.
Viel Spaß beim Pagen -- aber wenn ich einen erwische, der seinen Pager im Kino oder im Restaurant klingeln lässt, dann raucht's! Bis zum nächstenmal!
Infos |
|
Der Autor |
Michael Schilli arbeitet als Web-Engineer für AOL/Netscape in Mountain View, Kalifornien. Er ist Autor des 1998 bei Addison-Wesley erschienenen (und 1999 für den englischsprachigen Markt als "Perl Power" herausgekommenen) Buches "GoTo Perl 5" und unter michael@perlmeister.com zu erreichen. Seine Homepage: http://perlmeister.com |
Copyright © 2000 Linux New Media AG