January 09, 2005

amazlet.com の売れ筋商品情報の実装

[ Perl ]

先のエントリで紹介した amazlet.com のカテゴリ別ランキングですが、実際には AWS 3.0 の BrowseNodeSearch を使って実現しています。その商品の BrowseID を引数に BrowseNodeSearch を売り上げ順ソートで実行して、その結果を出力、で完了です。ただ、いろいろバッドな対処が必要なところがありました。

一つ目は Net::Amazon で BrowseId が取得できない点。これはどうも仕様みたいです。

AWS 3.0 の AsinSearch (の type=heavy) では応答の XML の中に BrowseList というエレメントが含まれています。()

<BrowseList>
  <BrowseNode>
    <BrowseId>575120</BrowseId> 
    <BrowseName>ジャンル別 - 外国映画 - アクション - パニック・スペタクル</BrowseName> 
  </BrowseNode>
  <BrowseNode>
    <BrowseId>3713711</BrowseId> 
    <BrowseName>ストア - COOP - 20世紀FOXストア - ドラマ</BrowseName> 
  </BrowseNode>
  <BrowseNode>
    <BrowseId>3713741</BrowseId> 
    <BrowseName>ストア - COOP - 20世紀FOXストア - アクション</BrowseName> 
  </BrowseNode>
  ...
</BrowseList>

こんな具合です。今回の実装においてはこの BrowseList の情報が必要なわけですが、Net::Amazon においては BrowseList の子要素である BrowseId を取得する方法が用意されていません。で、なぜか BrowseName は取得できます。

#!/usr/local/bin/perl
use strict;
use warnings;
use Net::Amazon;
use Encode;
 
use constant DEV_TOKEN => 'DM1XQEXM3YQU8';
 
my $ua = Net::Amazon->new(
    token => DEV_TOKEN,
    locale => 'jp',
);
 
my $asin = shift or die "need asin";
my $response = $ua->search( asin => $asin );
if ($response->is_error) {
    die $response->message;
}
 
for my $prop ($response->properties) {
    print encode('euc-jp', $_)."\n"
        for ($prop->browse_nodes);
}

こんなスクリプトを書いてやって実行すると、

[naoya@judy naoya]$ perl browsenode.pl B0001A7D22
ジャンル別 - 外国映画 - アクション - パニック・スペタクル
ストア - COOP - 20世紀FOXストア - ドラマ
ストア - COOP - 20世紀FOXストア - アクション
ストア - COOP - ストア別全ASIN - 20世紀FOXストア
ストア - バーゲンコーナー - 外国映画
ユーズドDVD - 外国映画
ユーズドDVD - 外国映画 - アクション
ユーズドDVD - 外国映画 - アドベンチャー

という具合に BrowseName の値はリストで返ってくるのですが、BrowseId が取得できず。変な仕様です。そこで、Hack しようということになるのですが Net::Amazon のコードに直接手を入れてしまうと後々めんどいので、自前のクラスで拡張してみました。

Net::Amazon::PropertyExt というクラスを作成。このクラスは Net::Amazon::Property の Decorator として実装しました。また、ひとつひとつの BrowseNode は Net::Amazon::BrowseNode というクラスのインスタンスとして実装しました。

for my $prop ($response->properties) {
    my $propext = Net::Amazon::PropertyExt->new($prop);
 
    # $node は Net::Amazon::BrowseNode
    for my $node ($propext->browselist) {
        sprintf("%d %s\n" $node->BrowseId, $node->BrowseName);
    }
}

こんな感じで使えば、BrowseList からそれぞれの BrowseNode の Id と Name をスマートに引っ張り出せるという代物です。

Net::Amazon::BrowseNode はただの入れ物なので、setter/getter があるだけのシンプルな実装です。

package Net::Amazon::BrowseNode;
use strict;
 
sub new {
    my ($class, $args) = @_;
    my $self = bless {}, $class;
    $self->{BrowseId} = $args->{BrowseId};
    $self->{BrowseName} = $args->{BrowseName};
    $self;
}
 
sub BrowseId {
    my $self = shift;
    @_ ? $self->{BrowseId} = shift : $self->{BrowseId};
}
 
sub BrowseName {
    my $self = shift;
    @_ ? $self->{BrowseName} = shift : $self->{BrowseName};
}
 
1;

Net::Amazon::PropertyExt は Decorator パターンで Net::Amazon::Property を内包します。AUTOLOAD と再bless による Decorator のどちらにしようかと思いましたが、なんとなく再bless でやってみました。

package Net::Amazon::PropertyExt;
use strict;
use base qw(Net::Amazon::Property);
use Net::Amazon::BrowseNode;
 
sub new {
    my $class = shift;
    my $prop = shift or die;
    my $self = bless $prop, $class;
    $self->_set_browselist;
    return $self;
}
 
sub _set_browselist {
    my $self = shift;
    my $browse_nodes = $self->{xmlref}->{BrowseList}->{BrowseNode};
    if(ref($browse_nodes) eq "ARRAY") {
      my @nodes = map {
        Net::Amazon::BrowseNode->new({
            BrowseName => $_->{BrowseName},
            BrowseId   => $_->{BrowseId},
        }); } @{ $browse_nodes };
      $self->browselist(\@nodes);
    } elsif (ref($browse_nodes) eq "HASH") {
      $self->browselist([ Net::Amazon::BrowseNode->new({
          BrowseName => $browse_nodes->{BrowseName},
          BrowseId   => $browse_nodes->{BrowseId},
      }) ]);
    } else {
      $self->browse_nodes([ ]);
    }
}
 
sub browselist {
    my $self = shift;
    @_ ? $self->{__browselist} = shift: $self->{__browselist};
}
 
1;

これで BrowseNode をオブジェクトとして取り出すことができます。取り出した BrowseNode の Id を引数に BrowseNodeSearch を実行すれば、その BrowseNode の売れ筋が得られます。

ひとつの商品に対して、BrowseNode は複数定義される場合があるのですが amazlet.com ではそのうちひとつの BrowseNode に絞ってランキングを出したかったので、Id でソートして一番若い BrowseNode のそれを使うようにしました。なんとなく Id が若いものが一番しっくりくる BrowseNode の気がしたので。

また、「パニック・スペタクル関連商品の売れ筋商品はこちら!」といった感じでラベルを出したいのですが、BrowseName は「ジャンル別 - 外国映画 - アクション - パニック・スペタクル」と長く使いにくいラベルになってるので、ここは力技で正規表現により一番後のジャンルを表示しています。

結構適当な仕様ですが、いざ動かしてみると案外それなりのラベルになってくれました。しかし、AWS 3.0 はそろそろ離れて ECS 4.0 の方で Hack していきたいところです。

Posted by naoya at January 9, 2005 06:48 PM | トラックバック (0)  b_entry.gif
トラックバック [0件]
TrackBack URL: http://mt.bloghackers.net/mt/suck-tbspams.cgi/1424
コメント [0件]
コメントする









名前、アドレスを登録しますか?