Aus Linux-Magazin 07/2003

Perl durchsucht den Amazon-Produktkatalog per Webservices

Dem Beispiel des famosen Suchdienstes Google folgend, bietet nun auch das virtuelle Kaufhaus Amazon.com seinen Produktkatalog per XML und Webservice an. Perl-Programmierer nutzen dies und suchen gezielt nach Produkten und Preisen.

Oft sind Browser zu unhandlich, um im Amazon-Katalog zu wühlen. Dann ist es praktischer, per Perl-Skript etwa durch alle angebotenen Perl-Bücher zu gehen und jenes herauszusuchen, auf das Amazon den höchsten Preisnachlass gibt. Per Skript könnte auch eine Website das Coverbild einer beliebigen CD darstellen, deren ASIN (Amazon Standard Item Number) sie kennt, oder alle Rolling-Stones-CDs ausgeben, die weniger als 13,99 US-Dollar kosten.

Amazon bietet seit einiger Zeit den programmierbaren Zugriff auf seine Datenbank an. Sowohl eine SOAP-Schnittstelle (Simple Object Access Protocol) als auch eine vereinfachte XML-über-HTTP-Anbindung ist möglich. Für einfache Anfragen mit einigen Parametern, auf die der Server mit Datensätzen antwortet, ist SOAP fast schon überdimensioniert. Daher bürgert es sich in der Webservice-Welt immer mehr ein, dass ein Webserver einfache parametrisierte HTTP-Requests entgegennimmt und mit XML antwortet. Das Programmieren mit Perls »SOAP::Lite« ist zwar kinderleicht, aber mit der folgenden XML/HTTP-Schnittstelle geht es noch einfacher.

Leichter als SOAP

Um das Buch mit der ISBN-Nummer 0201360683 zu finden, genügt bei Amazon.com ein HTTP-Request mit folgender URL:

http://xml.amazon.com/onca/xml2?t=xxx&dev-t=yyy&AsinSearch=0201360683&type=lite&f=xml

Der Request enthält die fünf folgenden Parameter:

  • »t«: Die Amazon-Partner-ID des
    Amazon-Associates-Programms. Sie dient nur dazu, die vom Webservice
    zurückgesandten Links mit der Partner-ID zu bestücken.
    Wer keine hat, gibt »webservices-20« an.
  • »dev-t«: Das Amazon-Developer-Token kann sich jeder
    Entwickler bei Amazon.com unter[3] abholen, wenn er seine
    E-Mail-Adresse hinterlässt und den Nutzungsbedingungen
    zustimmt. Mit dem Token (während dieser Artikel entstand,
    funktionierten beliebige Werte) kann er dann nach den Bestimmungen
    beliebig viele Anfragen senden, wenn er unter der Schmerzgrenze von
    einem Request pro Sekunde bleibt.
  • »AsinSearch«: Die eindeutige Produktnummer auf
    Amazon.com. Bei Büchern ist sie identisch mit deren
    ISBN-Nummer.
  • »type«: Legt mit »lite« oder
    »heavy« fest, ob die Kurz- oder Langform der
    XML-Antwort gewünscht wird.
  • »f«: Setzt mit »xml« das Ausgabeformat
    auf XML.

Der Amazon.com-Webservice wird daraufhin eine XML-Antwort wie in Abbildung 1 zurückschicken, die alle notwendigen Produktdaten des angeforderten Artikels enthält, sowie Links auf die von Amazon angebotenen Abbildungen, den Amazon-Preis, den Listenpreis und vieles mehr.

Abbildung 1: Der Amazon-Webservice antwortet mit diesen XML-Daten auf eine ASIN-Anfrage (Amazon.com Standard Item Number). Gesucht war das Buch mit der ISBN-Nummer 0201360683.

Abbildung 1: Der Amazon-Webservice antwortet mit diesen XML-Daten auf eine ASIN-Anfrage (Amazon.com Standard Item Number). Gesucht war das Buch mit der ISBN-Nummer 0201360683.

Produktsuche per Katalognummer

Solche Daten lassen sich mit Perl und einem Modul wie »XML::Simple« hervorragend extrahieren und für weitere Forschungen umwandeln und zurechtbiegen. Damit auch das noch einfacher wird, steht unter[2] (und auf dem CPAN) das brandneue Modul »Net::Amazon« zur Verfügung. Listing 1 zeigt, wie schön objektorientiert man so mit dem Amazon-Service spielen kann.

ASIN-Nummer
suchen

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 asinn(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 );
24 
25   # Response is Net::Amazon::ASIN::Response
26 my $resp = $ua->request($req);
27 
28 if($resp->is_success()) {
29     print $resp->as_string(), "n";
30 } else {
31     print "Error: ",
32           $resp->message(), "n";
33 }

Am Anfang zieht das Skript die beiden Module »Net::Amazon« und »Net::Amazon::Request::ASIN« herein. »Net::Amazon« funktioniert ähnlich wie die bekannte LWP-Library, die zunächst einen User-Agent und einen Request definiert und den Request anschließend an das User-Agent-Objekt weiterreicht.

Das »Net::Amazon«-Objekt nimmt in Zeile 15 das Developer-Token entgegen und führt danach beliebig viele Anfragen aus. Das Skript erwartet die ISBN-Nummer auf der Kommandozeile. Falls sie 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 sendet den HTTP-Request an den Amazon-Server, schluckt das zurückkommende XML, analysiert es und legt es im Antwortobjekt »$resp« ab. Dieses Objekt vom Typ »Net::Amazon::Response::ASIN« enthält dann die XML-Daten als Hash-Struktur.

Alle »Net::Amazon::Response::*«-Objekte verfügen über folgende Methoden:

  • »is_success()«: True, wenn die Suche erfolgreich
    war.
  • »is_error()«: Ein Fehler ist aufgetreten.
  • »message()«: Etwaige Fehlermeldung.
  • »as_string()«: Fasst die wichtigsten
    Ergebnisparameter lesbar zusammen.

Der Aufruf von »asin_fetch« mit der ASIN 0596000278 fördert folgendes Buch 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 beispielsweise Bücher, CDs oder Elektrogeräte. Zurzeit unterstützt das Modul explizit »Net::Amazon::Property::Book« sowie »Net::Amazon::Property::Music« – der Rest strandet als Objekt der Klasse »Net::Amazon::Property«. Die »properties()«-Methode eines Response-Objekts gibt eine Liste aller gefundenen Objekte zurück.

Die Basisklasse »Net::Amazon::Property« und ihre Kindklassen enthalten je eine Methode für jeden Produktparameter, den Amazon.com in XML zurückliefert (siehe Tabelle 1). Die Parameter 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, bieten spezialisierte Objekte wie »Net::Amazon::Property::Book« bequemere Methoden an. Eine Property von diesem Typ gibt mit der zusätzlichen Methode »authors()« die Liste der Autorennamen zurück – ohne Umweg über Unter-Hashes.

Property-Methoden

Property-Methoden

Schlüsselwort finden

Als weiteres Beispiel zeigt Listing 2, wie die Suche nach einem Schlüsselwort in einem Produktbereich klappt. Der Konstruktor des »Net::Amazon«-Objekts nimmt ab Zeile 17 nicht nur das Developer-Token entgegen, sondern auch den Parameter »max_pages«, der dort auf »5« gesetzt ist (entspricht der Standardeinstellung). Trifft eine Suchanfrage auf mehrere Produkte zu, liefert Amazon immer nur zehn auf einmal. Weitere Web-Requests müssen dann die folgenden Seiten nachholen. Ist der Parameter »max_pages« gesetzt, holt »Net::Amazon« automatisch die fehlenden Ergebnisse nach, bis die maximale Seitenzahl erreicht ist.

Zeile 11 zieht das »Net::Amazon:: Request::Keyword«-Modul herein. Der Konstruktoraufruf ab Zeile 22 setzt dann per »keyword«-Parameter das Schlüsselwort und per »mode«-Parameter den Produktbereich. Das Schlüsselwort (zum Beispiel »perl«) und der Produktbereich (»books«, »music«, »classical«, »electronics« und so weiter) stammen von der Kommandozeile.

Zeile 31 iteriert über die Liste der gefundenen »Property«-Objekte; diese Liste stammt von der »properties()«-Methode des »Response«-Objekts. Auch bei explizit angegebenem Suchbereich liefert Amazon manchmal Produkte aus anderen Bereichen, also stellt Zeile 33 sicher, dass es sich wirklich um Bücher handelt. Sie vergleicht dazu den »Catalog()«-Eintrag mit dem String »Book« und überspringt bei Abweichungen den Treffer. Hier sind Amazons Bezeichnungen übrigens nicht konsistent: Der »mode«-Parameter (Zeile 25) erwartet »books«, der »Catalog()«-Eintrag enthält »Book«.

Ab Zeile 35 kommen sowohl spezielle Methoden der »Net::Amazon::Property::Book«-Klasse (»authors()«, »title()«) als auch generische Methoden der »Net::Amazon::Property«-Klasse zum Einsatz (»Asin()«, »OurPrice()«), um den Treffer anzuzeigen.

Die Stichwortsuche fördert manchmal unerwartete Ergebnisse zu Tage. Wer nach Perl sucht, wird sich über ein Javascript-Buch vermutlich wundern. Genau das bietet ihm Amazon aber an:

$ keyword perl books
0596000480, David Flanagan, JavaScript:
  The Definitive Guide, $31.47
0596000278, Larry Wall/Tom Christiansen/Jon Orwant,
  Programming Perl (3rd Editio...

Wer herausfinden will, auf welche Perl-Bücher Amazon den höchsten Rabatt gibt, wird mit Perls Hilfe auch fündig. Listing 3 sucht nach einem Stichwort (etwa »perl«) und holt bis zu 200 Treffer. Anschließend filtert es die von »properties()« zurückgelieferten Artikel mit einem »grep«-Kommando und prüft, ob jeder Treffer tatsächlich ein Buch ist und ob der Titel tatsächlich das gesuchte Schlüsselwort enthält.

Die »sort«-Funktion in Zeile 32 sortiert absteigend nach dem Ergebnis der Funktion »saved«, die ab Zeile 46 definiert ist und zu einem Buch-Objekt die prozentuale Ersparnis zwischen Listenpreis und Amazon-Angebot errechnet. Effizient ist das zwar nicht, das Programm könnte auch eine Schwartz-Transformation zwischenschalten und nur die billigsten Bücher ständig bereithalten, falls es Tausende von Treffern untersuchen muss. Für die gezeigte Menge macht das aber kaum einen Unterschied.

Die Preisfelder enthalten übrigens beim amerikanischen Amazon ein Dollar-Zeichen, das die Zeilen 52 und 53 entfernen müssen, bevor Zeile 55 damit rechnet. Die »for«-Schleife ab Zeile 37 gibt die fünf aufregendsten Preisknüller mit prozentualer Ersparnis und Titel aus.

Suche nach
Schlüsselwort

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 moden(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 }

Rabatt-Suche

01 #!/usr/bin/perl
02 ###########################################
03 # cheapo - search for maximum discount
04 #       cheapo keyword
05 # Mike Schilli <mschilli1@aol.com>, 2003
06 ###########################################
07 
08 use strict;
09 use warnings;
10 
11 use Net::Amazon;
12 use Net::Amazon::Property;
13 use Net::Amazon::Request::Keyword;
14 
15 die "usage: $0 keyword" unless
16     defined $ARGV[0];
17 
18 my $ua = Net::Amazon->new(
19     token       => 'YOUR_AMZN_TOKEN',
20     max_pages   => 20,
21 );
22 
23 my $req = Net::Amazon::Request::Keyword->new(
24     keyword   => $ARGV[0],
25     mode      => "books"
26 );
27 
28  # Response: Net::Amazon::Keyword::Response
29 my $resp = $ua->request($req);
30 
31 my @books =
32    sort { saved($b) <=> saved($a) }
33    grep { $_->Catalog eq "Book" &&
34           $_->title =~ /$ARGV[0]/i }
35     $resp->properties;
36 
37 for(0..4) {
38     printf "%.2f%% (%s/%s) %snn",
39           saved($books[$_]),
40           $books[$_]->ListPrice,
41           $books[$_]->OurPrice,
42           $books[$_]->as_string;
43 }
44 
45 ###########################################
46 sub saved {
47 ###########################################
48     my($book) = @_;
49 
50     my $list = $book->ListPrice;
51     my $our  = $book->OurPrice;
52     $list =~ s/$//;
53     $our  =~ s/$//;
54 
55     return ($list - $our)/$list*100;
56 }

Bilderklau

Amazon präsentiert seine Produkte mit Abbildungen. Ein CGI-Skript kann das nutzen, um zu einer vorgegeben ASIN das passende Produktfoto anzuzeigen. Listing 4 enthält eine Implementierung, die einfach bei Amazon die URL abholt und 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: 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 keine Wohlfahrtsorganisation.

Hoch spezialisiert ist Listing 5: Es findet alle Rolling-Stones-CDs, die weniger als 13,99 US-Dollar kosten. Zeile 29 schneidet per »substr()«-Funktion vom Preis das an erster Stelle stehende Dollarzeichen ab, bevor sie ihn mit einem Fließkommawert vergleicht.

Bild
holen

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=0201360683n"
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());

Rolling-Stones-CDs

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 }

Bei der Suche zu beachten ist, dass Amazon klassische Musik und Pop-CDs in unterschiedliche Bereiche einsortiert, der Modus lautet »classical« oder »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.

Zurzeit steht der Webservice nur für die amerikanische Filiale Amazon.com und die britische Amazon.co.uk zur Verfügung. Für Letztere 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 in München segeln dem in den USA stationierten Mutterschiff immer ein wenig hinterher – aber vielleicht wird\’s ja bald. Viel Spaß beim Stöbern! (fjl)

Infos

[1] Listings zu diesem Artikel: [ftp://www.linux-magazin.de/pub/listings/magazin/2003/07/Perl]

[2] Distribution des Perl-Moduls »Net::Amazon«: [http://perlmeister.com/devel/#amzn]

[3] Die Amazon-SOAP-Seite mit Development- Kits und Beschreibung: [http://amazon.com/soap]

[4] Brian D. Foy, “Amazon.com wishlists”: The Perl Journal 02/03 (nur per Abo und online erhältlich)

Der
Autor


Michael Schilli arbeitet als Web-Engineer für AOL/Netscape in Mountain View, 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 ist [http://perlmeister.com].

LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben