これまでのブログ記事(以下)では、AmazonのMWSの後継となるSelling Partner API(以下、SP-API)へ移行するための準備をしてきました。
- Amazon MWS から SP-API への移行 [きっかけ編]
- Amazon MWS から SP-API への移行 [準備編]
- Amazon MWS から SP-API への移行 [Postmanによるテスト接続編]
今回は「PythonでSP-APIをたたく編」ということで、実際にPythonプログラムを作成してSP-APIをたたいてみて、使い方を理解していきたいと思います。
この記事でできるようになること
- PythonプログラムでSP-APIのたたき方、使い方が理解できるようになる
- MWSからSP-APIへの移行ができるようになる
なお、今後SP-APIを利用する際にあまり困らなくなるので、ちょっと元気が出ます。
必要となる知識
- Pythonプログラミングの知識
- JSONの知識
- サーバーの基本的な知識
- GETやPOSTといったHTTPプロトコルの仕組みの知識
- 公式リファレンス(英語)に記載のSP-APIの仕様を読み解く力(Google翻訳はみんなの友達!)
公式リファレンスはこちら
私の実装内容は、基本的には公式リファレンスに全て書いてあります。(が、理解することが難しい書き方になっています。)なので、特別なことは何もしておらず、どなたにでもオープンな情報になっています。
SP-API 公式リファレンス
SellingPartnerApiDeveloperGuide(日本語)
署名プロセス 公式リファレンス
AWS > ドキュメント > リファレンスガイド > 署名バージョン4の署名プロセス
署名プロセスが難解すぎるっ!!!
こちらを参考に、プログラムを作ればいいだけですってことなのかと思いますが、、、
Pythonを動かす環境
今回のブログでは開発環境の準備方法については割愛しますが、私のPythonプログラム開発環境は以下です。
- サーバー環境
- 契約 : Conoha
- OS : Cent OS 7
- プログラミング環境
- OS : Windows10
- 統合開発環境 : Visual Studio Code
- 言語 : Python
- Pythonバージョン
- Python 3.6.8
やばい、Python3.6系のセキュリティ修正パッチの提供終了予定が2021年12月... バージョン上げないといけないですね。
アクセスするSP-APIのAPIを決める
前回のブログ「Postmanによるテスト接続編」では、SP-APIの「getCatalogItem」というAPIをPostmanでたたいてResponseを取得しました。今回は前回同様のAPIをPythonでたたきます。PostmanでのAPIアクセス方法とPythonでのAPIアクセス方法を比較することで、PythonによるAPIプログラミングの感覚がつかめると思います。
Selling Partner API for Catalog Items
Overvies : The Selling Partner API for Catalog Items provides programmatic access to information about items in the Amazon catalog.
https://github.com/amzn/selling-partner-api-docs/blob/4b53b4e38bcb5745266f53cdf65feed2b56e0ddc/references/catalog-items-api/catalogItems_2020-12-01.md
概要 : Selling Partner API 「Catalog Items」は、プログラムによるAmazonカタログ内のアイテムに関する情報へのアクセスを提供します。
Pythonサンプルプログラムとレスポンス
早速ですが、以下にPythonのサンプルプログラムを記載します。
アクセストークン取得
前回の読み解きを再掲します。こちらをアクセストークン取得のPythonプログラムに設定します。
LWA(Login with Amazon)認証サーバーに対して、必要なパラメータをセットしてRequestを送信します。
- LWA認証サーバURL
- https://api.amazon.com/auth/o2/token
- HTTP メソッド
- POST
- POSTリクエストのBodyパラメータ
- grant_type:「refresh_token」という文字列を設定
- refresh_token:「リフレッシュトークン」の値を設定
- client_id:「LWA 認証情報 クライアントID」を設定
- client_secret:「LWA 認証情報 クライアント機密情報」を設定
まずはリフレッシュトークンからアクセストークンを取得する関数です。必要となるモジュールは適宜 import してください。
### =====================================================================================
### Access Token 取得関数
### =====================================================================================
### 初期モジュール宣言
import os,sys,time
import hashlib
import hmac
import urllib.parse
import requests
import json
from time import gmtime, strftime
from datetime import datetime
def SPAPI_Get_Token(SPAPI_LWA_Client_ID, SPAPI_LWA_Client_PW, SPAPI_Refresh_Token, SPAPI_Access_Token_Endpoint):
print("Func : SPAPI_Get_Token")
### === 引数の説明 ================================================
### SPAPI_LWA_Client_ID : LWA 認証情報 クライアントID
### SPAPI_LWA_Client_PW : LWA 認証情報 クライアント機密情報
### SPAPI_Refresh_Token : リフレッシュトークン
### SPAPI_Access_Token_Endpoint : LWA認証サーバURL
### ==============================================================
### 認証情報の作成
auth = (SPAPI_LWA_Client_ID, SPAPI_LWA_Client_PW)
### POSTパラメータ作成
params = {
"grant_type":"refresh_token",
"refresh_token":SPAPI_Refresh_Token
}
### POSTリクエスト処理の実行
SPAPI_Response = requests.post(url=SPAPI_Access_Token_Endpoint, auth=auth, data=params)
### レスポンスをDict型へデコード
SPAPI_Response_dict = SPAPI_Response.json()
### POST後のHTTPレスポンスコードとレスポンス内容の表示(確認用)
print(SPAPI_Response, flush=True)
print(json.dumps(SPAPI_Response_dict, indent=2, ensure_ascii=False), flush=True)
### 値の取得
resp_access_token = SPAPI_Response_dict['access_token']
resp_refresh_token = SPAPI_Response_dict['refresh_token']
resp_token_type = SPAPI_Response_dict['token_type']
resp_expires_in = SPAPI_Response_dict['expires_in']
return resp_access_token, resp_refresh_token, resp_token_type, resp_expires_in
こちらを関数として配置し、メイン関数から呼び出し実行すると、以下のような応答が返ってきます。無事、アクセストークンが取得できています。
Func : SPAPI_Get_Token
<Response [200]>
{
"access_token": "Atza|~~~省略~~~",
"refresh_token": "Atzr|~~~省略~~~",
"token_type": "bearer",
"expires_in": 3600
}
セキュリティ的に危ない部分は「~~~省略~~~」と置換しています。
SP-API の API アクセス
こちらも、前回の読み解きを再掲します。一部は修正/追記してます。SP-API公式リファレンスから以下の読み解きができるようになれば、SP-APIは自由自在に扱えるかと思います。
- HTTPメソッド
- GET
- SP-API エンドポイント
- https://sellingpartnerapi-fe.amazon.com
- Path
- /catalog/2020-12-01/items/{asin}
※{asin} には Amazon の商品に付与されているASINコードを入れる - asin:SP-APIから情報を取得したいASINコードを入力する
※今回は「B07WXL5YPW」(Nintendo Switch)を入力
- /catalog/2020-12-01/items/{asin}
- GETリクエストのパラメータ(クエリ部分)
- marketplaceIds:「A1VC38T7YXB528」を設定
- includedData : 「identifiers」「images」「productTypes」「salesRanks」「summaries」「variations」を設定
※こちらのリファレンス(includedData)を参照
- ヘッダー情報作成のためのパラメータ
- user-agent:アクセスした際に識別可能なUserAgent情報を記載する
※例:Zats-SPAPI-App/Ver1.0 (Tool=Postman;Platform=Windows10) - x-amz-access-token:リフレッシュトークンから取得した「アクセストークン」を設定
- user-agent:アクセスした際に識別可能なUserAgent情報を記載する
- 認証情報(Authorization)作成のためのパラメータ
- AccessKey:「AWS IAMユーザー アクセスキーID」を設定
- SecretKey:「AWS IAMユーザー シークレットアクセスキー」を設定
- AWS Region:「us-west-2」を設定
- Service Name:「execute-api」を設定
取得したアクセストークンを用いて、かつ上記の読み解き結果を参考にしながら「getCatalogItem」APIをたたきます。こちらも、 必要となるモジュールは適宜 import してください。
### =====================================================================================
### SP-API「getCatalogItem」にアクセスし、指定したASINコードの商品情報を取得する関数
### =====================================================================================
### 初期モジュール宣言
import os,sys,time
import hashlib
import hmac
import urllib.parse
import requests
import json
from time import gmtime, strftime
from datetime import datetime
def SPAPI_GetCatalogItemsForASIN(Asin_Code, SPAPI_Access_Token, SPAPI_IAM_User_Access_Key, SPAPI_IAM_User_Secret_Key, SPAPI_Method, SPAPI_Service, SPAPI_Domain, SPAPI_MarketplaceId, SPAPI_Region, SPAPI_Endpoint, SPAPI_SignatureMethod, SPAPI_UserAgent):
print('Function : SPAPI_GetCatalogItemsForASIN')
### === 引数の説明 ============================
### Asin_Code : リクエストするASINコード
### SPAPI_Access_Token : リフレッシュトークンから取得したアクセストークン
### SPAPI_IAM_User_Access_Key : IAMユーザのアクセスキー
### SPAPI_IAM_User_Secret_Key : IAMユーザのシークレットキー
### SPAPI_Method : HTTPアクセスプロトコル(GET)
### SPAPI_Service : IAMユーザのアクセス権限設定(execute-api)
### SPAPI_Domain : SP-APIエンドポイントのURLのドメイン部分(sellingpartnerapi-fe.amazon.com)
### SPAPI_MarketplaceId : APIリクエストするAmazonマーケットプレイスのID
### SPAPI_Region : APIリクエストするエンドポイントのAWS Region
### SPAPI_Endpoint : SP-APIエンドポイントのURL(https://を含むやつ)
### SPAPI_SignatureMethod : 署名方式(AWS4-HMAC-SHA256)
### SPAPI_UserAgent : APIリクエストする際のユーザエージェント情報(アクセス識別用)
### ==========================================
### ====================
### SP-API Path の定義
### ====================
# ### Version : 0 ★2022年3月31日で使えなくなる★
# ## パス設定
# SPAPI_API_Path = '/catalog/v0/items/{}'.format(Asin_Code)
# ## リクエストパラメータ設定
# request_parameters = 'MarketplaceId=' + str(SPAPI_MarketplaceId)
### Version : 2020-12-01
## パス設定
SPAPI_API_Path = '/catalog/2020-12-01/items/{}'.format(Asin_Code)
## リクエストパラメータ設定(パラメータはアルファベット順にすること! & URLエンコードしないと、リクエストエラーになる場合あり!カンマ区切り文字入れるならURLエンコード必須)
request_parameters_unencode = {
'includedData' : 'identifiers,images,productTypes,salesRanks,summaries,variations',
'marketplaceIds' : str(SPAPI_MarketplaceId)
}
request_parameters = urllib.parse.urlencode(request_parameters_unencode)
### Python による署名キーの取得関数
## 参考:http://docs.aws.amazon.com/general/latest/gr/signature-v4-examples.html#signature-v4-examples-python
def sign(key, msg):
return hmac.new(key, msg.encode('utf-8'), hashlib.sha256).digest()
def getSignatureKey(key, dateStamp, regionName, serviceName):
kDate = sign(('AWS4' + key).encode('utf-8'), dateStamp)
kRegion = sign(kDate, regionName)
kService = sign(kRegion, serviceName)
kSigning = sign(kService, 'aws4_request')
return kSigning
### ヘッダー情報と問合せ資格(credential)情報のための時刻情報作成
t = datetime.utcnow()
amzdate = t.strftime('%Y%m%dT%H%M%SZ')
datestamp = t.strftime('%Y%m%d') # Date w/o time, used in credential scope
### =================================================================================
### Canonical Request の作成
### 参考:http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
### =================================================================================
## URI設定
canonical_uri = SPAPI_API_Path
## 正規リクエストパラメータ設定
canonical_querystring = request_parameters
## 正規リクエストヘッダリストの作成
canonical_headers = 'host:' + SPAPI_Domain + '\n' + 'user-agent:' + SPAPI_UserAgent + '\n' + 'x-amz-access-token:' + SPAPI_Access_Token + '\n' + 'x-amz-date:' + amzdate + '\n'
## 正規リクエストヘッダリストの項目情報の作成(hostとx-amz-dateも入れてる)
signed_headers = 'host;user-agent;x-amz-access-token;x-amz-date'
## ペイロードハッシュ(リクエスト本文コンテンツのハッシュ)の作成
## ※GETリクエストの場合、ペイロードは空の文字列("")になる。
payload_hash = hashlib.sha256(('').encode('utf-8')).hexdigest()
## 正規リクエストの作成
canonical_request = SPAPI_Method + '\n' + canonical_uri + '\n' + canonical_querystring + '\n' + canonical_headers + '\n' + signed_headers + '\n' + payload_hash
## 問合せ資格情報を作成し、署名方式、ハッシュ化された正規リクエスト情報を結合した情報を作成する
credential_scope = datestamp + '/' + SPAPI_Region + '/' + SPAPI_Service + '/' + 'aws4_request'
string_to_sign = SPAPI_SignatureMethod + '\n' + amzdate + '\n' + credential_scope + '\n' + hashlib.sha256(canonical_request.encode('utf-8')).hexdigest()
## 定義した関数を用いて署名鍵を作成
signing_key = getSignatureKey(SPAPI_IAM_User_Secret_Key, datestamp, SPAPI_Region, SPAPI_Service)
## 署名鍵で、上記で作成した「string_to_sign」に署名
signature = hmac.new(signing_key, (string_to_sign).encode('utf-8'), hashlib.sha256).hexdigest()
## Authorizationヘッダの作成
authorization_header = SPAPI_SignatureMethod + ' ' + 'Credential=' + SPAPI_IAM_User_Access_Key + '/' + credential_scope + ', ' + 'SignedHeaders=' + signed_headers + ', ' + 'Signature=' + signature
## API問合せ用ヘッダ情報の作成
headers = {'user-agent':SPAPI_UserAgent, 'x-amz-access-token':SPAPI_Access_Token, 'x-amz-date':amzdate, 'Authorization':authorization_header}
## APIリクエストURLの作成
request_url = SPAPI_Endpoint + canonical_uri + '?' + request_parameters
### ====================
### SP-APIリクエスト
### ====================
print('=== Request ===')
print('Request URL = ' + request_url)
api_response = requests.get(request_url, headers=headers)
return api_response
表示するための簡単な構文は以下です。書いてなかった...
貴重なコメントいただいたので追記しました。 ありがとうございます。(2022.04.15)
## レスポンスをdict形式に変換
response_dict = json.loads(api_response.text)
## 見やすく表示する加工
response_json = json.dumps(response_dict, indent=4)
## 表示
print('=== Response detail - SPAPI_GetCatalogItemsForASIN ===')
print('Response Status : ' + str(api_response.status_code))
print('Response headers :\r\n' + str(api_response.headers))
print('Response json :\r\n' + str(response_json))
こちらも関数として配置し、アクセストークン取得関数と組み合わせてメイン関数から呼び出し実行することで、以下のような応答が返ってきます。無事、SP-APIの「 getCatalogItem 」から情報取得ができるようになるかと思います。
=== Function : SPAPI_GetCatalogItemsForASIN
=== Request ===
Request URL = https://sellingpartnerapi-fe.amazon.com/catalog/2020-12-01/items/B07WXL5YPW?includedData=identifiers%2Cimages%2CproductTypes%2CsalesRanks%2Csummaries%2Cvariations&marketplaceIds=A1VC38T7YXB528
=== Response 詳細 ===
Response Status : 200
Response headers :
{'Date': 'Wed, 08 Sep 2021 ~~~省略~~~ GMT', 'Content-Type': 'application/json', 'Content-Length': '1070', 'Connection': 'keep-alive', 'x-amzn-RequestId': '~~~省略~~~', 'x-amzn-RateLimit-Limit': '5.0', 'x-amz-apigw-id': '~~~省略~~~', 'X-Amzn-Trace-Id': '~~~省略~~~'}
=== Response 結果内容 ===
{
"asin": "B07WXL5YPW",
"identifiers": [
{
"marketplaceId": "A1VC38T7YXB528",
"identifiers": [
{
"identifier": "4902370542912",
"identifierType": "EAN"
}
]
}
],
"images": [
{
"marketplaceId": "A1VC38T7YXB528",
"images": [
{
"variant": "MAIN",
"link": "https://~~~省略~~~.jpg",
"height": 324,
"width": 500
}
]
}
],
"productTypes": [
{
"marketplaceId": "A1VC38T7YXB528",
"productType": "ABIS_VIDEO_GAMES"
}
],
"ranks": [
{
"marketplaceId": "A1VC38T7YXB528",
"ranks": [
{
"title": "\u30b2\u30fc\u30e0",
"link": "http://www.amazon.jp/gp/bestsellers/videogames",
"value": 3
},
{
"title": "Nintendo Switch\u672c\u4f53",
"link": "http://www.amazon.jp/gp/bestsellers/videogames/4731379051",
"value": 1
}
]
}
],
"summaries": [
{
"marketplaceId": "A1VC38T7YXB528",
"brandName": "\u4efb\u5929\u5802",
"browseNode": "4731379051",
"colorName": "\u30cd\u30aa\u30f3\u30d6\u30eb\u30fc/\u30cd\u30aa\u30f3\u30ec\u30c3\u30c9",
"itemName": "Nintendo Switch \u672c\u4f53 (\u30cb\u30f3\u30c6\u30f3\u30c9\u30fc\u30b9\u30a4\u30c3\u30c1) Joy-Con(L) \u30cd\u30aa\u30f3\u30d6\u30eb\u30fc/(R) \u30cd\u30aa\u30f3\u30ec\u30c3\u30c9",
"manufacturer": "\u4efb\u5929\u5802",
"sizeName": "\u672c\u4f53\u306e\u307f"
}
],
"variations": [
{
"marketplaceId": "A1VC38T7YXB528",
"asins": [
"B08NDDJJ35"
],
"variationType": "CHILD"
}
]
}
セキュリティ的に危ない部分や不要な部分、迷惑をかけそうな部分は「~~~省略~~~」と置換しています。
文字化け部分は、適宜エンコードすれば正しい表記に戻ります。
これで、無事SP-APIからAPI経由で欲しい情報を取得することができるようになりました。
こんなエラーが出たら
なお、こちらは前回記事からの再掲となりますが、アクセス権限の設定(IAMユーザへの権限ロール設定、Amazon Seller Central での権限ロール設定、その他権限設定まわり、等々)で何れかの設定が誤っていると、下記のエラーが出ます。
Status : 403
{
"errors": [
{
"message": "Access to requested resource is denied.",
"code": "Unauthorized",
"details": "The access token you provided has expired."
}
]
}
何度も書いちゃいますが、このエラーの厄介な点はどこが間違っているのかをエラーメッセージ内容から容易に特定できない点にあります。このエラーが出てしまった場合は、権限ロール設定の見直しから始めた方がよいかと思います。 権限ロール設定が間違ってない!という場合は、アクセストークンの有効期限切れ、署名ミス、等が考えられます。
他のエラーについても出る可能性はあります。公式リファレンスのエラーレスポンス内容を参考にデバッグを頑張りましょう。
This table contains HTTP status codes and associated information for error responses.
まとめ
これまでの記事で準備した情報をもとに、本日のページの流れに従うことでPythonによるSP-APIへのアクセス、APIによる情報取得ができるようになったと思います。
他のプログラミング言語でも、SP-APIからの情報取得方法は基本的に同じになると思うので、こちらの記事を参考にSP-APIへアクセスをしていただき、アプリケーション開発の助けになれば嬉しいです。
「Amazon MWS から SP-API への移行」シリーズは今回で終了です。私としては、なかなかの超大作になってしまいました。今後も、頑張っている方々のお役に立てるような記事を書いていければと思っています。
SP-APIでこんなことしてくれないか、こんなWebアプリケーション作ってくれないか、というお仕事は大歓迎ですので、ご連絡お待ちしております。(ちょっとだけアピールです。)
では、今日はここまでです。お疲れさまでした。