GetLowestPricedOffersForASINをpythonで実行する話

#本記事について
MWSのAPIについてpythonで遊んでいるが、そのうちの「GetLowestPricedOffersForASIN」というAPIがなぜか全然動かなくて詰んだ話。とりあえず解決したのでもし同じ悩みを抱えているエンジニアの助けになれば・・・

##とりあえず解決したコード
まず動いたコードを載せよう。

```python:MWS_API.py
### 初期モジュール宣言
import os, sys, time
import base64
import datetime
import hashlib
import hmac
import urllib.parse
import requests
import six
import xmltodict
import json
from time import gmtime, strftime
import pandas as pd
import pandas as np
from pathlib import Path

###★アクセス用key===個人のやつを入れてね===
AMAZON_CREDENTIAL = {
    'SELLER_ID': '★',
    'ACCESS_KEY_ID': '★',
    'ACCESS_SECRET': '★',
}
DOMAIN = 'mws.amazonservices.jp'
ENDPOINT = '/Products/2011-10-01'

### 関数宣言
## 使用するAPIの宣言と必須パラメータを与える関数
def GetLowestPricedOffersForASIN(ItemCondition='New'):
    print('Funcition : GetLowestPricedOffersForASIN', flush=True)

    ## GetLowestPricedOffersForASINの必須パラメータを作成
    q = {
        'Action' : 'GetLowestPricedOffersForASIN',
        'MarketplaceId' : 'A1VC38T7YXB528',#日本
        'ASIN' : 'B000xxxx',#調べたいASINを入れる
        'ItemCondition' : ItemCondition,
    }
    return q

##POST関数
def PostMWS(q):
    timestamp = datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ')

    ##POSTするのに必要なパラメータの作成
    data = {       
        'AWSAccessKeyId' : AMAZON_CREDENTIAL['ACCESS_KEY_ID'],
        'SellerId' : AMAZON_CREDENTIAL['SELLER_ID'],
        'SignatureVersion' : '2',
        'Timestamp' : timestamp,
        'Version' : '2011-10-01',
        'SignatureMethod' : 'HmacSHA256',
    }
    print(type(data['Timestamp']))

    data.update(q)
    query_string = urllib.parse.urlencode(sorted(data.items()))
    
    canonical = "{}\n{}\n{}\n{}".format(
        'POST', DOMAIN, ENDPOINT, query_string
        )
    h = hmac.new(
        six.b(AMAZON_CREDENTIAL['ACCESS_SECRET']),
        six.b(canonical), hashlib.sha256
        )
    signature = urllib.parse.quote(base64.b64encode(h.digest()), safe='')
    url = 'https://{}{}'.format(
        DOMAIN, ENDPOINT)
    data = '{}&Signature={}'.format(
        query_string, signature)
    headers = {'Content-Type': 'application/x-www-form-urlencoded'}

    res = requests.post(url,data,headers=headers)
  
    return res
    
if __name__ == '__main__':
    q = GetLowestPricedOffersForASIN()
    res = PostMWS(q)
    response_xml = res.text
    print(response_xml)
```

##なにが起きたのか？
これから下の文は愚痴のようなものなので、動いた！サンキュー！って方は無視して大丈夫です。
解決するまでは以下のようなコードで実行していた。

```python:MWS_API.py
### 初期モジュール宣言
import os, sys, time
import base64
import datetime
import hashlib
import hmac
import urllib.parse
import requests
import six
import xmltodict
import json
from time import gmtime, strftime
import pandas as pd
import pandas as np
from pathlib import Path

###★アクセス用key===個人のやつを入れてね===
AMAZON_CREDENTIAL = {
    'SELLER_ID': '★',
    'ACCESS_KEY_ID': '★',
    'ACCESS_SECRET': '★',
}
DOMAIN = 'mws.amazonservices.jp'
ENDPOINT = '/Products/2011-10-01'

### 関数宣言
## ASINに応じた結果を返す関数
def GetLowestPricedOffersForASIN(ItemCondition='New'):
    print('Funcition : GetLowestPricedOffersForASIN', flush=True)

    ## GetLowestPricedOffersForASINの必須パラメータを作成
    q = {
        'MarketplaceId' : 'A1VC38T7YXB528',#日本
        'ASIN' : 'B000xxxx',#調べたいASINを入れる
        'ItemCondition' : ItemCondition,
    }
    return q

##POST関数
def PostMWS(q):
    timestamp = datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ')

    ##POSTするのに必要なパラメータの作成
    data = {       
        'AWSAccessKeyId' : AMAZON_CREDENTIAL['ACCESS_KEY_ID'],
        'Action' : 'GetLowestPricedOffersForASIN',
        'SellerId' : AMAZON_CREDENTIAL['SELLER_ID'],
        'SignatureVersion' : '2',
        'Timestamp' : timestamp,
        'Version' : '2011-10-01',
        'SignatureMethod' : 'HmacSHA256',
    }
    print(type(data['Timestamp']))

    data.update(q)
    query_string = urllib.parse.urlencode(sorted(data.items()))
    
    canonical = "{}\n{}\n{}\n{}".format(
        'POST', DOMAIN, ENDPOINT, query_string
        )
    h = hmac.new(
        six.b(AMAZON_CREDENTIAL['ACCESS_SECRET']),
        six.b(canonical), hashlib.sha256
        )
    signature = urllib.parse.quote(base64.b64encode(h.digest()), safe='')

##★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
    url = 'https://{}{}?{}&Signature={}'.format(
        DOMAIN, ENDPOINT,query_string, signature)
  
    res = requests.post(url)
##★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★  
    return res
    
if __name__ == '__main__':
    q = GetLowestPricedOffersForASIN()
    res = PostMWS(q)
    response_xml = res.text
    print(response_xml)
```

★で囲っている部分が異なるところである。
例えばGoogleなどでmwsのAPIの送り方を調べると上記のような形で書かれている。事実私もそれを参考に作成し、**ほかのAPIではうまくいっていた。**
そう
####ほかのAPIではうまくいっていたのである。

ちなみに上のコードを実行すると400エラーが返ってくる。これは必須パラメータが足りないことを示しているようだ。

##解決への道
調べていたらこんな記事を見つけた
https://teratail.com/questions/127617

クエストリング・・・？リクエストボディ・・・？なんのこっちゃ・・・

調べてみたらダメなやり方はPOSTで指定しているのはURLのみ、確かにURL上はデータがあるように見えるがこれだとリクエストボディ上は空になるらしい。
**なるほど、つまりこうか！！**

```python:MWS_API.py
##★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
    url = 'https://{}{}'.format(
        DOMAIN, ENDPOINT)
    data = '{}&Signature={}'.format(
        query_string, signature)
    res = requests.post(url,data=data)
##★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★  
```

**・・・が、ダメ！！！**

なんでだ・・・さらに調べる。するとこんな記事が
https://sellercentral.amazon.com/forums/t/getlowestpricedoffersforasin-service-call-with-no-xml-response/252922

この方はheaderをつけることで解決したらしい。
**なるほど、つまりこうか！！**

```python:MWS_API.py
##★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
    url = 'https://{}{}'.format(
        DOMAIN, ENDPOINT)
    data = '{}&Signature={}'.format(
        query_string, signature)
    headers = {'Content-Type': 'application/x-www-form-urlencoded'}

    res = requests.post(url,data=data,headers=headers)
##★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★  
```

**・・・が、ダメ！！！！！！なんでやああああああああああああ！！！！！**

このとき結果を見ていて気付いたが、どうにもデータがうまく入っていないように見える・・・？まさか？

```python:MWS_API.py
##★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
    url = 'https://{}{}'.format(
        DOMAIN, ENDPOINT)
    data = '{}&Signature={}'.format(
        query_string, signature)
    headers = {'Content-Type': 'application/x-www-form-urlencoded'}

    res = requests.post(url,data,headers=headers)
##★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★  
```

**・・・う、動いた・・？**

##課題事項
とりあえず動いて助かったのだが、疑問が残る。
・ほかのAPIはクエストリングだけで動くのに、なぜこいつはリクエストボディやヘッダーをつけなければ動かないのか？
（もしかしたらボディやヘッダーをつけるのが正規なのかもしれない・・・）

・requests.postのdata引数について。クイックスタートやほかの投稿をみるとdata引数に送るデータを渡していたのでなにも考えずdata=dataとしていたがダメっぽい。。。よく読むとdata引数に渡すのは辞書と書いてある、要調査。

2019/10/21時点。