主にプログラミングに関して。Python, .NET Framework(C#), JavaScript, その他いくらか。
記事にあるサンプルやコードは要検証。使用に際しては責任を負いかねます

スポンサーサイト

                
tags:
上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

GAE with PythonでAmazon APIにリクエストを送るサーバを立てる(プロキシサーバ?)

                
tags:
Amazon Product Advertising API(以下Amazon API)を使うには、アソシエイトIDに付随する固有のIDが必要で、このIDは他人に知られてはならないことになっている。なのでこのAPIを使ったサービスをユーザーに提供するなら、IDがユーザーに読まれないようにしておく必要がある。
どうすれば読まれないか、たとえばユーザーからAmazonに直接リクエストを送らずに、プロキシサーバを介せばいいだろう。ユーザーから商品検索に必要な情報(キーワードやEANやら)をプロキシサーバに送り、プロキシサーバでIDなどを付加してAmazon APIが受け付けられる形にしてリクエスト。その返ってきたレスポンスをユーザーに送るという流れ。
というわけで今回はAmazon APIにリクエストを送ってくれるGAEサーバ設置。GAEのwebappフレームワークを使う。GAEを使ったことがなければ、GAEのチュートリアルへ。チュートリアルの4項目、『webappフレームワークの使用』までの内容と最後の立ち上げしか使わない。
ちなみに以前作った所有物管理アプリで似たものを使っている。



今回作るのは、ユーザーからEANを受け取って、Amazon APIにEANによるアイテム検索をリクエストするサーバー。EANは複数個まとめてリクエストできるようにする。Amazon APIでは一度にリクエストを受け付けられるEANは10個までとされているが、もっとまとめてやりたいのでプロキシサーバでは100個まで受け取って、ループで順次処理してくれるようにする。
ページ下部にスクリプトを載せている。アソシエイトIDやアクセスキーを取得して関数内のxxxxxxxxxxxxxxxxxと置き換えてもらえれば使える(はず、HTML表示に際しての落ち度がなければ)。一応テキストファイルでも上げておこう。

1.
まずユーザーからプロキシサーバにどうやってEANを渡そうか。URLに含めてしまえばシンプルで簡単だろうと思ったのでユーザーからのリクエストは以下のように送ってもらうようにする。
"http://***.appspot.com/amazonjp/xxx_xxx_xxx_xxx"
***はGAEにサーバ立ち上げをするとき、サーバ設置者が任意に命名することができる。xxxにはリクエストするEANを入れる。それぞれのEANナンバーは"_"アンダーバーで区切る。
ユーザーからのリクエストの形を決めたので、次はプロキシサーバ側でのリクエストの受け付けである。ユーザーから受け取ったリクエストから、まずEANを抽出したい。URLマッピングを使う。
application = webapp.WSGIApplication([('/amazonjp/(.*)', Jp), ], debug=False)

こうしておくことで、"/amazonjp/"にきたリクエストに対して処理Jp(このスクリプト内ではクラス)を施せる。さらに/amazonjp/に続くURLはクラスを宣言するときに以下のようにgetの後ろに引数をつけることで取得して扱うことができる。URLマッピングというらしい。

class Jp(webapp.RequestHandler):
def get(self, codes):

これでユーザーがURLとして入力した/amazonjp/に続く文字列が変数codesに入る。詳しくはこちらwebappのURLマッピング

2.
次はEANを受け取ったプロキシサーバ上での処理。EANをAmazonに送ってレスポンスを受け取ればいい。EANを使ってAmazonにリクエストを投げるということの詳しい説明は省く。タイムスタンプを付けて、アソシエイトIDを付けて、という風にやるのだが、いくつかのブログで詳しく説明されているし、Amazonも公式のドキュメントを配布している。ページ下部に実際のスクリプトを載せていて、"amazon_req"という関数で処理している。
Amazon APIのリクエストを所定の形式で投げると、レスポンスはXMLで返ってくる。そのXMLをプロキシサーバ上で成形する必要がある。
まずユーザーに不必要な情報を削る。レスポンスにはアソシエイトIDが含まれているので削る→"xmlstr = re.sub('<Argument Name="AWSAccessKeyId" Value="xxxxxxxxxxxxxxxx"/>', "", xmlstr)"。
そしてXMLとしての形式に成形する。今回プロキシサーバで100個までEANを受け取って処理できるようにしている。プロキシサーバが100個のEANを受け取った場合、10個ずつ10回リクエストを投げ、その10回のレスポンスを単純に連結している。この状態ではXMLの標準形式から外れているので、XMLとしての形にしなければならない。
XMLはまずドキュメントの頭にXML宣言<?xml version="1.0" ?>が必要で、その宣言に続く本文は1組の括弧"<hoge>~</hoge>"(hogeは任意)に囲われていなければならない。要は以下のような形ならXML
<?xml version="1.0" ?><hoge>~</hoge>

以下のようになっては駄目、宣言以下は1つの括弧に囲まれていないと駄目。
<?xml version="1.0" ?><hoge>~</hoge><hoge>~</hoge>

というわけで、Amazon APIのレスポンスを連結した状態では、XML宣言がドキュメント内に複数あるのでXML宣言をひとまずすべて消す。
xmlstr = re.sub('<\?xml version="1.0" \?>', "", xmlstr)

あとは改めて文頭にXML宣言を付けて、宣言以下を1つの括弧でくくってしまう。これでXMLドキュメントとしてブラウザも認識してくれるようになる。
return '<?xml version="1.0" ?><hoge>' + xmlstr +'</hoge>'

プロキシサーバ上でのXML成形終了。

3.
AmazonからのレスポンスのかたまりをXMLに成形した。あとはユーザーに返すだけ。細かい説明は割愛。RequestHandlerを継承したクラス内でself.response.out.write()にXMLを渡してやればいい。あとはreturnなりなんなりでそのクラスの処理を終了してやる。これでリクエストをしたユーザーにXMLのレスポンスが返る。

4.
サーバーを立ち上げる。これもとてもシンプルな説明が公式にあるので割愛する。GAEのデプロイ
あとは立ち上げたサーバーに↓のようなリクエストをブラウザなどを使ってなげるだけ。
"http://***.appspot.com/amazonjp/xxx_xxx_xxx_xxx"

amazon_proxy.txt



#!/usr/bin/env python
#-*- coding: utf-8 -*-

import re
import cgi
import urllib
import hashlib, hmac
import base64
import sys
import datetime

from google.appengine.api import users
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
from google.appengine.ext.webapp.util import login_required
from google.appengine.ext import db
from google.appengine.ext.db import Key

def id_filter(str):
ids = re.findall("[0-9]{13}", str)
if not ids:
ids = re.findall("[0-9A-Z]{10}", str)
ids = sorted(set(ids), key=ids.index)
if (len(ids)>100): ids = ids[0:100]

return ids

def amazon_req(ids, country):
idNum=len(ids)
loop = (idNum-1)/10+1
xmlstr=''

key1='AWSAccessKeyId=xxxxxxxxxxxxxxxxxxxxxxxx'
key2='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
associate='AssociateTag=xxxxxxxxxxxxxxxxxxxxxxxx'
sign_head='GET\necs.amazonaws.jp\n/onca/xml\n'
reqhead='http://ecs.amazonaws.jp/onca/xml?'

service='Service=AWSECommerceService'
operation='Operation=ItemLookup'
version='Version=2010-06-01'
response='ResponseGroup=Medium'
searchindex='SearchIndex=All'
if len(ids[0])==13:
idtype='IdType=EAN'
elif len(ids[0])==10:
idtype='IdType=ASIN'
for l in range(loop):
id=','.join(map(str,ids[l*10:l*10+10]))
id_quo=urllib.quote(id)
itemid='ItemId=%s' % id_quo
ymd=datetime.datetime.utcnow().strftime("Timestamp=%Y-%m-%d")
hms=datetime.datetime.utcnow().strftime("T%H:%M:%SZ")
hms=urllib.quote(hms)
timestamp=ymd+hms
if idtype=='IdType=EAN':
request=[timestamp, service, operation, version, response, idtype, itemid, searchindex, associate]
elif idtype=='IdType=ASIN':
request=[timestamp, service, operation, version, response, idtype, itemid, associate]
reqSorted =sorted(request)
reqJoined=key1+'&'+'&'.join(reqSorted)

reqJoined2=sign_head+reqJoined

hmac_digest = hmac.new(key2, reqJoined2, hashlib.sha256).digest()
base64_encoded = base64.b64encode(hmac_digest)
result = urllib.quote(base64_encoded)

reqstr = reqhead + reqJoined + '&Signature=' + result
xmlfile=urllib.urlopen(reqstr)
xmljoin=xmlfile.read()
xmlstr += xmlJoin

xmlstr = re.sub('\n','',xmlstr)
xmlstr = re.sub('<\?xml version="1.0" \?>', "", xmlstr)
xmlstr = re.sub('<Argument Name="AWSAccessKeyId" Value="xxxxxxxxxxxxxxxx"/>', "", xmlstr)

return '<?xml version="1.0" ?><hoge>' + xmlstr +'</hoge>'

class Jp(webapp.RequestHandler):
def get(self, codes):
ids = id_filter(codes)

xml = amazon_req(ids, 'jp')

##self.response.headers['Content-Type']='text/xml'
self.response.out.write(xml)
return 0

def main():
application = webapp.WSGIApplication(
[('/amazonjp/(.*)', Jp),
],
debug=False)
run_wsgi_app(application)

if __name__ == "__main__":
main()

111012_0.jpg
***********************
111012_1.jpg

            

コメントの投稿

非公開コメント

プロフィール

hMatoba

Author:hMatoba
Github

最新記事
リンク
作ったものなど
月別アーカイブ
カテゴリ
タグリスト

検索フォーム
Amazon
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。