【Python初学者】楽天価格チェッカーを作ってみた ② ― 楽天APIリクエスト(rakuten_api.py)
はじめに🐰
Python初学者のわたしが覚えるために、学んだことを整理し、理解を深めるために記事を書いています。
私が実際にやってみて、悩んだ部分や過程などを残していきます🐥
今回は api/rakuten_api.py の解説です😊
このファイルは「楽天APIにお願いして商品データをもらってくる」役割を担っています🚌
外部のサービスとPythonプログラムをつなぐ API連携 の基本の内容になります!
本日のゴール⚽️
-
requestsライブラリで外部APIにリクエストを送る方法を理解する! - レスポンス(返ってきたデータ)から必要な情報だけを取り出せるようになる!
- エラーハンドリングの書き方を覚える!
APIとは?
API(Application Programming Interface) とは、サービスが外部に公開している「問い合わせ窓口」のようなものです🐣
楽天は「このURLに条件(パラメータ)を送れば、商品データをJSON形式で返しますよ」というAPIを公開しています!
Pythonから requests ライブラリを使ってそのURLにアクセスします🐣
SimpleLogger について
コード内で self.logger.info(...) という記述が出てきます。これはプロジェクト内で SimpleLogger クラスを使ったログ出力です!
print() と似ていますが、ログレベルによる色分け・ファイルへの保存などを一度にこなしてくれます。
実装の詳細はこの記事では省きますが、各クラスの __init__ で次のように準備して使っています😊
完成したコード
import requests
from typing import Optional
import os
import sys
from utils.logger import SimpleLogger
from utils.path_helper import get_env_path
from dotenv import load_dotenv
# .envファイルを読み込んでAPIキーを取得する
env_path = get_env_path()
load_dotenv(dotenv_path=env_path)
class RakutenAPI:
def __init__(self):
self.logger_setup = SimpleLogger()
self.logger = self.logger_setup.get_logger()
# -----------------------
# 1つ目のフロー
# 楽天APIに商品検索をリクエストして、結果を返す
# -----------------------
def search(self, product_name: str, page: int = 1) -> Optional[dict]:
self.logger.info(f"楽天API検索を開始します: {product_name}")
# APIのエンドポイント(URL)
url = "https://openapi.rakuten.co.jp/ichibams/api/IchibaItem/Search/20220601"
# リクエストに渡すパラメータ
params = {
"applicationId": os.getenv("RAKUTEN_API_ID"), # APIキー(.envから取得)
"accessKey": os.getenv("RAKUTEN_ACCESS_KEY"), # APIキー(.envから取得)
"keyword": product_name, # 検索ワード
"format": "json", # 返してほしい形式
"hits": 30, # 1回で最大30件取得
"page": page, # 取得するページ番号
}
try:
# GETリクエストを送信(最大10秒待つ)
response = requests.get(url, params=params, timeout=10)
# HTTPエラー(404, 500など)があれば例外を発生させる
response.raise_for_status()
# JSON形式でレスポンスを取得
rakuten_response = response.json()
self.logger.info("楽天API検索に成功しました")
return rakuten_response
except requests.exceptions.RequestException as e:
# 通信エラー・HTTPエラー・タイムアウトをすべてここで受け取る
self.logger.error(f"通信 or HTTPエラー: {e}")
return None
# -----------------------
# 2つ目のフロー
# APIのレスポンスから必要な情報だけを取り出す
# -----------------------
def format_product_data(self, api_data: dict) -> list[dict]:
product_data_list: list[dict] = []
# "Items" キーにある商品リストを1件ずつ処理する
for raw_item in api_data.get("Items", []):
product = raw_item["Item"]
# 必要な情報だけ辞書にまとめる
product_data = {
"name": product["itemName"],
"price": product["itemPrice"],
"url": product["itemUrl"],
"shop": product["shopName"],
"review_avg": product.get("reviewAverage", 0),
"review_count": product.get("reviewCount", 0),
}
product_data_list.append(product_data)
return product_data_list
コードの詳細解説
① .env ファイルとAPIキーの管理
from dotenv import load_dotenv
env_path = get_env_path()
load_dotenv(dotenv_path=env_path)
APIキーは外部に漏れてはいけない秘密情報です!
コードに直接書くのではなく、.env ファイルに保存しておき、python-dotenv ライブラリで読み込みます。
※ get_env_path() について
.envファイルの場所を取得するための自作関数です。
詳細は第⑦回で解説しますので、ここでは「.envの場所を指定している」とだけ理解していただければOKです😊
# .env ファイルの中身(例)
RAKUTEN_API_ID=あなたのAPIキー
RAKUTEN_ACCESS_KEY=あなたのアクセスキー
💡 処理の流れ
① .envファイルにAPIキーを保存する
② load_dotenv() で.envの内容を環境変数として読み込む
③ os.getenv() で環境変数から値を取得する
👉 この3ステップで、安全にAPIキーを扱うことができます
# コード側では os.getenv() で取り出す
os.getenv("RAKUTEN_API_ID")
これにより、コードをGitHubに公開してもAPIキーは隠したままにできます!
② requestsライブラリでGETリクエストを送る
response = requests.get(url, params=params, timeout=10)
requests.get() は指定したURLにリクエストを送り、レスポンスを返す関数です。
| 引数 | 役割 |
|---|---|
url |
リクエスト先のURL |
params |
URLに付け加えるパラメータ(辞書形式) |
timeout=10 |
10秒以内に返答がなければエラーにする |
params に辞書を渡すと、自動的に URL の後ろに ?keyword=青汁&hits=30&... のような形式で付け加えてくれます。
💡 ポイント①(URLの仕組み)
? や & はURLに検索条件をつけるための記号です。
print(response.url) を使うと、実際にどのようなURLが送られているか確認できます😊
💡 ポイント②(APIのルール)
APIでは、パラメータ名は自由に決めるのではなく、公式ドキュメントで指定された名前を使う必要があります!
今回の applicationId や keyword も、楽天のAPI仕様に従っています👍️
※楽天APIの詳細は、デベロッパー登録後にドキュメントで確認できます🐣
③ raise_for_status()
response.raise_for_status()
HTTPのステータスコードをチェックするメソッドです!
- 200番台:成功 → 何もしない
- 400番台(例:404 Not Found):クライアントエラー → 例外を発生させる
- 500番台(例:500 Internal Server Error):サーバーエラー → 例外を発生させる
このメソッドを使わないと、エラーが起きてもプログラムがそのまま進んでしまうことがあります。
なぜ必要なの?🧐
例えば👇
URLが間違っていて「404エラー」が返ってきた場合・・・
一見「エラー=失敗」と思いがちですが、実はこうなっています👇
- 通信 → 成功(サーバーにはちゃんとリクエストが届いている)
- サーバーの返答 →「そのページは存在しません(404)」というメッセージ
👉 つまり「通信は成功しているけど、中身がエラー」な状態です!
このとき requests.get() は「ちゃんと返事が来たからOK!」と判断してしまうため、エラーでもプログラムがそのまま進んでしまいます😱
④ response.json()
rakuten_response = response.json()
APIのレスポンスはJSON形式(テキスト)で返ってきます!
.json() を呼ぶとPythonの辞書(dict)に変換してくれます。
# 返ってくるデータのイメージ(一部)
{
"Items": [
{
"Item": {
"itemName": "青汁 30包",
"itemPrice": 1980,
"itemUrl": "https://...",
"shopName": "健康ショップ",
... # 他にも多くの項目がありますが省略しています
}
}
]
}
💡 ポイント
今回のデータは「辞書の中にリストがあり、その中にさらに辞書が入っている」構造になっています。
少し複雑に見えますが、「商品が複数あるためリストになっている」と考えると理解しやすいです😊
⑤ エラーハンドリング(try / except)
try:
response = requests.get(url, params=params, timeout=10)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
self.logger.error(f"通信 or HTTPエラー: {e}")
return None
requests.exceptions.RequestException は以下をすべてまとめてキャッチできる例外クラスです。
-
ConnectionError:インターネット接続の問題 -
Timeout:タイムアウト -
HTTPError:raise_for_status()が発生させたエラー
エラーが起きたときは None を返すことで、呼び出し元が「データが取れなかった」と判断できるようにしています。
💡 処理の流れ
① リクエスト送信
② HTTPエラーなら raise_for_status で止まる
③ 問題なければ json() で変換を試みる
④ 通信エラー(接続失敗・タイムアウト)やHTTPエラーが発生した場合は except に入る
⑤ Noneを返す
👉 エラーが発生した場合でもプログラムが止まらないようにするための仕組みです!
細かいエラーの種類をすべて覚える必要はなく、「通信まわりのエラーをまとめて扱える」と理解すればOKです😊
⑥ Optional[dict] の型ヒント
def search(self, product_name: str, page: int = 1) -> Optional[dict]:
Optional[dict] は「dict か None のどちらかを返します」という意味です。
from typing import Optional でインポートして使います。エラー時に None を返す関数には Optional をつけておくと、読んだ人が「あ、Noneが返ることがあるんだ」とわかります🐣
関数の戻り値の型を明示することで、コードを読む人にとって理解しやすくなります!
⑦ dict.get() でキーを安全に取り出す
product.get("reviewAverage", 0)
👉 この処理の動き
キーがある → その値を返す
キーがない → 0を返す(デフォルト値)
このように .get("キー", デフォルト値) を使うことで、
キーが存在しない場合でもエラーにならず、安全に値を取得できます!
辞書["キー"] はキーが存在しないと KeyError が発生しますが、
.get("キー", デフォルト値) を使うとキーがなくても安全に値を取り出せます😊
reviewAverage や reviewCount は、楽天APIのレスポンスに含まれる項目です!
ただし、すべての商品にレビュー情報があるとは限らないため、
存在しない場合もあります。
そのため、ここでは .get() を使い、値がない場合は 0 を返すようにしています🐣
👉 一方で、product["itemName"] のような書き方は、キーが必ず存在する場合に使います!
存在しない場合はエラー(KeyError)になるため、必ずある項目に対して使うのが基本です😊
⑧ リストへの追加:append()
product_data_list.append(product_data)
append() はリストの末尾に要素を追加するメソッドです!
ループのなかで1件ずつ辞書を作り、リストに追加していくのはとてもよく使うパターンです🐣
まとめ
| 学んだこと | ポイント |
|---|---|
requests.get() |
URLにGETリクエストを送る |
params |
辞書形式でURLパラメータを渡す |
timeout |
一定時間で諦める設定 |
raise_for_status() |
HTTPエラーを例外に変換する |
.json() |
レスポンスをPythonの辞書に変換 |
try / except |
エラーを安全にキャッチする |
.get("キー", デフォルト) |
キーがなくても安全に値を取り出す |
.env + dotenv
|
APIキーをコードから分離して管理する |
次回は price_tools/price_list_builder.py(フィルタリングと並び替え)を解説します!
シリーズ一覧
- ① プロジェクト全体像とフォルダ構成
- ② 楽天APIリクエスト(rakuten_api.py) ← 今回
- ③ フィルタリング・並び替え(price_list_builder.py)
- ④ 統計計算(price_stats.py)
- ⑤ CSV保存(csv_saver.py)
- ⑥ ポップアップUI(popup.py)
- ⑦ パス管理・設定(path_helper.py & config.py)
- ⑧ 全体統括(main_flow.py)
