Letztens stiess ich zufällig auf ein DVD Angebot von Amazon, welches um 50% reduziert war. Nun ist dies zugegebenermaßen nichts besonders spannendes, aber ich wollte wissen ob es vielleicht noch ein paar andere gute Angebote gibt.

Zunächst dachte ich daran, den Amazon E-Commerce Web Service zu verwenden, aber zu meiner Enttäuschung bietet dieser keinerlei Möglichkeit zur Abfrage von Preisen, und muss daher zur Schnäppchenjagd eine Nischenrolle zugewiesen bekommen.

Das Ergebnis sind also ein paar Skripte, die die Amazon Produkt Webseiten runterladen und parsen. Ziemlich hässlich das alles, aber was solls.

Obwohl das alles reichlich unschön ist und auch bei der nächsten kleinen Änderung von Seiten Amazons an ihren Webseiten nichtmehr tun wird, wollte ich es euch nicht vorenthalten.

Das folgende Skript lädt alle Titel in einer bestimmten Kategorie in Amazon mittels des AWS ECS herunter und speichert sie in der Datei AmazonCache.pm.

Leider beschränkt Amazon die Abfrage auf maximal 4000 Titel insgesamt (keine Möglichkeit das zu umgehen), also muss man sich entweder klein genügende Kategorien aussuchen oder evtl. mit Suchabfragen hantieren, irgendwie. (All diese Ideen sind hier nicht implementiert.)

#!/usr/bin/perl
use strict; use warnings;
use utf8;

my $susbscription_id = "00000000000000000000";
use AWSECommerceService; # Amazon laden
use Data::Dumper; 

my @data = ();

############## Amazon Daten abrufen ###############

my $page = 1; my $total_pages = 0;
do {
 print 'Lade Seite ', $page, "\n";
 
 my $obj = AWSECommerceService->new;
 $obj->ItemSearch(
    SOAP::Data->value(
        SOAP::Data->name('SubscriptionId')
            ->value($susbscription_id),
        SOAP::Data->name('Request')
            ->value(
                \SOAP::Data->value( # \ sorgt für korrekte Verschachtelung
                    SOAP::Data->name('SearchIndex')
                        ->value('DVD'),
                        #->value('Books'), # Für Bücher, bringt aber in Dtl wegen Preisbindung wenig
                        #->value('VideoGames'), # Videospiele
                    SOAP::Data->name('ItemPage')
                        ->value($page),
                    SOAP::Data->name('BrowseNode')
                        ->value('508098') # Anime
                        #->value('698198') # Manga
                        #->value('3504091') # Manga Übersicht
                        #->value('639320') # GameCube
                )
            )
    )
 );
 my $som = $obj->{_call};
 sleep 1;
 
 $total_pages = $som->valueof('//TotalPages');
 print 'Seiten insgesamt: ', $total_pages, "\n" unless $page > 1;
 
 my $node_num = 1;
 while($som->match("//Items/[$node_num]")) {
    $node_num++;
    next unless $som->match("//Items/[$node_num]/ItemAttributes");  
    my @authors = $som->valueof("//Items/[$node_num]/ItemAttributes/Author");
    my $item = $som->valueof("//Items/[$node_num]");
    push @data, {
        Title => $item->{'ItemAttributes'}->{'Title'},
        Authors => [ @authors ],
        ASIN => $item->{'ASIN'},
        Owned => 0
    };
 }
 unless ($page % 10) {
    open my $fh, '>', 'AmazonCache.pm'; print $fh Dumper(\@data); close $fh;
 }
 
} while ( $page++ <= $total_pages and $page < 1000 );

open my $fh, '>', 'AmazonCache.pm'; print $fh Dumper(\@data); close $fh;

Hier ist der passende HTML Seiten Downloader dazu:

use strict; use warnings;
use utf8; use encoding 'utf8';
use LWP::Simple;

mkdir 'grab' unless -d 'grab';

use vars qw($VAR1);
use AmazonCache; # Cache laden
my $cache = $VAR1;

for my $item (@$cache) {
    print $item->{ASIN}, "\n";
    LWP::Simple::mirror('http://www.amazon.de/dp/'.$item->{ASIN}, 'grab/'.$item->{ASIN}.'.html');
}

Und schließlich ein Skript was die Ersparnisse aus den heruntergeladenen HTML Seiten rausparst:

use strict; use warnings;
use utf8;
use XML::LibXML;

my $p = XML::LibXML->new;
$p->recover(2);
$p->load_ext_dtd(0);
$p->expand_entities(0);
$p->complete_attributes(0);

my @c;
for my $f (<grab/*.html>) {
    my $d = $p->parse_html_file($f);
 
    my ($prod_table) = $d->findnodes('//*[@id="priceBlock"]/table[@class="product"]');
    next unless $prod_table;
 
    my $savings = $prod_table->findvalue('//tr[contains(td[@class="productLabel"],"sparen")]/td[@class="price"]');
    next unless $savings;
 
    my $title = $d->findvalue('//div[@class="buying"]/b[@class="sans"]');
 
    my $listprice = $prod_table->findvalue('//tr[td[@class="productLabel"]]/td[@class="listprice"]');
 
    my $newprice = $prod_table->findvalue('//tr[td[@class="productLabel"]]/td/b[@class="price"]');
 
    push @c, [ mangle($f), $title, $newprice, $listprice, $savings ];
    push @{$c[-1]}, mangle($c[-1][-1]);
}
display();

sub mangle {
    my ($e) = @_;
    for ($e) {
        chomp;
        $_ = $1 if m!^grab/(.*)\.html$!;
        $_ = $1 if m!(\d+(?:[.,]\d+)?)%!;
        y!,!.!;
        $_ += 0 if m!^\d+(?:[.]\d+)?$!;
    }
    $e
}

sub display {
    for my $e (sort { $b->[-1] <=> $a->[-1] } @c) {
        print $e->[0],":\n\tTitel:\t", $e->[1],
        "\n\tPreis:\t", $e->[2],
        "\n\tAlt:\t", $e->[3],
        "\n\tSpar:\t", $e->[4], "\n";
    }
    print '-'x20,"\n";
}