⇒ PythonでBASEのAPIを叩いて商品データを取得してみる -その1-
前回はリフレッシュトークンを返す関数を作成しましたので、今回は
■「リフレッシュトークンからアクセストークンを取得する」処理と、
■「アクセストークンを使ってBASEのAPIから商品データを取得する」
という2つの処理を作成します。
リフレッシュトークンからアクセストークンを取得する
アクセストークン(リフレッシュトークンver)の取得方法についてはこちら↓を参照。
前回作成した「get_refresh_token()」で送信したパラメータとほぼ同じですね。
認可コードから アクセストークンを取得 | リフレッシュトークンから アクセストークンを取得 |
|||
---|---|---|---|---|
Name | Description | Name | Description | |
grant_type | authorization_code | ⇒ | grant_type | refresh_token |
client_id | クライアントID | client_id | クライアントID | |
client_secret | クライアントシークレット | client_secret | クライアントシークレット | |
code | 認可コード | ⇒ | refresh_token | リフレッシュトークン |
redirect_uri | 登録したコールバックURL | redirect_uri | 登録したコールバックURL |
grant_typeがリフレッシュトークン用の値に変わり、認可コードを設定していた場所がそのままリフレッシュトークンに置き換わっただけです。
という事でコードは次の様になります。
import requests import json from datetime import datetime POST_TOKEN_URL = "https://api.thebase.in/1/oauth/token" GRANT_TYPE_REFRESHTOKEN = 'refresh_token' # リフレッシュトークンの有効期限 TOKEN_LIFETIME = 29 ''' ----------------------------------------------------------------- リフレッシュトークンを使用してアクセスコードを取得する ----------------------------------------------------------------- ''' def get_accesstoken(): # 設定ファイルから過去に取得したリフレッシュトークンを含む、 # 認証用データ一式の取得 ini_data = get_ini_data() refresh_token = ini_data[IniData.TOKEN.value] # リフレッシュトークンが無い(初回実行時)または # リフレッシュトークンの失効日が近い場合は、 # アクセスコードからリフレッシュトークンの再取得を行う if refresh_token == "": refresh_token = get_refresh_token(ini_data) else: last_gettime = datetime.strptime(ini_data[IniData.DATE.value], '%Y/%m/%d %H:%M:%S') diff_day = (datetime.now() - last_gettime).days if diff_day > TOKEN_LIFETIME: refresh_token = get_refresh_token(ini_data) # refreshTokenが空白の場合は処理キャンセルと判断し、終了 if refresh_token == "": return param = { 'grant_type' : GRANT_TYPE_REFRESHTOKEN, # refresh_token 'client_id' : ini_data[IniData.ID.value], # クライアントID 'client_secret' : ini_data[IniData.SEC.value], # クライアントシークレット 'refresh_token' : refresh_token, # リフレッシュトークン 'redirect_uri' : ini_data[IniData.URI.value], # 登録したコールバックURL } response = requests.post(POST_TOKEN_URL, params=param) token_data = json.loads(response.text) return token_data['access_token']
では軽く解説。
まず冒頭にいきなり未作成の関数「get_ini_data()」が出てきていますが、こちらは設定を保存しているテキストファイルからデータを読み込む処理と思って下さい。読み込んだデータは辞書型で返しますので、ini_data[“キー”]の形式で値を取り出しています。
こちらの中身の処理は特に解説はしませんが、値の直書きに置き換えても問題無いので好きな様に変えて下さい。
※その場合、「ini_data[IniData.****.value]」となっている箇所は該当する値に置き換える必要があります
続いてリフレッシュトークンの取得日と現在日付との日数差を比較し、期限切れ間近であれば再取得を行っています。
前回までのコードと組み合わせる事で、初回起動時とトークンの有効期限切れの場合はトークンの取得を。
2度目以降の起動ではローカルに保存した設定ファイルからトークンの値を参照して、アクセストークンの取得を行う処理が出来ました。
アクセストークンを使ってBASEのAPIから商品データを取得する
ではいよいよ本題です。
取得したアクセストークンを使ってBASEのAPIを叩き、ついでに取得したデータをCSVで保存する所までを実装します。
商品情報の一覧を取得に関する手順の詳細についてはこちらを参照↓
パラメータの一覧はこちら。
Name | Description |
---|---|
order | 並び替え項目。list_order、created、modifiedのいずれか (任意 デフォルト: list_order) |
sort | 並び順。asc か desc のいずれか (任意 デフォルト: asc) |
limit | リミット (任意 デフォルト: 20, MAX: 100) |
offset | オフセット (任意 デフォルト: 0) |
max_image_no | 画像番号 1~20 (任意 デフォルト: 5) |
image_size | 画像サイズ。origin、76、146、300、500、640、sp_480、sp_640からカンマ区切りで複数指定 (任意 デフォルト: origin) |
パラメータは全て任意設定のため、パラメータ無しでもAPIは叩けるみたいですね。
一応今回は「limit」をMAX(100)、「max_image_no」を10にそれぞれ設定してリクエストしてみました。
そのコードがこちら。
import os import requests APIURL_GET_ITEMLIST = "https://api.thebase.in/1/items" # requests成功の場合の数値 REQUEST_SUCCESS = 200 ''' ----------------------------------------------------------------- 商品データリストの一覧を取得し、CSV形式で出力する ----------------------------------------------------------------- ''' def get_itemlist(): param = { "limit": "100", # データ取得数 "max_image_no": "10", # 取得画像欄 } access_token = get_accesstoken() header = { "Authorization": "Bearer " + access_token, } response = requests.get(APIURL_GET_ITEMLIST, params=param, headers=header) # 取得に成功していれば出力 if response.status_code == REQUEST_SUCCESS: filePath = os.path.join(os.path.dirname(os.path.abspath(__file__)), "base_itemlist.csv") # データをCSVとして保存(後述) output_csv(response.text, filePath)
では簡単に解説します。
冒頭のパラメータ作成部分は今までのトークンを取得する方法と同じです。
そして新たに「header」を作成していますが、アクセストークンはこのヘッダー部分にセットする必要があります。書き方は上記コードの通りです。
パラメータ、ヘッダ共に作成できたら、通常通り「requests.get」を使えば送信完了です。引数「params」と同様に「headers」がありますので、headerの値はそちらに渡します。
レスポンスデータをCSVに書き込む
ではAPI処理が成功したと仮定して、そのデータをCSVに書き込む部分を解説します。
以下はレスポンスの例。
{ "items":[ { "item_id":1234, "title":"Tシャツ", "detail":"とってもオシャレなTシャツです。", "price":3900, "proper_price":null, "stock":10, "visible":1, "list_order":1, "identifier":"abcd-1234", "img1_origin":"https://baseec2.s3.amazonaws.com/images/item/origin/45fc036c772c8469fa40396b2ef0fb9b.jpg", "img2_origin":"https://baseec2.s3.amazonaws.com/images/item/origin/2a4de4965fa23b7b89944199713a827e.jpg", "img3_origin":null, "img4_origin":null, "img5_origin":null, "modified":1414731171, "variations":[ { "variation_id":11, "variation":"黒色", "variation_stock":6, "variation_identifier":"abcd-1234-b" }, { "variation_id":12, "variation":"白色", "variation_stock":4, "variation_identifier":"abcd-1234-w" } ] }, { "item_id":12345, "title":"ロングTシャツ", "price":4900, "proper_price":null, "stock":20, "visible":1, "list_order":2, "identifier":null, "img1_origin":"https://baseec2.s3.amazonaws.com/images/item/origin/45fc036c772c8469fa40396b2ef0fb9b.jpg", "img2_origin":"https://baseec2.s3.amazonaws.com/images/item/origin/2a4de4965fa23b7b89944199713a827e.jpg", "img3_origin":null, "img4_origin":null, "img5_origin":null, "modified":1414731172, "variations":[ { "variation_id":21, "variation":"Mサイズ", "variation_stock":12, "variation_identifier":null }, { "variation_id":22, "variation":"Lサイズ", "variation_stock":8, "variation_identifier":null } ] } ] }
データはJSONと呼ばれるテキストデータ形式で返って来ます。
こちらを以下の処理を使い、
import json import csv import copy ''' ----------------------------------------------------------------- 引数として受け取ったjsonデータを解析し、CSVファイルとして保存する ----------------------------------------------------------------- ''' def output_csv(response, file_path): item_list = json.loads(response) with open(file_path, "w", newline="") as f: option_list = [] writer = csv.writer(f) for item in item_list["items"]: option_list.clear() # 末尾にオプションデータが存在する場合は抽出する item_list = list(item.values()) if type(item_list[len(item_list) - 1]) == list: option_list = item_list.pop(len(item_list) - 1) # オプション無しの場合はそのまま行出力 if len(option_list) == 0: writer.writerow(item_list) continue # オプション指定がある場合は、 # オプション個数分の行を出力する for option in option_list: tmp = copy.copy(item_list) tmp.extend(list(option.values())) writer.writerow(tmp)
以下の画像の様なCSV形式で保存します。
※1行目の項目ヘッダーは分かりやすい様に後付けしただけなので、実際には出力されません。
サイズやカラーバリエーションの無い商品についてはそのまま。(2行目)
有る商品については、U列以降にバリエーションデータを出力しつつ、それ以外の項目(A~T列)には同じものを出力するといった形式にしています。(3~5行目)
BASEの一括登録用CSVフォーマットにあわせるためにこの様な形にしただけなので、出力方法の処理は自由に変更して問題ありません。
この出力方法のデメリットとしては、商品が大量&バリエーションも大量に設定されている場合に行数が膨れあがってしまい、CSVのサイズが非常に重くなってしまうという点です。
かといってバリエーションデータを分割せずにそのまま出力してしまうと、今度は一覧データを編集して”更新”したりする場合に修正が面倒になってしまうと思うんですよね。
一覧だけ見たい場合と更新用で処理を分けるのも有りかもしれない。
処理の内容についてですが、バリエーションデータを抽出する部分は最初のif処理で行っています。
そして抽出したバリエーションデータの個数分、行を出力する処理は最後のfor分で行っています。
バリエーションデータを行分割せずに出力したい場合は、とりあえずこの二箇所の処理を消すだけで出力できると思います。
感想
初めてPythonで実用的な処理を組んでみました。
開発は最初こそ手間取ったものの、ライブラリが充実しているおかげで面倒な処理もサクサク実装でき、使いこなせれば開発スピードも上がるなーと思いました。「これ使いたい!」と思ったライブラリがすぐにインストールできるのも良いですね。
暫くは慣れる為にも色々使っていこうと思います。
後、BASEAPI関連で一つ。
気付いた人もいると思いますが、パラメータに指定できる「limit」は最大100となっています。じゃあ200商品分のデータを取得したい場合はどうするの?
・・・って思うんですが実際どうするんだろう。
「offset」ってパラメータがありますが、例えばこれに50を指定して「limit」に100を指定すると、50個目~149個目が取得できるってイメージで良いのかな・・・?
その辺は近いうちに検証してみます。
そもそもBASEのマニュアル自体に説明が少ないんだよなぁ・・・。