Amazon Product Advertising APIを使って、「Flex × Python」からAmazonの商品データの検索するアプリを作ってみた
こんにちわ!兄貴とTwitter上でマジレス兄弟喧嘩をしたRyoAbeです。
今回は何を作ったかと言いますと表題にある通り、
AmazonのAPI(Product Advertising API)を使った商品データの検索出来るアプリです。
(ほんとただそれだけですw)
かるーくAPI触ってどんな感じか探るだけだったんですが、
意外に手こずっちゃって、おかげでけっこう勉強になったんで
Blogにまとめがてら載せることにしました!
◎そもそもProduct Advertising APIってなんぞやって話から
Product Advertising API は、Amazon の商品情報や関連コンテンツをプログラムを通してアクセスできるサービスを提供することで、Web 開発者の皆様が、ご自分の Web サイトでAmazon の商品を紹介することによる紹介料の獲得を可能とします。
そもそもは、ブログとかのアフィリエイトとかに使うことが目的っぽい。
WordPressのプラグインのAmazonLinkとかもこのAPIを使ってるんだって。
まぁー、勉強ついでとはいえ今回の僕のAPIの使い方ちょっと間違ってますw
◎まずはアカウントの作成
まー上のリンク先(Product Advertising API)にも書いてあるんですが、アカウントを作らなきゃAPIは使えないんですわ。
アカウント作成が完了すると、"AWS アクセスキー ID"*1ってのを貰えて、それを使ってAPIにアクセスするわけです。
アカウントの作成手順については、下記のリンクを参考にして下さい
Product Advertising API アカウント作成 ヘルプ
◎アカウントも作成できたし、作り始めるか
今回使う言語はPythonです。
作り始めるって言ってもPythonのモジュールにPyAWSってのがあるんで、
大してプログラムは組まないんですが。。(そのつもりだった。。)
- まずはPyAWSのホームページからダウンロード
- インストールから、使い方までは下記のサイトを参考にした
◎よーし、PyAWS使ってみよー
対話モードでとりあえず使ってみる。
(このときは、のん気なもんだった。。)
$ python # まずはモジュールのインポート >>> from pyaws import ecs # アカウントを作成後にもらったキーをセット >>> ecs.setLicenseKey('自分のアクセスキー') # アクセス先は日本なので、setLocaleにjpをセット >>> ecs.setLocale('jp') # いざ、検索!! 試しに"Python"って調べてみる >>> books = ecs.ItemSearch('Python', SearchIndex='Books', ResponseGroup='Images') Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/Library/Python/2.6/site-packages/pyaws/ecs.py", line 339, in ItemSearch return pagedIterator(XMLItemSearch, argv, "ItemPage", 'Items', plugins) File "/Library/Python/2.6/site-packages/pyaws/ecs.py", line 216, in __init__ dom = self.__search(** self.__arguments) File "/Library/Python/2.6/site-packages/pyaws/ecs.py", line 348, in XMLItemSearch return query(buildRequest(argv)) File "/Library/Python/2.6/site-packages/pyaws/ecs.py", line 174, in query e = buildException(errors) File "/Library/Python/2.6/site-packages/pyaws/ecs.py", line 159, in buildException e = globals()[ class_name ](msg) KeyError: u'ingParameter'
うげぇーーーー、エラー発生。。
APIから返却値(XML)にErrorが返ってきてるため、
PyAWS内のqueryメソッドでエラーが起きてしまっている様子。。
なんでErrorで返ってくるんだぁーーー!?!?!?!?
早速、調べてみた!!
↓
↓
↓
↓
↓
◎なにやら、去年(2009年08月15日)APIの仕様が変わったらしい
前までは"Access Key ID"のみでAPIの使用は出来たんだが、認証方法が変わったらしい。
名称変更にともない、Product Advertising API にリクエストを送信いただく都度、認証のための電子署名を含めていただくことが必要になります。この変更は、2009年5月11日より3ヶ月の間の移行期間の後、2009年8月15日には、Product Advertising API へ送信されるリクエストは全て認証されることとなり、認証されない場合、リクエストは処理されなくなります。Product Advertising API へのリクエストに署名認証を含めるための簡単な方法については、こちらの開発者向けガイドをご覧ください。
Amazon アソシエイト Web サービスの名称変更および署名認証についてのお知らせ より
PyAMFのリリースを見てみると、2007/04/08で終わってる。。
そりゃ対応してないわ。。
どうしよっかな。。。
↓
↓
↓
↓
↓
◎じゃあ、自分で作っちゃえ。
対応してないなら、自分で作っちゃえ。
PyAWSを直接修正するってのも手ではあったが、一から作った方が勉強になるしねb
# だったら初めから一から作れって話だし、
# 探せば新しい仕様に対応したモジュールがあるような気もするけど無視!
■プログラム概要
■処理フローの説明
---------------------------- | Product Advertising API | ---------------------------- ↑②③ ↓④ ========================================= | [Python]サーバサイド(main.py, aws.py) | ========================================= ↑① ↓⑤ ====================================== | [Flex]クライアントサイド(main.swf) | ======================================
■実際に返ってくるXMLはこんな感じ
- keyword
- ResponseGroup
- ItemAttributes
- SearchIndex
- Books
で検索した場合のXML↓↓
<ItemSearchResponse> <OperationRequest> <HTTPHeaders> <Header Name="UserAgent" Value="Python-urllib/1.17 AppEngine-Google; (+http://code.google.com/appengine)"/> </HTTPHeaders> <RequestId>11111111-2222-3333-4444-555555555555</RequestId> <Arguments> <Argument Name="Operation" Value="ItemSearch"/> <Argument Name="Service" Value="AWSECommerceService"/> <Argument Name="Signature" Value="1111111111111111111111111111"/> <Argument Name="Version" Value="2009-01-06"/> <Argument Name="Keywords" Value="python"/> <Argument Name="AWSAccessKeyId" Value="AAAAAAAAAAAAAAAAAAAAAAA"/> <Argument Name="Timestamp" Value="2010-04-16T05:17:19Z"/> <Argument Name="ResponseGroup" Value="ItemAttributes"/> <Argument Name="SearchIndex" Value="Books"/> </Arguments> <RequestProcessingTime>0.1920510000000000</RequestProcessingTime> </OperationRequest> <Items> <Request> <IsValid>True</IsValid> <ItemSearchRequest> <Condition>New</Condition> <DeliveryMethod>Ship</DeliveryMethod> <Keywords>python</Keywords> <MerchantId>Amazon</MerchantId> <ResponseGroup>ItemAttributes</ResponseGroup> <ReviewSort>-SubmissionDate</ReviewSort> <SearchIndex>Books</SearchIndex> </ItemSearchRequest> </Request> <TotalResults>62</TotalResults> <TotalPages>7</TotalPages> <Item> <ASIN>4797353953</ASIN> <DetailPageURL>http://www.amazon.co.jp/%E3%81%BF%E3%82%93%E3%81%AA%E3%81%AEPython-%E6%94%B9%E8%A8%82%E7%89%88-%E6%9F%B4%E7%94%B0-%E6%B7%B3/dp/4797353953%3FSubscriptionId%3DAKIAJD5ODXRC4SZWODZA%26tag%3Dws%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3D4797353953 </DetailPageURL> <ItemLinks> <ItemLink> <Description>Add To Wishlist</Description> <URL>http://www.amazon.co.jp/gp/registry/wishlist/add-item.html%3Fasin.0%3D4797353953%26SubscriptionId%3DAKIAJD5ODXRC4SZWODZA%26tag%3Dws%26linkCode%3Dxm2%26camp%3D2025%26creative%3D5143%26creativeASIN%3D4797353953 </URL> </ItemLink> <ItemLink> <Description>Tell A Friend</Description> <URL>http://www.amazon.co.jp/gp/pdp/taf/4797353953%3FSubscriptionId%3DAKIAJD5ODXRC4SZWODZA%26tag%3Dws%26linkCode%3Dxm2%26camp%3D2025%26creative%3D5143%26creativeASIN%3D4797353953 </URL> </ItemLink> <ItemLink> <Description>All Customer Reviews</Description> <URL>http://www.amazon.co.jp/review/product/4797353953%3FSubscriptionId%3DAKIAJD5ODXRC4SZWODZA%26tag%3Dws%26linkCode%3Dxm2%26camp%3D2025%26creative%3D5143%26creativeASIN%3D4797353953 </URL> </ItemLink> <ItemLink> <Description>All Offers</Description> <URL>http://www.amazon.co.jp/gp/offer-listing/4797353953%3FSubscriptionId%3DAKIAJD5ODXRC4SZWODZA%26tag%3Dws%26linkCode%3Dxm2%26camp%3D2025%26creative%3D5143%26creativeASIN%3D4797353953 </URL> </ItemLink> </ItemLinks> <ItemAttributes> <Author>柴田 淳</Author> <Binding>単行本</Binding> <EAN>9784797353952</EAN> <Edition>改訂版</Edition> <ISBN>4797353953</ISBN> <Label>ソフトバンククリエイティブ</Label> <ListPrice> <Amount>2940</Amount> <CurrencyCode>JPY</CurrencyCode> <FormattedPrice>¥ 2,940</FormattedPrice> </ListPrice> <Manufacturer>ソフトバンククリエイティブ</Manufacturer> <NumberOfPages>484</NumberOfPages> <PackageDimensions> <Height Units="hundredths-inches">110</Height> <Length Units="hundredths-inches">827</Length> <Weight Units="hundredths-pounds">150</Weight> <Width Units="hundredths-inches">591</Width> </PackageDimensions> <ProductGroup>Book</ProductGroup> <ProductTypeName>ABIS_BOOK</ProductTypeName> <PublicationDate>2009-04-11</PublicationDate> <Publisher>ソフトバンククリエイティブ</Publisher> <Studio>ソフトバンククリエイティブ</Studio> <Title>みんなのPython 改訂版</Title> </ItemAttributes> </Item> : : </Items> </ItemSearchResponse>
詳しくはこちら
■ソース載っけます
- main.swf(クライアントサイド)
- main.pyへAMFで検索文字を渡す
- main.pyからXMLを受け取る
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" xmlns:local="*"> <mx:Script> <![CDATA[ import mx.utils.ObjectUtil; import mx.messaging.AbstractConsumer; import mx.collections.ArrayCollection; import mx.collections.XMLListCollection; import mx.controls.Alert; import flash.net.*; [Bindable] public var xml_data:XML; [Bindable] public var xElementCollection:XMLListCollection; // 検索時のプルダウン用 [Bindable] public var search_indexes:ArrayCollection = new ArrayCollection( [ {label:"全て", data:"All"}, {label:"和書", data:"Books"}, {label:"洋書", data:"ForeignBooks"}, {label:"DVD", data:"DVD"}, {label:"曲名", data:"MusicTracks"}, {label:"ソフトウェア", data:"Software"}, {label:"ゲーム", data:"VideoGames"} ]); // データ送信 private function doRequest():void{ // レスポンダーを作成 var responder:Responder = new Responder(onSuccess, onFault); // コネクションの作成 var connection:NetConnection = new NetConnection(); // コネクト先のPythonのURL connection.connect("http://searchamazondatarabe.appspot.com/"); connection.objectEncoding = ObjectEncoding.AMF3; // サーバサイドに渡す値 var data:Object = new Object(); data['search_word'] = nameText.text; data['search_index'] = myComboBox.selectedItem.data; // サーバサイド(main.py)のitemSearchメソッドを呼ぶ connection.call("Service.itemSearch", responder, data); } // データ取得成功 private function onSuccess(e:*):void{ // 受け取ったXMLをバインドする xml_data = new XML(e.toString()); xElementCollection = new XMLListCollection(xml_data.Items.Item.ItemAttributes); } // データ取得失敗 private function onFault(e:*):void{ Alert.show("通信失敗"); } // 実ページ遷移用 add 2010/4/26 private function onItemClicked(e:*):void{ var i:int = e.rowIndex; var urls:String = xml_data.Items.Item[i].DetailPageURL; var url:URLRequest = new URLRequest(urls); navigateToURL(url, "_blank"); } ]]> </mx:Script> <mx:TextInput x="10" y="43" id="nameText" enter="doRequest()" /> <mx:Button label="検索" id="idTest" click="doRequest()" x="286" y="43"/> <mx:Label x="10" y="10" text="Amazonでデータを検索" color="#FFFFFF" fontSize="16"/> <mx:DataGrid right="10" left="10" top="84" bottom="30" itemClick="onItemClicked(event)" id="myDataGrid" dataProvider="{xElementCollection}"> <mx:columns > <mx:DataGridColumn headerText="タイトル" dataField="Title"/> <mx:DataGridColumn headerText="著者名" dataField="Author"/> <mx:DataGridColumn headerText="商品カテゴリー" dataField="ProductGroup"/> <mx:DataGridColumn headerText="Binding" dataField="Binding"/> <mx:DataGridColumn headerText="ページ数" dataField="NumberOfPages"/> <mx:DataGridColumn headerText="発行年月日" dataField="PublicationDate"/> <mx:DataGridColumn headerText="発売元" dataField="Publisher"/> </mx:columns> </mx:DataGrid> <mx:ComboBox x="178" y="43" width="100" dataProvider="{search_indexes}" id="myComboBox"/> </mx:Application>
- main.py(サーバサイド)
# -*- coding: utf-8 -*- import logging import wsgiref.handlers from pyamf.remoting.gateway.wsgi import WSGIGateway from aws import AWS def search(data): aws = AWS(select_access_key_id='自分のシークレットアクセスキー', aws_access_key_id='自分のアクセスキー') result = aws.doItemSearch(keyword=data['search_word'], search_index=data['search_index']) return result def main(): services = { 'Service.itemSearch': search, } gateway = WSGIGateway(services, logger=logging, debug=True) wsgiref.handlers.CGIHandler().run(gateway) if __name__ == '__main__': main()
- aws.py(こいつがProduct Advertising APIとの連携をする)
# -*- coding: utf-8 -*- from time import strftime, gmtime # GMTIME取得用 import urllib # URLエンコード import hmac, hashlib # HMAC-SHA256の算出用 import base64 # Base64エンコード用 import types # 型を調べるときに使用 AWS_DOMAIN = "ecs.amazonaws.jp" class AWS: def __init__(self, **params): # パラメータセット self.__setParams(params) def __setParams(self, params): """ パラメータセット """ self.setSelectAccessKey( params.get("select_access_key_id") ) self.setAWSAccessKeyId( params.get("aws_access_key_id") ) self.setResponseGroup( params.get("response_group") or "ItemAttributes") self.setTimeStamp( params.get("timestamp") or strftime("%Y-%m-%dT%H:%M:%SZ", gmtime()) ) self.setOperation( params.get("operation") or "ItemSearch") self.setSearchIndex( params.get("search_index") or "Books" ) self.setService( params.get("service") or "AWSECommerceService") self.setVersion( params.get("version") or "2009-01-06") self.setKeyWord( params.get("keyword") ) def doItemSearch(self, **params): """ 検索 """ # パラメータセット if params.get("keyword") : self.setKeyWord( params.get("keyword") ) if params.get("operation") : self.setOperation( params.get("operation") ) if params.get("search_index") : self.setSearchIndex( params.get("search_index") ) # パラメータをURLエンコード enc_params = self.__doParamEncode() # Signature作成 signature = self.__createSignature(enc_params) # リクエストURLを生成 request_url = 'http://' + AWS_DOMAIN + "/onca/xml?" + enc_params + "&Signature=" + signature # urlopen result = urllib.urlopen(request_url) return result.read() def __doParamEncode(self): """ URLのパラメータをURLエンコード """ params = { "Service" : self.getService(), "AWSAccessKeyId" : self.getAWSAccessKeyId(), "Operation" : self.getOperation(), "SearchIndex" : self.getSearchIndex(), "ResponseGroup" : self.getResponseGroup(), "Version" : self.getVersion(), "Timestamp" : self.getTimeStamp(), "Keywords" : self.getKeyWord() } # ソート sorted(params.iteritems()) # URLエンコードした文字列を格納するタプル enc_param_list = [] # パラメータをURLエンコード for key in sorted(params.keys()): if type(params[key]) == types.UnicodeType: params[key] = params[key].encode("UTF-8") enc_param = "%s=%s" % (key, urllib.quote(params[key]) ) enc_param_list.append( enc_param ) return "&".join(enc_param_list) def __createSignature(self, enc_params): """ Signatureの作成 """ # 署名用文字列の作成 message = "\n".join(["GET", AWS_DOMAIN, "/onca/xml", enc_params]) # Secret Access KeyをHMAC-SHA256形式でハッシュ化 hmac_digest = hmac.new(self.getSelectAccessKey(), message, hashlib.sha256).digest() # Base64エンコード base64_encoded = base64.b64encode(hmac_digest) # URLエンコード(2回目) signature = urllib.quote(base64_encoded) return signature # KeyWord def setKeyWord(self, keyword): self.__keyword = keyword def getKeyWord(self): return self.__keyword # TimeStamp def setTimeStamp(self, timestamp): self.__timestamp = timestamp def getTimeStamp(self): return self.__timestamp # Service def setService(self, service): self.__service = service def getService(self): return self.__service # AWSAccessKeyId def setAWSAccessKeyId(self, aws_access_key_id): self.__aws_access_key_id = aws_access_key_id def getAWSAccessKeyId(self): return self.__aws_access_key_id # SelectAccessKey def setSelectAccessKey(self, select_access_key_id): self.__select_access_key_id = select_access_key_id def getSelectAccessKey(self): return self.__select_access_key_id # Operation def setOperation(self, operation): self.__operation = operation def getOperation(self): return self.__operation # SearchIndex def setSearchIndex(self, search_index): self.__search_index = search_index def getSearchIndex(self): return self.__search_index # ResponseGroup def setResponseGroup(self, response_group): self.__response_group = response_group def getResponseGroup(self): return self.__response_group # Version def setVersion(self, version): self.__version = version def getVersion(self): return self.__version
◎完成したものがこれ
GAEに載っけました。
http://searchamazondatarabe.appspot.com/main.swf
◎残タスク
- XMLの返却値でAutherタグ(著者名)が複数ある場合に、まんまAutherタグが表示されちゃう
◎まだまだやりたいこと
- デフォルトで10件しばりになってるんで、それを外す
- 画像とかも表示したりしたい
- 実ページへの遷移のクリックイベントの追加
◎作ってみて思ったこと
- Product Advertising APIをちゃんとした使い方しよw
- 今回は本当の用途と全然違う使い方をしてしまったw アフィリエイトでもやってみっか。
- ってかPythonって楽しいね
- なんかもっと綺麗な書き方(そもそも作りがおかしいとか)もあると思うけど、書いてて楽しかった
参考サイト
- Amazon E-Commerce Service @DoboWiki
- PythonでAmazon Product Advertising APIを使う @人工知能に関する断想録
- AWSからProduct Advertising APIへ @[Z]ZAPA ブロ〜グ2.0
今日はこんなとこで!
※追記 ・実ページへの遷移のクリックイベントの追加 add 2010/4/26
*1:AWS アクセスキー IDとは、AWSの開発者アカウントに関連付けられた識別子です。AWSで提供されているすべてのWebサービスにアクセスするときに、AWS アクセスキー IDが必要になります。
*2:クライアント(Flex)側でAPIに直接アクセスするって手もあったけどね。Python触りたかったしw
*4:詳細はこちら(認証(Timestamp及びSignature)@AjaxTower)、もしくは、こっち(Product Advertising API)
*5:詳しくはこちら(Flex の Binding 具体例と内部事情の覗き見CommentsAdd Star@くって煮ブログ)を参照