Neben Google, Amazon und Ebay bietet neuerdings auch Yahoo! eine Programmierschnittstelle zu einigen seiner Dienste an. Drei einfache Perl-Skripts stöbern heute damit Tippfehler, Internet-Bildchen und verschollen geglaubte Klassenkameraden auf.
Immer mehr Internet-Anbieter stellen ihre Dienste nicht nur über eine Browser-Schnittstelle ins Netz, sondern erlauben Entwicklern, deren eigene Applikationen mittels Web-APIs einzuklinken. Yahoo! bietet seit neuestem ein REST-(URLs hin, XML zurück)-Interface zu seinem Such-Engine an, zu dem es natürlich auch ein passendes Perl-Modul gibt: ``Yahoo::Search'', erhältlich vom CPAN und entwickelt vom großen Regex-Zampano Jeffrey Friedl.
Es vereinfacht die Suche nach Dokumenten, Bildern, Videos und einiges mehr, denn HTTP-Zugriffe und XML-Extraktion sind sauber hinter einfachen Methodenaufrufen versteckt. Wer eine Applikation entwickeln möchte, holt sich einfach eine eigene Application-ID ab. Sie berechtigt zu 5.000 Aufrufen der in diesem Artikel vorgestellten Dienste pro Tag. Die Registrierung erfordert auf [2] lediglich eine Yahoo-ID, die es ebenfalls kostenlos gegen die Angabe einer gültigen Email-Adresse gibt.
Falls man nicht genau weiss, wie ein bestimmtes Wort geschrieben wird, bietet sich der Duden oder ein Englisch-Wörterbuch an. Neuerdings kann man aber auch einen Such-Engine zur Rechtschreibprüfung heranziehen.
Im Wildwuchs des Internets finden sich zwar haufenweise Rechtschreibfehler,
doch erstaunlicherweise folgen die Mehrzahl aller Webseiten der korrekten
Rechtschreibung. Such-Engines bemerken die Diskrepanz und bieten
üblicherweise ``Did you mean ...?''-Funktionen an.
Das Programm typo
ruft über die Methode Term()
und dem Parameter Spell
den Korrektur-Webservice bei Yahoo! auf:
$ typo miscelaneous Corrected: miscellaneous
Und der Vorteil eines Search-Engines gegenüber einem herkömmlichen Lexikon ist freilich, dass ersterer auch populärwissenschaftlich auf dem Laufenden ist:
$ typo foo foughters Corrected: foo fighters
Wer also ein Lied im Radio hört, aber den Ansager beim Bekanntgeben des Bandnamens anschließend nur undeutlich versteht, gibt einfach das Gehörte in die Suchmaschine ein. Falls die Gruppe recht bekannt ist, wird's die Korrektur schon richten. Und sogar einige deutsche Wörter kennt man bei Yahoo!:
$ typo hotzenplutz Corrected: hotzenplotz
Listing typo
zeigt die kurze Implementierung: Die use
-Anweisung
in Zeile 13 zieht das vorher mit einer CPAN-Shell installierte Modul
Yahoo::Search
herein und übergibt ihm die vorher auf [2] abgeholte
Application-ID. Die ID linux_magazin
ist gültig und sollte für kurze
Tests reichen. Wer eines der heute vorgestellten Skripts häufiger benutzt,
sollte sich eine eigene holen.
Die Methode Terms()
nimmt mit dem Parameter Spell
ein
Wort oder eine Wortfolge entgegen, schickt sie an den Service auf
der Yahoo!-Seite, untersucht das zurückgeschickte XML und fieselt
eine etwaige Antwort heraus. Zeile 15 legt sie in der Variablen
$suggestion
ab. Das anschließende if-else-Konstrukt gibt sie entweder
die Anwort aus, oder, falls Such-Engine nichts außergewöhnliches
zum untersuchten Begriff eingefallen ist, ``No corrections''.
Leider hinkt die Internationalisierung des Service noch etwas hinterher, und zu Wörtern mit Umlauten fallen ihm (noch) keine Korrekturen ein.
01 #!/usr/bin/perl -w 02 ########################################### 03 # typo - Get Yahoo! spelling corrections 04 # Mike Schilli, 2005 (m@perlmeister.com) 05 ########################################### 06 use strict; 07 08 my $term = "@ARGV"; 09 10 die "usage: $0 word/phrase ..." 11 unless length $term; 12 13 use Yahoo::Search AppId => "your_yahoo_token"; 14 15 my($suggestion) = Yahoo::Search->Terms( 16 Spell => $term); 17 18 if(defined $suggestion) { 19 print "Corrected: $suggestion\n"; 20 } else { 21 print "No suggestions\n"; 22 }
Die Spiders der Search-Engines grasen ständig das bekannte Internet nach neu auftauchenden Informationen ab. Entsprechend ändern sich die Suchresultate zu bestimmten Begriffen. Wer einmal am Tag eine Abfrage mit den Namen seiner alten Schulkameraden abfeuert, bekommt mit, falls diese neue Homepages aufziehen oder zu Rang und Namen kommen. Natürlich hat niemand die Zeit, das jeden Tag manuell durchzuführen und sich die Resultate zu merken.
Das Skript buddy
wird deshalb einmal am Tag per Cronjob aufgerufen und
holt jeweils die ersten 25 Ergebnisse zu einer Liste von Namen ein,
die in der Konfigurationsdatei ~/.buddy
liegen.
Alle bis Dato unbekannten URLs und einen kurzen Ausschnitt aus dem
zur Suchabfrage passenden Textteil der gefundenen Webseite schickt
buddy
dann an eine vorgegebene Email-Adresse. So bleibt der
Empfänger auf dem Laufenden und bekommt mit, wenn sich ein
bekannter Name irgendwo auftaucht -- vielleicht sogar bei
einer Nobelpreisnominierung?
buddy
hält die so gefundenen URLs in einem Cache vorrätig, der sich
die Einträge einen Monat lang merkt und wiedergefundene Treffer
so markiert, als wären sie gerade zum ersten Mal aufgetaucht.
So simuliert der Cache ein ``schlechtes'' Gedächtnis, das sich
wieder freut, falls ein alter Name nach einem Monat der Abwesenheit
wieder auftaucht.
Abbildung 1: Nachricht per Email: Alte Klassenkameraden sind auf dem Web aufgetaucht. |
In Zeile 10 erwartet das Skript die Email-Adresse, an die es die
gefundene Änderungen verschickt.
Wird buddy
von der Kommandozeile mit der Option -v
(für verbose) aufgerufen, initialisiert Zeile 22 das Log4perl-Framework mit
dem Log-Level $DEBUG
, und macht es damit gesprächiger. Andernfalls
werden nur Log-Messages der Priorität WARN
und höher ausgegeben.
Zeile 24 deklariert die weiter unten definierte Funktion mailadd
, damit
Perl weiß, dass es sich um eine Funktion handelt und
Aufrufe schon vor der Definition ohne lästige Klammern erfolgen
können. mailadd
unterhält eine our
-Variable $maildata
, die
es sich mit der ab Zeile 88 definierten Funktion mailsend
teilt.
Aufrufe von mailadd
hängen nur Text an $maildata
an, der dann
beim Aufruf von mailsend
per Mail::Send
an die angegebene
Email-Adresse abgeschickt wird.
Die aus dem Modul Sysadm::Install
importierte Funktion plough
(Englisch: pflügen), nimmt in Zeile 28 eine Callback-Funktion und
einen Dateinamen entgegen. Sie liest die Datei ein,
ruft den Callback nach jeder gelesenen Zeile auf und übergibt den
Zeileninhalt in der Variablen $_
. Zeile 29 verwirft mit #
beginnende
Kommentarzeilen und der chomp
-Befehl sägt den Zeilenumbruch ab.
Zeile 31 schiebt gefundene ``Buddies'' ans Ende des stetig wachsenden
Arrays @buddies
.
Zeile 48 kontaktiert dann über die Results()
-Methode den
Yahoo!-Dienst.
Der Name eines Klassenkameraden aus der Konfigurationsdatei wird in
doppelte Anführungszeichen eingerankt und dann als String
(qq{"$buddy"}
) mit dem Doc
-Parameter übergeben, denn es
handelt sich um eine Web-Suche.
Die als Liste zurückkommenden Ergebnisobjekte geben über die
Methoden Url()
und Summary()
URL und Kurzbeschreibung der
Treffer aus.
Der in Zeile 34 angelegte File-Cache wird üblicherweise hinter den
Kulissen in /tmp/FileCache
angelegt und speichert abgelegte Einträge
wegen des auf 30 Tage gesetzten Parameters default_expires_in
einen Monat lang.
1 # Meine Klassenkameraden 2 Bodo Ballermann 3 Rudi Ratlos 4 Rambo Zambo 5 Elli Pirelli
Da sich der Webservice bei Anfragen und Antworten strikt an UTF-8 hält,
müssen die Namen in UTF-8 in ~/.buddy
stehen. Wer eine neuere
Linux-Distribution fährt, dessen Editor wird die eingetippten Namen
gleich in UTF-8 abspeichern. Wer noch hinterher hinkt, und mit Latin1
arbeitet, kann die Daten mit einem kleinen Programm wie
# toutf8 use Text::Iconv; my $conv = Text::Iconv->new("Latin1", "UTF-8"); print $conv->convert(join '', <>);
und einem Kommandoaufruf wie
$ toutf8 buddy.latin1 >~/.buddy
schnell von Latin1 nach UTF-8 konvertieren.
Im Skript ist lediglich die Email-Adresse in Zeile 10 an die lokalen Gegebenheit anzupassen. Ein Cron-Eintrag der Form
0 5 * * * $HOME/bin/buddy
ruft das Skript dann einmal täglich um fünf Uhr früh auf, klappert den Such-Engine ab, frischt den Cache auf und schickt eine Email, falls sich etwas getan hat.
001 #!/usr/bin/perl -w 002 ########################################### 003 # buddy - Keep track of buddies with 004 # Yahoo! Search 005 # Mike Schilli, 2005 (m@perlmeister.com) 006 ########################################### 007 use strict; 008 009 my $BUDDY_FILE = "$ENV{HOME}/.buddy"; 010 my $EMAIL_TO = 'meldung@ich.lausche.com'; 011 012 use Sysadm::Install qw(:all); 013 use Yahoo::Search; 014 use Text::Wrap; 015 use Cache::FileCache; 016 use Log::Log4perl qw(:easy); 017 use Getopt::Std; 018 use Mail::Send; 019 020 getopts("v", \my %o); 021 022 Log::Log4perl->easy_init($o{v} ? 023 $DEBUG : $WARN); 024 sub mailadd; 025 026 my @buddies = (); 027 028 plough sub { 029 return if /^\s*#/; 030 chomp; 031 push @buddies, $_; 032 }, $BUDDY_FILE; 033 034 my $cache = Cache::FileCache->new({ 035 namespace => "Buddy", 036 default_expires_in => 3600*24*30, 037 }); 038 039 my $search = Yahoo::Search->new( 040 AppId => "linux_magazin", 041 Count => 25, 042 ); 043 044 for my $buddy (@buddies) { 045 046 DEBUG "Search request for '$buddy'"; 047 048 my @results = $search->Results( 049 Doc => qq{"$buddy"} 050 ); 051 052 my $buddy_printed = 0; 053 054 DEBUG scalar @results, " results"; 055 056 for my $result (@results) { 057 058 if($cache->get($result->Url())) { 059 DEBUG "Found in cache: ", 060 $result->Url(); 061 # Refresh if found 062 $cache->set($result->Url(), 1); 063 next; 064 } 065 066 mailadd "\n\n### $buddy ###" 067 unless $buddy_printed++; 068 069 mailadd $result->Url(); 070 071 $cache->set($result->Url(), 1); 072 073 mailadd fill(" ", " ", 074 $result->Summary()), ""; 075 } 076 } 077 078 mailsend(); 079 080 ########################################### 081 sub mailadd { 082 ########################################### 083 our $maildata; 084 $maildata .= "$_\n" for @_; 085 } 086 087 ########################################### 088 sub mailsend { 089 ########################################### 090 our $maildata; 091 092 return unless defined $maildata; 093 094 DEBUG "Sending email: $maildata"; 095 096 my $msg = Mail::Send->new(); 097 $msg->to($EMAIL_TO); 098 $msg->subject("Buddy Watch News"); 099 my $fh = $msg->open; 100 print $fh $maildata; 101 close $fh; 102 }
Auch die Suche nach Bildern unterstützt der neue Service. Zu einem
Begriff findet der Search-Engine eine Reihe passender Bilder, die
das Skript slideshow
dann im 5-Sekunden-Takt wie in einer
Diashow nach und nach im Browser darstellt.
Wie Abbidlung 2 zeigt, stellt
das Skript beim ersten Aufruf zunächst
ein einfaches Suchformular dar. Trägt der Benutzer einen
Suchbegriff ein (z.B. ``San Francisco''), und klickt auf den
``Search''-Knopf, ist der CGI-Parameter q
gesetzt und Zeile 40
ruft die Methode Results()
des Pakets Yahoo::Search
auf.
Der Parameter Image
übergibt den Suchbegriff, ein Count
von
50 limitiert das Ergebnis auf 50 Treffer, und ein auf 0 gesetztes
AllowAdult
verhindert mehr oder weniger erfolgreich, dass sich
plötzlich nackte Menschen auf dem Bildschirm tummeln.
Da der Text der Bildunterschrift in UTF-8 daherkommt, zeigt die
Methode header()
in Zeile 21 dem Browser neben den üblichen
Header-Zeilen an, das die dynamisch erzeugte Webseite in UTF-8 kodiert ist.
Das Skript slideshow
speichert das Ergebnis einer Suchabfrage,
also die Image-URLs und deren Summary-Texte, als Array von Arrays
in einem persistenten Datei-Cache ab, damit der Dia-Projektor nicht
bei jedem neuen Bild wieder zum Such-Engine zurückkehren muss.
Der persistente Datei-Cache Cache::FileCache
speichert aber
Key-Value Paare ab, wobei als Werte nur einfache Skalare und keine
verschachtelten Strukturen erlaubt sind. Das Modul Storable
hilft
hier aus der Patsche, denn dessen Funktion freeze()
kann eine
Datenstruktur serialisieren, bevor sie in den Cache wandert. Kommt
der serialisierte Datensalat wieder aus dem Cache zurück, wandelt
ihn der De-Serialisierer thaw()
wieder in eine verschachtelte
Perl-Datenstruktur um.
Damit das CGI-Skript nicht versehentlich hereinkommende Daten
ungeprüft für Systemaufrufe verwendet und damit Sicherheitslöcher
in die Applikation reißt, steht in der She-Bang-Zeile hinter
dem Aufruf des Perl-Interpreters die Option -T
.
Der erste if
-Block ab Zeile 23 kommt zum Einsatz, falls das Skript
sowohl mit dem Query-String also auch mit der fortlaufenden Nummer
des aktuellen Bildes aufgerufen wurde. Ist dies ist der Fall, steht
im Cache die Folge der Bild-URLs und deren Beschriftungen. Zeile
24 taut den Array von Arrays auf und der Modulo-Operator in Zeile
26 sorgt dafür, dass die fortlaufend hochgezählte Bildnummer immer
in den Array hinein zeigt und nie darüber hinausschießt.
Die in Zeile 27 mit dem Parameter 5 (Sekunden) aufgerufene Funktion
refresh()
ist ab Zeile 67 definiert. Sie gibt HTML-Sequenzen
zurück, die den Browser mittels eines META-Tags veranlassen, nach
Ablauf der übergebenen Anzahl von Sekunden eine neue Seite zu
laden, die eine um Eins erhöhte Bildnummer aufweist.
Ein weiterer Parameter, $reset
, legt fest, ob der nachzuladende URL
das nächste Bild anzeigt (next_url
zählt einfach den
Nummern-Parameter s
um eins Hoch) oder ob es mit dem Ursprungs-URL
(hervorgeholt von der Funktion url()
des CGI-Moduls)
zurück zur Startseite mit dem Eingabefeld geht.
Um das Skript zu installieren, verfrachtet man es einfach ins
Verzeichnis cgi-bin
des Webservers und stellt den Web-Browser
darauf ein. Willkommen zum unschlagbaren Charme von Privatfotos!
01 #!/usr/bin/perl -wT 02 ########################################### 03 # slideshow 04 # Mike Schilli, 2005 (m@perlmeister.com) 05 ########################################### 06 use strict; 07 08 use CGI qw(:all); 09 use Yahoo::Search AppId => "linux_magazin"; 10 use Cache::FileCache; 11 use Storable qw(freeze thaw); 12 13 my $cache = Cache::FileCache->new({ 14 namespace => 'slideshow', 15 default_expires_in => 3600, 16 auto_purge_on_set => 1, 17 }); 18 19 my $data; 20 21 print header(-charset => "utf-8"); 22 23 if(param('q') and defined param('s')) { 24 $data = thaw $cache->get(param('q')); 25 my $seq = param('s'); 26 $seq %= scalar @$data; 27 print refresh(5); 28 print center( 29 a({href => url()}, "Stop"), 30 a({href => next_url()}, "Next"), 31 p(), 32 b(param('q')), ":", 33 i($data->[$seq]->[1]), p(), 34 img({src => $data->[$seq]->[0]}), 35 p(), a({href => $data->[$seq]->[0]}, 36 $data->[$seq]->[0]), 37 ); 38 } elsif(param('q')) { 39 my @results = 40 Yahoo::Search->Results( 41 Image => param('q'), 42 Count => 50, 43 AllowAdult => 0, 44 ); 45 if(@results) { 46 for(@results) { 47 push @$data, 48 [$_->Url(), $_->Summary()]; 49 } 50 print refresh(0); 51 $cache->set(param('q'), 52 freeze($data)); 53 } else { 54 print refresh(0, 1); 55 } 56 } else { 57 print h2("Slideshow Search"), 58 start_form(), 59 textfield(-name => 'q'), 60 submit(-value => "Search"), 61 end_form(), 62 font({size => 1}, 63 "Powered by Yahoo! Search"); 64 } 65 66 ########################################## 67 sub refresh { 68 ########################################## 69 my($sleep, $reset) = @_; 70 71 return start_html( 72 -title => "Slideshow", 73 -head => meta({ 74 -http_equiv => "Refresh", 75 -content => "$sleep, URL=" . 76 ($reset ? url() : next_url()) 77 })); 78 } 79 80 ########################################## 81 sub next_url { 82 ########################################## 83 my $s = param('s'); 84 $s ||= 0; 85 86 return sprintf "%s?q=%s&s=%d", url(), 87 param('q'), $s+1; 88 }
Abbildung 2: Eine Anfrage nach "San Francisco" im Suchfeld der Applikation ... |
Abbildung 3: ... liefert eine spannende Diashow, hauptsächlich mit Urlaubsfotos von Privatleuten. |
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. |