Dem Beispiel des famosen Suchdienstes Google folgend, bietet nun auch das weltgrößte virtuelle Kaufhaus Amazon.com seinen umfangreichen Katalog programmatisch per Webservice an.
Nicht jeder möchte mit einem Browser herumhantieren, um im Amazon-Katalog zu wühlen. Wie wär's damit, durch alle angebotenen Perl-Bücher zu gehen und dasjenige herauszusuchen, auf das Amazon.com den höchsten Preisnachlass gibt? Was tun, wenn eine Website das Coverbild einer beliebigen CD darstellen möchte, deren ASIN (Amazon.com Standard Item Number) sie kennt? Oder alle Rolling-Stones-CDs auszugeben, die's für weniger als $13.99 gibt?
Amazon.com bietet seit einiger Zeit den programmierbaren Zugriff auf seine Datenbank an. Sowohl eine SOAP-Schnittstelle wie auch eine vereinfachte XML-über-HTTP-Anbindung ist möglich. Für simple Anfragen mit einigen Parametern, auf die der Server mit Datensätzen antwortet, ist SOAP fast schon überdimensioniert. Aus diesem Grund bürgert es sich in der Webservice-Welt immer mehr ein, dass ein Webserver einfache parametrisierte HTTP-Requests entgegennimmt und darauf mit XML antwortet. Zwar kann man mit Perls SOAP::Lite kinderleicht SOAP programmieren, aber mit der heute verwendeten XML/HTTP-Schnittstelle geht's noch einfacher.
Nehmen wir an, wir wollten das Buch mit der ISBN-Nummer 0201360683 finden. Hierzu reicht einfach ein HTTP-Request mit folgender URL:
http://xml.amazon.com/onca/xml2? t=xxx&dev-t=yyy&AsinSearch=0201360683&type=lite&f=xml
Im Request stehen folgende Parameter:
lite
oder heavy
fest, ob die Kurz- oder Langform der
XML-Antwort gewünscht wird.
xml
das Ausgabeformat auf XML.
Der amazon.com Webservice wird daraufhin sofort eine XML-Antwort nach Abbildung 1 zurückschicken, die alle notwendigen Produktdaten des angeforderten Artikels enthält, Links auf die von amazon.com angebotenen Abbildungen, den Amazon-Preis, den Listenpreis und vieles andere mehr.
Abbildung 1: Die XML-Antwort auf die ASIN-Web-Anfrage |
Dergleichen Daten lassen sich natürlich mit Perl und einem Modul
wie XML::Simple hervorragend extrahieren und für weitere Forschungen
ummodeln und zurechtbiegen. Damit auch das noch einfacher wird,
steht unter [2] (und auch auf dem CPAN) das brandneue Modul
Net::Amazon
zur Verfügung, mit dem man, wie in Listing
asin_fetch
gezeigt, schön objektorientiert mit dem Amazon-Service
spielen kann.
01 #!/usr/bin/perl 02 ########################################### 03 # asin_fetch 04 # Mike Schilli, 2003 (m@perlmeister.com) 05 # Fetch book info by ASIN 06 # asin_fetch 0201360683 07 ########################################### 08 use warnings; 09 use strict; 10 11 use Net::Amazon; 12 use Net::Amazon::Request::ASIN; 13 14 my $ua = Net::Amazon->new( 15 token => 'YOUR_AMZN_TOKEN', 16 ); 17 18 die "usage: $0 asin\n(use 0201360683 as " . 19 "an example)\n" unless defined $ARGV[0]; 20 21 my $req = Net::Amazon::Request::ASIN->new( 22 asin => $ARGV[0], 23 #type => 'heavy', 24 ); 25 26 # Response is Net::Amazon::ASIN::Response 27 my $resp = $ua->request($req); 28 29 if($resp->is_success()) { 30 print $resp->as_string(), "\n"; 31 } else { 32 print "Error: ", 33 $resp->message(), "\n"; 34 }
Am Anfang zieht das Skript die Module Net::Amazon
und
Net::Amazon::Request::ASIN
herein --
Net::Amazon
funktioniert ähnlich wie die bekannte LWP-Library,
die zunächst einen UserAgent definiert, dann einen Request und diesen
anschließend an den UserAgent weiterreicht. Das Net::Amazon
-Objekt
nimmt den Developer-Token entgegen und führt anschließend beliebig
viele Anfragen aus. Das Skript asin_fetch
erwartet die ISBN-Nummer auf der
Kommandozeile. Falls diese fehlt, bricht Zeile 18 den Reigen mit einer
usage
-Meldung ab. Zeile 21 definiert die ASIN-Anfrage und
setzt den asin
-Parameter auf die angegebene Nummer. Zeile 26 führt
dann den eigentlichen HTTP-Request an den Amazon-Server aus, schluckt
das zurückkommende XML, analysiert es und legt es in einer Hashstruktur
im Antwortobjekt $resp
vom Typ Net::Amazon::Response::ASIN
ab.
Alle Net::Amazon::Response::*
-Objekte verfügen über die Methoden
is_success()
, is_error()
, message()
(etwaige Fehlermeldung)
und as_string()
(fasst die wichtigsten Ergebnisparameter lesbar zusammen).
Der Aufruf von asin_fetch
mit der ASIN 0596000278 fördert folgendes zutage:
$asin_fetch 0596000278 Larry Wall/Tom Christiansen/Jon Orwant, "Programming Perl (3rd Edition)", 2000, $34.97, 0596000278
Die gefundenen Objekte (Net::Amazon nennt sie Properties) sind entweder
Bücher, CDs, Elektrogeräte etc. -- zur Zeit werden
Net::Amazon::Property::Book
und Net::Amazon::Property::Music
explizit unterstützt, der Rest strandet als generisches
Net::Amazon::Property
.
Die properties()
-Methode eines Response-Objektes gibt eine Liste
aller
gefundenen Objekte zurück.
Allgemein bietet Net::Amazon::Property
(und damit auch die davon abgeleiteten Klassen)
für jeden von Amazon.com
im zurückgelieferten XML
definierten Produktparameter eine Methode an, unter anderem:
Asin()
ReleaseDate()
ListPrice()
OurPrice()
Manufacturer()
Catalog()
Artist()
Authors()
Artist
-Eintrag zurück.
ImageUrlLarge()
Large
auch: Medium/Small).
ProductName()
Diese variieren je nach Art des Produkts. So gibt es bei Büchern kein
Artist
-Feld wie bei CDs,
sondern einen Authors
-Eintrag, der wiederum einen
Unter-Hash enthält, der unter einem Author
-Schlüssel entweder
einen Einzeleintrag oder eine Referenz auf eine Liste mit Autoren
enthält. Um diesen Wirrwarr zu vereinfachen, bietet Net::Amazon
in spezialisierten Net::Amazon::Property
-Objekten
wie Net::Amazon::Property::Book
bequemere
Methoden an.
Ist eine Property zum Beispiel von diesem Typ, gibt eine zusätzliche
Methode authors()
die Liste der Autorennamen zurück.
Als weiteres Beispiel zeigt Listing keyword
, wie man auf Amazon.com nach
einem Schlüsselwort in einem Produktbereich sucht.
Der Konstruktor des Net::Amazon
-Objekts nimmt ab Zeile 17 nicht
nur den Developer-Token entgegen, sondern auch den Parameter
max_pages
, der dort auf 5 gesetzt ist (entspricht der Standardeinstellung).
Liefert eine Suchanfrage
mehrere Treffer, liefert Amazon immer nur 10 auf einmal, und weitere
Web-Requests müssen eventuell folgende Seiten nachholen.
Wenn man die Seitenzahl max_pages
setzt, abstrahiert Net::Amazon
dies
und holt automatisch solange Ergebnisse nach, bis die maximale Seitenzahl
erreicht ist, oder die Treffer ausgehen.
Zeile 11 zieht
das Net::Amazon::Request::Keyword
-Modul herein, dessen Konstruktoraufruf
ab Zeile 22 das auf der Kommandozeile bereitgestellte Schlüsselwort
(zum Beispiel perl
) und den Produktbereich (z.B. books
, music
,
classical
, electronics
) mit dem mode
-Parameter setzt.
Zeile 31 bekommt mit der properties()
-Methode des
Response
-Objekts eine Liste gefundener Property
-Objekte und
iteriert über diese.
Auch bei explizit angegebenem Suchbereich liefert Amazon manchmal Produkte
aus anderen Bereichen, also stellt Zeile 33 sicher, dass es sich nur
um Bücher handelt, indem es den Catalog()
-Eintrag mit dem String
Book
vergleicht (man beachte die Namensinkonsistenz mit dem
vorher mit books
festgelegten mode
-Parameter) und bei Abweichungen
den Treffer einfach überspringt. Ab Zeile 35 kommen dann sowohl spezielle
Methoden der Net::Amazon::Property::Book
-Klasse
(authors()
, title()
), als auch generische Methoden der
Net::Amazon::Property
-Klasse (Asin()
, OurPrice()
) zum Einsatz,
um den Treffer anzuzeigen.
01 #!/usr/bin/perl 02 ########################################### 03 # keyword - search by keyword 04 # keyword what_to_search_for mode 05 # Mike Schilli <mschilli1@aol.com>, 2003 06 ########################################### 07 use strict; 08 use warnings; 09 10 use Net::Amazon; 11 use Net::Amazon::Request::Keyword; 12 13 die "usage: $0 keyword what mode\n(use " . 14 "perl/books as an example)\n" 15 unless defined $ARGV[1]; 16 17 my $ua = Net::Amazon->new( 18 token => 'YOUR_AMZN_TOKEN', 19 max_pages => 5, 20 ); 21 22 my $req = Net::Amazon::Request::Keyword 23 ->new( 24 keyword => $ARGV[0], 25 mode => $ARGV[1], 26 ); 27 28 # Response: Net::Amazon::Keyword::Response 29 my $resp = $ua->request($req); 30 31 for ($resp->properties) { 32 33 next unless $_->Catalog() eq "Book"; 34 35 print join(", ", $_->Asin(), 36 join("/", $_->authors()), 37 $_->title(), 38 $_->OurPrice()), "\n"; 39 }
Folgender Aufruf, der nach Büchern sucht, auf die das Stichwort ``perl'' passt, fördert auch Produkte zutage, von denen man das nicht unbedingt erwarten würde:
$ keyword perl books 0596000480, David Flanagan, JavaScript: The Definitive Guide, $31.47 0596000278, Larry Wall/Tom Christiansen/Jon Orwant, Programming Perl (3rd Editio ...
Auf welche Perl-Bücher gibt Amazon den höchsten Rabatt? Listing cheapo
sucht nach einem Stichwort (z.B. perl
) und holt wegen dem auf
20 gesetzten Parameter max_pages
gleich bis zu 20 mal 10 Treffer
ein. Anschließend filtert es die von properties()
zurückgelieferten
Artikel mittels des grep
-Kommandos und prüft, ob ein Treffer
tatsächlich ein Buch ist und ob der Titel (mittels title()
) tatsächlich
das gesuchte Schlüsselwort enthält.
Die sort
-Funktion in Zeile 31 sortiert absteigend nach dem Ergebnis
der Funktion saved
, die ab Zeile 45 definiert ist und zu einem
Buch-Objekt die prozentuale Ersparnis zwischen Listen- und Amazonpreis
errechnet. Effizient ist das nicht, da könnte man eine
Schwartz-Transformation zwischenschalten und auch nur die wirklich billigsten
Bücher ständig bereithalten, falls man wirklich tausende
von Treffern untersuchen wollte, aber für die gezeigte Menge macht's
kaum einen Unterschied. Die Preisfelder enthalten übrigens beim
amerikanischen Amazon ein Dollar-Zeichen, das die Zeilen 51 und 52
extrahieren müssen, bevor Arithmetik betrieben wird.
Die for
-Schleife ab Zeile 36 gibt dann die fünf aufregensten Preisknüller
mit prozentualer Ersparnis und Titel aus.
01 #!/usr/bin/perl 02 ########################################### 03 # maxauthors keyword 04 # Mike Schilli <mschilli1@aol.com>, 2003 05 ########################################### 06 07 use strict; 08 use warnings; 09 10 use Net::Amazon; 11 use Net::Amazon::Property; 12 use Net::Amazon::Request::Keyword; 13 14 die "usage: $0 keyword" unless 15 defined $ARGV[0]; 16 17 my $ua = Net::Amazon->new( 18 token => 'YOUR_AMZN_TOKEN', 19 max_pages => 20, 20 ); 21 22 my $req = Net::Amazon::Request::Keyword->new( 23 keyword => $ARGV[0], 24 mode => "books" 25 ); 26 27 # Response: Net::Amazon::Keyword::Response 28 my $resp = $ua->request($req); 29 30 my @books = 31 sort { saved($b) <=> saved($a) } 32 grep { $_->Catalog eq "Book" && 33 $_->title =~ /$ARGV[0]/i } 34 $resp->properties; 35 36 for(0..4) { 37 printf "%.2f%% (%s/%s) %s\n\n", 38 saved($books[$_]), 39 $books[$_]->ListPrice, 40 $books[$_]->OurPrice, 41 $books[$_]->as_string; 42 } 43 44 ########################################### 45 sub saved { 46 ########################################### 47 my($book) = @_; 48 49 my $list = $book->ListPrice; 50 my $our = $book->OurPrice; 51 $list =~ s/\$//; 52 $our =~ s/\$//; 53 54 return ($list - $our)/$list*100; 55 }
Oder wie wär's mit einem CGI-Skript, das zu einer vorgegeben ASIN
ein Bild des Produkts anzeigt? Listing asin_img
zeigt eine Implementierung, die einfach bei Amazon den URL abholt und
dann einen Redirect ausführt. Nach der Installation im cgi-bin
-Verzeichnis
lässt der Aufruf
http://localhost/cgi-bin/asin_img?asin=0201360683
den Browser das entsprechende Bild anzeigen. Zu beachten ist allerdings, dass Amazon verlangt, dass man keine Informationen aus deren Datenbestand anzeigt, ohne irgendwie zurück zu Amazon zu linken -- der Rubel muss schließlich Rollen, Amazon ist kein Wohlfahrtsunternehmen.
01 #!/usr/bin/perl 02 ########################################### 03 # asin_img - Fetch an ASIN's image 04 # Mike Schilli, 2003 (m@perlmeister.com) 05 ########################################### 06 use warnings; 07 use strict; 08 09 use CGI qw(:all); 10 use CGI::Carp qw(fatalsToBrowser); 11 use Net::Amazon; 12 use Net::Amazon::Request::ASIN; 13 14 my $ua = Net::Amazon->new( 15 token => 'YOUR_AMZN_TOKEN' 16 ); 17 18 die "usage: $0 asin=0201360683\n" 19 unless param('asin'); 20 21 my $req = Net::Amazon::Request::ASIN->new( 22 asin => param('asin') 23 ); 24 25 my $resp = $ua->request($req); 26 27 print redirect( 28 $resp->properties->ImageUrlLarge());
Und Listing roll
findet schließlich alle Rolling-Stones-CDs unter
$13.99
. Die substr()
-Funktion schneidet vom Preis
das an erster Stelle stehende Dollarzeichen ab, bevor er mit einem
Fließkommawert verglichen wird.
Zu beachten
ist, dass es bei Amazon für klassische und Pop-CDs unterschiedliche
Bereichte gibt (die modes heissen classical
bzw. music
).
Objekte vom Typ Net::Amazon::Property::Music
führen außer einer
artist()
-Methode für den Interpreten auch album()
für den CD-Titel.
01 #!/usr/bin/perl 02 ########################################### 03 # All Rolling Stones CDs for < $13.99 04 # Mike Schilli <mschilli1@aol.com>, 2003 05 ########################################### 06 07 use strict; 08 use warnings; 09 10 use Net::Amazon; 11 use Net::Amazon::Request::Keyword; 12 13 my $ua = Net::Amazon->new( 14 token => 'YOUR_AMZN_TOKEN', 15 max_pages => 10, 16 ); 17 18 my $req = Net::Amazon::Request::Keyword->new( 19 keyword => "Rolling Stones", 20 mode => "music" 21 ); 22 23 # Response: Net::Amazon::Keyword::Response 24 my $resp = $ua->request($req); 25 26 for ($resp->properties) { 27 if($_->Catalog eq "Music" && 28 $_->OurPrice && 29 substr($_->OurPrice, 1) < 13.99) { 30 print $_->album, " ", 31 $_->OurPrice(), "\n"; 32 } 33 }
Zur Zeit steht der Webservice nur für den amerikanischen amazon.com
und den britischen amazon.co.uk zur Verfügung. Für den letzteren ist
dem Konstruktor der Net::Amazon
-Klasse zusätzlich das Wertepaar
locale => uk
zu übergeben.
Die deutsche Schwester Amazon.de hatte leider bis Redaktionsschluss
noch nicht nachgezogen, die
Jungs und Mädels aus Hinterpfuideifel bei München
hinken ja dem in den USA stationierten Mutterschiff immer ein
wenig hinterher -- aber vielleicht wird's ja bald was, praktisch wär's!
Viel Spass beim Stöbern!
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. |