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. |