タグ「Python コード サンプル」が付けられているもの

この間、SAXパーサーでAmazon Product(以下略)を解析したので、更なるXML柔軟利用を目指してXPathを使ってみる。

Amazon APIへのRESTリクエスト作成はこちら。また、最新のソースはこのあたり

ElementTree 参考URL

XPath の構文

注意点
  • xmlns属性があると、タグを指定するときに、'//tag_name' などと書くことができずに、'//{http://xmlns_attribute_value}tag_name'のようにいちいち記述する必要がある。
  • 要するに、elm.find('./ItemAttributes/Title') と書くことはできず、elm.find('./{http://webservices.amazon.com/AWSECommerceService/2005-10-05}ItemAttributes/{http://webservices.amazon.com/AWSECommerceService/2005-10-05}Title のようにする必要がある')
#!Python2.6
# -*- coding: utf-8 -*-

import amazon_ecs  # 自作モジュールをインポート
import urllib2
from xml.etree import ElementTree

# xmlns を付加したタグ名を返す
def qn(tag):
    return ElementTree.QName(ns, tag).text

ns = r'http://webservices.amazon.com/AWSECommerceService/2005-10-05'

# xmlnsが指定されている場合、タグを{xmlnsの値}タグ名 といちいち書く必要があるため、そのように展開したものを保持しておく
# ./{http://webservices.amazon.com/AWSECommerceService/2005-10-05}ItemAttributes/{http://webservices.amazon.com/AWSECommerceService/2005-10-05}Title
q_items  = './/{0}'.format(qn('Item'))
q_title  = './{0}/{1}'.format(qn('ItemAttributes'), qn('Title'))
q_author = './{0}/{1}'.format(qn('ItemAttributes'), qn('Author'))
q_asin   = './{0}'.format(qn('ASIN'))
q_url    = './{0}'.format(qn('DetailPageURL'))
q_img    = './{0}/{1}'.format(qn('SmallImage'), qn('URL'))

# Amazon Product Advertise API リクエスト URLを生成
operation = amazon_ecs.ItemSearch()
operation.keywords('手塚 治虫')
operation.search_index('Books')
operation.response_group('Large')
request = operation.request()
print 'REQUEST : {0}'.format(request)

# ElementTreeを生成
root = ElementTree.parse(urllib2.urlopen(request)).getroot()
# XPathを使用してElementTreeを解析
items =  root.findall(q_items)
for item in items:
    print '-' * 100
    print 'TITLE : {0}'.format(item.find(q_title).text)
    print 'AUTHOR : {0}'.format(item.find(q_author).text)
    print 'ASIN : {0}'.format(item.find(q_asin).text)
    print 'URL : {0}'.format(item.find(q_url).text)
    print 'IMG : {0}'.format(item.find(q_img).text)

PyDevで走らせた結果

py_xpath01

Pythonの思想

初めてのPython 第3版

コマンドラインから import this すると、Pythonの思想の深淵に触れることができる。

>>> import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

Amazon アソシエイト Web サービスの名称が、「Product Advertising API」に変わり、電子署名を 8月15日までにリクエストに含める必要がある旨のメールが先日届いた。

ama_py01

GAE/Python 上に、Amazon 情報取得処理を置くことを念頭に、リクエストのサンプルを作成してみる。

まず、参照すべきページ。

以下、簡単に Product Advertising API を利用するための概要。

1.Amazon Web Services アカウントの取得

  • Amazon Product Advertising API (長い・・・) を利用して、Amazonの情報を利用するためには、すべてのリクエストに、のAWSアクセス識別情報を含める必要がある。
  • アクセス識別情報は、アルファベットと数字からなりリクエスト送信者を一意に識別する。
  • AWS Access Key ID とAWS Secret Access Keyとがあり、アカウントの取得により、参照可能となる
  • アカウントの取得はhttp://aws.amazon.com/から行い、取得すると、Your Accountメニュー - Account Identifies から参照可能

ama_py02

Your Accountメニュー - Account Identifies から・・・

ama_py03

AWS Access Key ID とAWS Secret Access Keyと参照できる

2.アソシエイトになる

  • アソシエイトは自分のWebサイトからAmazonのサイトを参照し販売手数料を得る
  • アソシエイトとなるには、Associate ID が必要
  • Associate ID は登録した地域でのみ有効
  • アソシエイトの登録は、https://affiliate.amazon.co.jp/ から

3.Java で リクエストを処理する

最終的にPythonで、サンプルを作成したが、最初Javaでやろうと思い途中まで確認したので一応手順をメモしておく。

webサービスを呼び出すことによって、Product Advertising API クライアントサイドライブラリスタブが自動生成される。

Eclipse で、AmazonWsStubプロジェクトを作成したとすると、以下の手順

(1) ディレクトリ構成

AmazonWsStub
├─bin
├─src
└─jaxws-custom.xml

(2)jaxws-custom.xml の内容

<jaxws:bindings wsdlLocation="http://ecs.amazonaws.com/AWSECommerceService/AWSECommerceService.wsdl" xmlns:jaxws="http://java.sun.com/xml/ns/jaxws">
  <jaxws:enableWrapperStyle>false</jaxws:enableWrapperStyle>
</jaxws:bindings>

(3) WebService Import を実行するコマンド

wsimport -d ./bin -s ./src  -p com.ECS.client.jax http://ecs.amazonaws.com/AWSECommerceService/AWSECommerceService.wsdl -b jaxws-custom.xml
  ama_py04

プロジェクトディレクトリの様子

ama_py05

Java クラス群が自動作成された

(4) サンプルを作成して動かしてみる。

package info.typea.test.ecs;

import java.util.List;

import com.ECS.client.jax.AWSECommerceService;
import com.ECS.client.jax.AWSECommerceServicePortType;
import com.ECS.client.jax.Item;
import com.ECS.client.jax.ItemAttributes;
import com.ECS.client.jax.ItemSearch;
import com.ECS.client.jax.ItemSearchRequest;
import com.ECS.client.jax.ItemSearchResponse;
import com.ECS.client.jax.Items;

public class ProductAdvertisingAPITest {

public static void main(String[] args) {
ProductAdvertisingAPITest me = new ProductAdvertisingAPITest();
me.itemSearchTest();
}
public void itemSearchTest() {
// サービスのセット
AWSECommerceService service = new AWSECommerceService();
// サービスのポートをセット
AWSECommerceServicePortType port = service.getAWSECommerceServicePort();
// オペレーションオブジェクトの取得
ItemSearchRequest request = new ItemSearchRequest();
// オブジェクトの設定
request.setSearchIndex("Books");
request.setKeywords("dog");
ItemSearch itemSearch = new ItemSearch();
itemSearch.setAWSAccessKeyId("*************"); // Dummy
itemSearch.getRequest().add(request);
// Webサービスを呼び出し応答を格納
ItemSearchResponse response = port.itemSearch(itemSearch);

// 結果を表示
System.out.println("-- print item search result --");
List<Items> itemslist = response.getItems();
for (Items items : itemslist) {
List<Item> itemlist = items.getItem();
for (Item item : itemlist) {
ItemAttributes attr = item.getItemAttributes();
System.out.format(
"%s %s %s %s\n",
item.getASIN(),
attr.getTitle(),
attr.getAuthor().get(0),
item.getDetailPageURL()
);
}
}
}
}

ama_py06

結果が帰ってきた・・・ のはいいけど、どうも、ぱっと見、今回問題としている Secret Access Key が現時点では指定できなさそう?SOAP?これに署名計算を埋め込む???まぁPythonを覚えたいので、Pythonでやってみよう。

 

4. Pythonでリクエストを作成する

ということで、Pythonでやってみる。今回は署名付きRESTリクエストを作成するところまで。

RESTリクエストの詳細は、http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/index.html?AnatomyOfaRESTRequest.html このあたりを参照。

基本的には、署名の作成方法に従った、RESTリクエストを作成する。

このサイトを大いに参考にさせていただいた感謝。

(1) 基本イメージ

Amazon Product Advertising API は ざっくり Operation として、行いたい操作(たとえばItemSearch)をパラメータ(たとえばKeywords)とともに指定したリクエストをRESTやSOAPで送信し、結果をXMLで取得する。

俺流amazonの作り方―Amazon Webサービス最新活用テクニック

まずは、署名をつけたリクエストが正しく受理されることが目標なので、Oparation 基底クラスにそのあたりの基本機能を実装し、個別の操作はその派生クラス(または汎用クラス)としてつど作っていけばよいかなと。

(2) 手順

署名の作成方法に、署名つきRESTリクエストの作成手順が載っているが、これだけでは実際わかりにくい。

Product Advertising API Signed Requests Helper というツールが提供されており(Web もしくは ダウンロード)、これを確認すると、上記署名の作成手順の途中経過が、順を追って確認できるようになっている。

AWS Access Key ID とAWS Secret Access Keyと未署名のRESTリクエストを入力して、Display Signed URL ボタンを押すと・・・ama_py07

Unsigned URL : 未署名のRESTリクエストが・・・

Name-Value Paires : KEY名と値のペアに分割され、TimeStampがなければ付与

ama_py08

Sorted Pairs : ソートされ・・・

String-To-Sign : 電子署名用の文字列が生成され・・・

Signed URL : 署名されたURLが生成される

ので、この結果を確認しながら、同じ出力が得られるようにしていけばよい。

(3) Python サンプルソース

ItemSearch API を利用するサンプルを作成してみた。

基本的には、素直に、実装したつもり。

URL エンコード RFC 2396

タイムスタンプ : ISO 8601

あたりを参考に。

#!Python2.6
# -*- coding: utf-8 -*-
import urllib
from datetime import datetime
import hashlib, hmac
import base64

#@see: http://d.hatena.ne.jp/niiyan/20090509/1241884365
class Operation:
__safe_chars = '-._~'
__ecs_url = 'http://ecs.amazonaws.jp/onca/xml'
__service = 'AWSECommerceService'
__access_key_id = '***********' # Dummy
__associate_tag = '*******' # Dummy
__secret_access_key = '**********************' # Dummy
__http_verb = 'GET'
__value_of_host_header_in_lowercase = '/onca/xml'
__http_request_uri = 'ecs.amazonaws.jp'
def __init__(self):

self.__param_map = {}


def operation_name(self):
return ''

def request(self):
self.set_parameter('Service', self.__service)
self.set_parameter('AWSAccessKeyId', self.__access_key_id)
self.set_parameter('AssociateTag', self.__associate_tag)
self.set_parameter('Operation', self.operation_name())
self.set_parameter('Timestamp', datetime.utcnow().isoformat() + 'Z')
#Name-Value Pairs
n_v_pair_list = []
for key in self.__param_map.keys():
n_v_pair_list.append(urllib.quote(key, self.__safe_chars) + '=' + urllib.quote(self.__param_map[key], self.__safe_chars))

#Sorted Pairs
n_v_pair_list.sort()
request_parm_str = '&'.join(n_v_pair_list)
#String-To-Sign
sing_part_list = [self.__http_verb, self.__http_request_uri, self.__value_of_host_header_in_lowercase, request_parm_str]
str_to_sign = '\n'.join(sing_part_list)
hmac_digest = hmac.new(self.__secret_access_key, str_to_sign, hashlib.sha256).digest()
base64_encoded = base64.b64encode(hmac_digest)
signature = urllib.quote(base64_encoded);
return '%s?%s&Signature=%s' % (self.__ecs_url, request_parm_str, signature)
def set_parameter(self, key, value='', remove=False):
if remove:
del self.__param_map[key]
else:
self.__param_map[key] = value
def response_group(self, value='', remove=False):
self.set_parameter('ResponseGroup', value='Medium', remove=False)

class ItemSearch(Operation):
'''
@see: http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/index.html?ItemSearch.html
'''
def operation_name(self):
return 'ItemSearch'
def keywords(self, value, remove=False):
self.set_parameter('Keywords', value, remove)
def search_index(self, value='Books', remove=False):
self.set_parameter('SearchIndex', value, remove)

request = ItemSearch()
request.keywords('手塚 治虫')
request.search_index('Books')
request.response_group('Reviews')
print request.request()

結果作成された URL

http://ecs.amazonaws.jp/onca/xml?AWSAccessKeyId=1498TGK1YPN1JATPXXG2&AssociateTag=typea09-22&Keywords=%E6%89%8B%E5%A1%9A%E3%80%80%E6%B2%BB%E8%99%AB&Operation=ItemSearch&ResponseGroup=Medium&SearchIndex=Books&Service=AWSECommerceService&Timestamp=2009-07-29T15%3A57%3A09.593000Z&Signature=QwyP%2BmIsLJSoZZVEvO0ps5zPHfXZ1vevY/4sRiR7pAo%3D

ama_py09

正しく認証されて、結果が返ってきた。

また一歩野望に近づいた!