エンジニア

2022.08.25

Amazon SP-API をPythonで動かしてみた【実コードあり】(part3 POST編 : feed API使ってみた)

■□■ はじめに ■□■

さて、Amazon SP-API のpart2 の続きになります。
前回のGETの処理は無事に動きましたか?
最初の1回動くまで結構苦戦しませんでしたか? 私はしました(泣)
ではpart3ではPOST編です。

GETと大体の流れは同じなのですがfeedは工程が多い・・・
そんなイメージでした。


ちなみにfeedAPIは要は更新処理を行うAPIで在庫情報だったり、売価情報だったりを
更新できたりします。
なにか更新を行うときには避けては通れないAPIですね。はい。

■□■ 準備 ■□■

part2 でGETまで動いているのであれば、これといった準備は不要です。

■□■ とりあえずコード化するまえに手で取得してみましょう ■□■

っといきたいんですが、feedAPIを使う際はいろいろとステップがあるんです。

(これは公式ドキュメントのユースケースに記載がありますので参照いただければと思います)

  1. 基本更新はtsvにて行います そのtsvファイルの読み込みを行います
  2. /feeds/2021-06-30/documentsにてfeed_document_id,document_urlというものを取得します
  3. 取得したdocument_urlの配下に1.のtsvファイルを配置します
  4. /feeds/2021-06-30/feeds にて 2.のdocument_idを指定して更新指示をだします
  5. feed APIは非同期処理なので更新ステータス待ちをします
  6. 更新ステータスが完了になったら結果urlを返すので結果を取得します

ざっくりいうとこんな流れになります。
本パートのメインは2と4になります。(これがPOST処理です)
全部書くと超絶長くなるので、5,6(これはGET処理)に関しては割愛いたします m(_ _)m

2. /feeds/2021-06-30/documentsの実行

method : POST
url : https://sellingpartnerapi-fe.amazon.com/feeds/2021-06-30/documents
           →日本であればsellingpartnerapi-fe.amazon.com 部分は固定
x-amz-access-token: ★取得したaccess_token
user-agent: ★適当に設定(サンプルでは My Selling Tool/2.0 (Language=Java/1.8.0.221; Platform=Windows/10) )
body : json形式
{
'contentType': 'text/xml; charset=UTF-8'
}

Postmanで実行するとこのような感じになります

[Authorization]

[Headers]

[body]

</>code はこんな感じです

ご覧の通り設定項目が増えました・・・
設定項目のbodyの設定のところがドキュメントからだと読み解きづらく、結構悩んだ箇所でもあります。

■□■ コード化するとこんな感じになります ■□■

ではこれをpythonで書くとこうなります。
※あくまでサンプルは一部の抜粋なので環境にあわせて適時変更お願いいたします。

import sys
import hashlib
import hmac
import datetime
import requests
import json
import time

def main() -> dict:
    method = 'POST'
    canonical_uri = '/feeds/2021-06-30/documents'

    access_token = '★取得したaccess_token'

    request_parameters_dict = {
        'contentType': 'text/xml; charset=UTF-8'
    }

    request_parameters = json.dumps(request_parameters_dict)

    # AWS 認証情報を作成
    headers = make_post_authorization_header(
        method, canonical_uri, request_parameters, access_token)

    # リクエスト発行
    response = apicall_sp_api_post(
        canonical_uri, headers, request_parameters)

    # 実行制限にかからないように待機する
    time.sleep(20)

    return response


def apicall_sp_api_post(canonical_uri, headers, request_parameters):

    # リクエストデータを作成
    endpoint = 'https://sellingpartnerapi-fe.amazon.com' + canonical_uri
    request_url = endpoint + '?' + request_parameters

    # リクエストを送信
    response = requests.post(
        endpoint, data=request_parameters, headers=headers)
    response.raise_for_status()

    result_dict = json.loads(response.text)

    return result_dict



def make_post_authorization_header(method, canonical_uri, request_parameters, access_token):

    # ************* REQUEST VALUES *************
    service = 'execute-api'
    host = 'sellingpartnerapi-fe.amazon.com'
    region = 'us-west-2'
    user_agent = "My Selling Tool/2.0 (Language=Java/1.8.0.221; Platform=Windows/10)"
    content_type = 'application/json'

    access_key = '★AWS のアクセスキーを記載'
    secret_key = '★AWS のシークレットキーを記載'

    # Key derivation functions. See:
    # 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, date_stamp, regionName, serviceName):
        kDate = sign(('AWS4' + key).encode('utf-8'), date_stamp)
        kRegion = sign(kDate, regionName)
        kService = sign(kRegion, serviceName)
        kSigning = sign(kService, 'aws4_request')
        return kSigning

    if access_key is None or secret_key is None:
        raise Exception('No access key is available.')

    t = datetime.datetime.utcnow()
    amz_date = t.strftime('%Y%m%dT%H%M%SZ')
    date_stamp = t.strftime('%Y%m%d')

    # ************* TASK 1: CREATE A CANONICAL REQUEST *************
    # http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html

    canonical_querystring = ''
    canonical_headers = 'content-type:' + content_type + '\n' + \
        'host:' + host + '\n' + 'x-amz-date:' + amz_date + '\n'

    signed_headers = 'content-type;host;x-amz-date'

    payload_hash = hashlib.sha256(
        request_parameters.encode('utf-8')).hexdigest()

    canonical_request = method + '\n' + canonical_uri + '\n' + canonical_querystring + \
        '\n' + canonical_headers + '\n' + signed_headers + '\n' + payload_hash

    # ************* TASK 2: CREATE THE STRING TO SIGN*************
    # Match the algorithm to the hashing algorithm you use, either SHA-1 or
    # SHA-256 (recommended)
    algorithm = 'AWS4-HMAC-SHA256'
    credential_scope = date_stamp + '/' + region + \
        '/' + service + '/' + 'aws4_request'
    string_to_sign = algorithm + '\n' + amz_date + '\n' + credential_scope + \
        '\n' + \
        hashlib.sha256(canonical_request.encode('utf-8')).hexdigest()

    # ************* TASK 3: CALCULATE THE SIGNATURE *************
    # Create the signing key using the function defined above.
    signing_key = getSignatureKey(secret_key, date_stamp, region, service)

    signature = hmac.new(signing_key, (string_to_sign).encode(
        'utf-8'), hashlib.sha256).hexdigest()

    # ************* TASK 4: ADD SIGNING INFORMATION TO THE REQUEST *************
    authorization_header = algorithm + ' ' + 'Credential=' + access_key + '/' + \
        credential_scope + ', ' + 'SignedHeaders=' + \
        signed_headers + ', ' + 'Signature=' + signature

    headers = {'x-amz-access-token': token,
               'user-agent': user_agent,
               'Content-Type': content_type,
               'X-Amz-Date': amz_date,
               'Authorization': authorization_header}

    return headers

これでfeedDocumentIdとurlが取得できます。

3. 取得したdocument_urlの配下に1.のtsvファイルを配置

urlの場所にデータをputするのは単純にこんな感じです。

def put_document_file ():
        content_type = "text/xml; charset=UTF-8"
        url = '★取得したurl'
        filename = '★tsvファイル(私の場合はaws lambdaを使っていたので/tmpとかに一旦配置しました)'

        # ファイルをオープン
        with open('/tmp/' + filename) as f:
            upload_data = f.read()

        # リクエストを発行
        response = requests.put(
            url,
            data=upload_data,
            headers={'Content-Type': content_type}
        )
        response.raise_for_status()

4. /feeds/2021-06-30/feeds にて 2.のdocument_idを指定して更新指示をだします

ここはPostmanでもできるんですが、ながれは2と一緒なのでもうそのままコードで行きます
先程のurlとbodyの値がちょっと変わるだけですね。

def main() -> dict:

    method = 'POST'
    canonical_uri = '/feeds/2021-06-30/feeds'
    access_token = '★取得したアクセストークン'

    feed_document_id = '★取得したfeed_document_id'

    # 並び順が違うとエラーになるので注意
    # おそらくこの順番で書かないとエラーになります
    # https://developer-docs.amazon.com/sp-api/docs/reports-api-v2021-06-30-reference#createreportspecification
    request_parameters_dict = {
        'feedType': 'POST_FLAT_FILE_PRICEANDQUANTITYONLY_UPDATE_DATA',
        'marketplaceIds': ['A1VC38T7YXB528'],
        'inputFeedDocumentId': feed_document_id
    }

    request_parameters = json.dumps(request_parameters_dict)

    # AWS 認証情報を作成
    headers = make_post_authorization_header(
        method, canonical_uri, request_parameters, access_token)

    # リクエスト発行
    response = apicall_sp_api_post(
        canonical_uri, headers, request_parameters)

    # 実行制限にかからないように待機する
    time.sleep(20)

make_post_authorization_headerとapicall_sp_api_postは使いまわしになります。

その後は

  1. GET /feeds/2021-06-30/feeds/{feedId} にて更新ステータスを確認して
  2. GET /feeds/2021-06-30/documents/{feedDocumentId} にて更新レポートがあるurlを取得して更新レポートをGETする。

という流れになります。
ここはGETになるので、part2のモジュールを使うことができます。

以上がAmazon SP-APIのPOST編になります。
一度処理が通るとあとは同じようにできるのですが、はじめの一回はかなりハテナマークが頭をよぎりながらやっていました。

本編でAmazon SP-APIは最後になります。 ブログ見て少しでもお役にたてたら光栄です。

弊社は「ひと」とITをつなぐ をモットーに日々仕事に従事しております。

興味を持っていただけたら是非とも、エントリーフォームにからエントリーして、ハンズラボで一緒に仕事をしましょう!

一覧に戻る