適当な思いつきで書くブログ

UbuntuやPerlやJavaScriptやVimやZshやShellScriptやMySQLと戯れている中で適当な思いつきでやってみたことを書いています。

Amazon APIのキーワード検索結果をXML::TreePPとHTML::Template::Compiled使って一覧表示してみた(Perl始めました2日目)

テーマ:検索機能とその結果一覧を表示する

前回Amazon APIから個別商品情報をXMLで取得して、それをXML:TreePPで配列に変換、情報を抜き取ってHTML::Template::CompiledでHTML出力までやってみました。

今回は、ユーザが入力したキーワードをリクエストとしてAmazon APIに渡し、その一覧をHTML出力してみたいと思います。


(先に感想を述べちゃうと、背伸びし過ぎました><)

今回いろいろなところで挫折orz

背伸びし過ぎたせいでいろいろなところでつまづきました。

挫折その1/2:エラーヒントがもらえずに困った><

エラーが出ても今までエラーとしか言われなくて、何行目がどんなエラーなのか皆目検討がつかず困っていたところ、ぐぐってみたら「デバッグの基本 'PERL-LABO'」でエラーヒントを表示する方法を教えてもらいました(というか読ませてもらいました。)

use CGI::Carp qw(fatalsToBrowser);

これでだいぶ楽になりました。

PHPならデフォルトでエラー出してくれるのになーとかって言っちゃいけないんですかね(^ー^;

挫折その2/2:HTML::Templateでハッシュを思い通りに出力できず困った><

Amazonの個別商品情報は比較的簡単に表示したい情報を抜き取ることが出来たのですが、今回はキーワードにヒットした商品が個々のリストで返ってくるので必要な情報の抜き取りに時間がかかりました。

結果的に使用していたモジュールでは対応できないというオチだった(と思う)のですが…。


ということで、今回使用したTemplateモジュールはHTML::Template::Compiledです。

下記お二人の記事を参考にさせて頂きました。ありがとうございました。


HTML::Templateに較べ強力な気がします。

配列をJavaScriptのように追っていくことができ、

//RequestのItemSearchRequestのKeywordsのvalueを出力
<h2>"<tmpl_var name=Request.ItemSearchRequest.Keywords>"の検索結果</h2>

20080313.tmpl(36行目)

また、配列であればloopもしてくれます。

<tmpl_loop name=Item>
<tr>
  <td>
    <a href="http://www.amazon.co.jp/exec/obidos/ASIN/<TMPL_VAR name=ItemAttributes.ASIN>/tekiomo-22/ref=nosim/"><img src="http://images-jp.amazon.com/images/P/<tmpl_var name=ASIN>.09.TZZZZZZZ.jpg"></a>
  </td>
  //(… 中略 …)
</tr>
</tmpl_loop>

20080313.tmpl(40-49行目)

id:holidays-lさんに添削してもらったので Re2: めっきり春だし今日からPerlを始めました

ありがとうございました。

#!/usr/bin/perl を #!/usr/bin/env perl に変えた。perlが/usr/local/bin/等にあってもパスが通ってれば動きます。

あーなるほど。「動かない場合はご利用されているサーバーの(ry」な文章が不要なのですね。

use warningsを追加(#!/usr/bin/env perl -wでもいいけど)

作り終わった後に↓読みました。ごめんなしあー

Perl5編 第5章 安全なスクリプトを書く

(そういえば"use warnings"を追加してから作り始めたのですが、エラーが出まくって消したら動いたのでコメントアウトしていましたw)

HTML::Templateの代わりにTemplateを使用。(ただの好み)

テンプレートをスクリプトに埋め込んだ(1個ぐらいなんだったら埋め込んじゃった方がよさげ)

おっしゃる通りです。

「今後を見据えて」&ひとりで全部やっていますが「デザインとプログラムの分離」ということで"まとめずに"の方向で何卒。

Perl::Tidy

Perl::Critic

ぐぐって読みました。勉強になります。

結果:いろいろありましたができました

なんとか出来上がりました。

デモはこちら


けどソース汚いです。ページングのあたりもうちょっとなんとかできそうです。

また、気合いで作った部分が多くこれでは勉強になっていない気もします…。

次回は商品カテゴリ(Books/Music/DVDなど)別の表示切替とかかな

デモを見て頂けると分かると思いますが、今回商品別の表示はサムネルとタイトルのみです。

Amazon APIはBooksカテゴリであれば"著者"や"発行者"、Musicカテゴリであれば"アーティスト名"や"レコード会社"など返ってくる情報が商品カテゴリ別に異なります。次回はサムネイルおよびタイトル以外の情報を商品カテゴリ別に表示したいと思います。


ソースは下記


20080314.pl(perl)

#!/usr/bin/env perl

use strict;
use warnings;
use CGI;
#use CGI::Carp qw(fatalsToBrowser);
use XML::TreePP;
use HTML::Template;
use HTML::Template::Compiled;


my $q = CGI->new;
my $ItemPage = $q->param('ItemPage') || "1";
my $SearchIndex = $q->param('SearchIndex') || "Books";
my $Keywords = $q->param('Keywords') || "Perl";


my $tpp = XML::TreePP->new(force_array => [ "Item", "Author" ]);
my $req_url = "http://webservices.amazon.co.jp/onca/xml?SearchIndex=" . $SearchIndex . "&Keywords=" . $Keywords . "&ItemPage=" . $ItemPage . "&ResponseGroup=Small&Operation=ItemSearch&Service=AWSECommerceService&AWSAccessKeyId=/*あなたのAmazonアクセスキー*/";
my $tree = $tpp->parsehttp(GET => $req_url);

my $template = HTML::Template::Compiled->new(
  filename => '20080313.tmpl',
);


#現在のページ
my $carrent_page = $tree->{ItemSearchResponse}->{Items}->{Request}->{ItemSearchRequest}->{ItemPage};

#表示件数の最小値
my $carrent_min = ($carrent_page - 1) * 10 + 1;

#表示件数の最大値
my $carrent_max = $tree->{ItemSearchResponse}->{Items}->{TotalResults} < $carrent_page * 10
   ? $tree->{ItemSearchResponse}->{Items}->{TotalResults}
   : $carrent_page * 10;

#ページング
my $tortal_page = $tree->{ItemSearchResponse}->{Items}->{TotalPages};
my $paging_html = '';
my $paging_min_page = $carrent_page <= 4
                      ? 1
                      : $carrent_page <= $tortal_page - 4
                        ? $carrent_page - 4
                        : $tortal_page - 9;
my $paging_max_page = $carrent_page <= $tortal_page - 3
                      ? $paging_min_page + 9
                      : $tortal_page;

#前へ
if ($carrent_page != 1) {
  $paging_html  .= "<a href='20080313.pl?SearchIndex=" . $tree->{ItemSearchResponse}->{Items}->{Request}->{ItemSearchRequest}->{SearchIndex}
                . "&Keywords=" . $tree->{ItemSearchResponse}->{Items}->{Request}->{ItemSearchRequest}->{Keywords}
                . "&ItemPage=" . ($carrent_page - 1) . "'>&lt;&lt;&nbsp;前へ</a>\n";
}

#ページ別リンク
for (my $i = $paging_min_page; $i <= $paging_max_page; $i++) {
  $paging_html  .= "&nbsp;";
  
  if ($i == $carrent_page) {
    $paging_html  .= "<strong>$i</strong>\n"
  } else {
    $paging_html  .= "<a href='20080313.pl?SearchIndex=" . $tree->{ItemSearchResponse}->{Items}->{Request}->{ItemSearchRequest}->{SearchIndex}
                  . "&Keywords=" . $tree->{ItemSearchResponse}->{Items}->{Request}->{ItemSearchRequest}->{Keywords}
                  . "&ItemPage=$i'>$i</a>\n";
  }
  
  $paging_html  .= "&nbsp;";
}

#次へ
if ($carrent_page != $tortal_page) {
  $paging_html  .= "<a href='20080313.pl?SearchIndex=" . $tree->{ItemSearchResponse}->{Items}->{Request}->{ItemSearchRequest}->{SearchIndex}
                . "&Keywords=" . $tree->{ItemSearchResponse}->{Items}->{Request}->{ItemSearchRequest}->{Keywords}
                . "&ItemPage=" . ($carrent_page + 1) . "'>次へ&nbsp;&gt;&gt;</a>\n";
}


$template->param(Item => $tree->{ItemSearchResponse}->{Items}->{Item});
$template->param(Request => $tree->{ItemSearchResponse}->{Items}->{Request});
$template->param(
  Results => {
    "TotalResults"  => $tree->{ItemSearchResponse}->{Items}->{TotalResults},
    "TotalPages"    => $tree->{ItemSearchResponse}->{Items}->{TotalPages},
    "CarrentMinNum" => $carrent_min,
    "CarrentMaxNum" => $carrent_max,
    "Request"       => $tree->{ItemSearchResponse}->{Items}->{Request}
  }
);
$template->param(
  SearchIndex => [
    "Blended", "Books", "Music", "MusicTracks", "Classical", "Video", "DVD", "VHS", "VideoGames", "Electronics", "Kitchen", "Toys", "Software"
  ]
);
$template->param(Paging => $paging_html);


print "Content-Type: text/html\n\n", $template->output;

追記:ページングの処理がおかしかったので修正(検索結果が10ページに満たないときの処理)

#39行目あたりから
my $paging_min_page = $carrent_page <= 4
                      ? 1
                      : $carrent_page <= $tortal_page - 4
                        ? $carrent_page - 4
                        : $tortal_page - 9 <= 0
                          ? 1
                          : $tortal_page - 9;
my $paging_max_page = $tortal_page - 4 <= $carrent_page
                      ? $tortal_page
                      : $paging_min_page + 9 <= $tortal_page
                        ? $paging_min_page + 9
                        : $tortal_page;
<||

20080314.tmpl(template)
>|html|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>"<tmpl_var name=Request.ItemSearchRequest.Keywords>"の検索結果 - Amazon検索</title>
<style type="text/css">
/*&lt;![CDATA[*/
  body{
  	font-size: 90%;
  	line-height: 1.4;
  }
  img{
  	border: solid 1px #CC;
  }
  h1{
  	font-size: 120%;
  }
/*]]&gt;*/
</style>
</head>

<body>

<h1>Amazon検索</h1>

<form method="get" action="">
  <select name="SearchIndex">
    <tmpl_loop name=SearchIndex>
    <option value="<tmpl_var _>"><tmpl_var _></option>
    </tmpl_loop>
  </select>
  <input type="text" name="Keywords">
  <input type="submit">
</form>

<h2>"<tmpl_var name=Request.ItemSearchRequest.Keywords>"の検索結果</h2>
<p><tmpl_var name=Results.TotalResults>件中 <tmpl_var name=Results.CarrentMinNum> から  <tmpl_var name=Results.CarrentMaxNum> までを表示</p>
<p><tmpl_var name=Paging></p>
<table>
  <tmpl_loop name=Item>
  <tr>
    <td>
      <a href="http://www.amazon.co.jp/exec/obidos/ASIN/<TMPL_VAR name=ItemAttributes.ASIN>/tekiomo-22/ref=nosim/"><img src="http://images-jp.amazon.com/images/P/<tmpl_var name=ASIN>.09.TZZZZZZZ.jpg"></a>
    </td>
    <td>
      <h3><a href="http://www.amazon.co.jp/exec/obidos/ASIN/<TMPL_VAR name=ASIN>/tekiomo-22/ref=nosim/"><tmpl_var name=ItemAttributes.Title></a></h3>
    </td>
  </tr>
  </tmpl_loop>
</table>
<p><tmpl_var name=Paging></p>

</body>
</html>