Pythonで金価格APIを使う:価格の取得、キャッシュ、グラフ化
Pythonで金価格APIを使う方法を解説します。requestsでの最新価格の取得、エラー処理とリトライ、レスポンスのキャッシュ、pandasの時系列とmatplotlibによるグラフ化まで紹介します。
Pythonで金価格APIを使うための準備
このチュートリアルでは、Pythonで金価格APIを使う方法を、最初のリクエストからキャッシュ付きのクライアント、直近10か月のグラフまで順を追って説明します。使用するのは、貴金属、産業用金属、エネルギー、通貨、暗号資産を扱うJSON REST APIのMetalAPIと、よく知られた3つのライブラリです。HTTPにはrequests、時系列にはpandas、グラフにはmatplotlibを使います。すべてのコードはPython 3.9以降で動作します。
無料アカウントを作成し、ダッシュボードのAPI keysでAPIキーを作成します。キーはmk_live_で始まり、一度しか表示されないため、そのまま環境変数にコピーしてください。Freeプランはクレジットカード不要で、月100リクエストと毎日の価格更新が含まれています。この記事の内容はすべてFreeプランで試せます。
pip install requests pandas matplotlib
# macOSとLinux
export METALAPI_KEY="mk_live_your_key_here"
# Windows PowerShell
$env:METALAPI_KEY = "mk_live_your_key_here"キーはスクリプトに直接貼り付けず、os.environ["METALAPI_KEY"]で読み込んでください。変数が設定されていなければ、はっきりとエラーになります。.envファイルを使いたい場合は、python-dotenvパッケージのload_dotenv()を1回呼び出すだけで読み込めます。キーがリポジトリに含まれないように、.envを.gitignoreに追加してください。
Pythonで金価格を取得する方法
MetalAPIのすべてのエンドポイントは、https://metalapi.com/api/v1配下のGETリクエストです。キーは、api_keyクエリパラメーター、Authorization: Bearerヘッダー、X-API-Keyヘッダーのいずれかで送信できます。おすすめはヘッダーです。完全なURLはログに残りやすく、requestsの例外の中にはクエリ文字列付きのURLを出力するものがあるためです。/latestエンドポイントは現在の価格を返します。baseで通貨を指定し、currenciesでレスポンスを必要なシンボルだけに絞り込みます:
import os
import requests
resp = requests.get(
"https://metalapi.com/api/v1/latest",
params={"base": "USD", "currencies": "XAU"},
headers={"X-API-Key": os.environ["METALAPI_KEY"]},
timeout=10,
)
data = resp.json()
if not data["success"]:
raise SystemExit(f"MetalAPI error {data['error']['code']}: {data['error']['info']}")
price = data["rates"][data["base"] + "XAU"]
print(f"Gold: {price:,.2f} {data['base']} per troy ounce")レスポンスは次のようになります。数値はMetalAPIのドキュメント全体で使われている例の値で、リアルタイムの価格ではありません:
{
"success": true,
"timestamp": 1792834892,
"date": "2026-10-24",
"base": "USD",
"rates": { "XAU": 0.0002501319, "USDXAU": 3997.89 },
"rate_limit": { "limit": 100, "remaining": 99, "used": 1, "plan": "Free", "...": "..." }
}この値の場合、スクリプトはGold: 3,997.89 USD per troy ounceと出力します。rates["USDXAU"]は金1トロイオンスの基準通貨建て価格です。rates["XAU"]はその逆数で、1ドルあたりの金のオンス数です。これを誤って読み取ってしまうのが、金価格のコードで最も多いバグです。キーをdata["base"]から組み立てておけば、base=EUR(キーはEURXAU)やbase=INRに切り替えてもスクリプトは正しく動作します。1グラムあたりや1キログラムあたりの価格が必要なら、unit=gramまたはunit=kilogramを追加します。timestampは価格データの時刻をUnix秒で表したもので、リクエストの時刻ではありません。
Pythonで金価格APIのエラーを処理する
失敗した呼び出しは、HTTPエラーステータスと、success: falseを含むJSON本文を返します。本文のerrorオブジェクトには、数値のcode、type、infoメッセージが含まれます。分岐にはコードを使い、メッセージのテキストは決して使わないでください。再試行する価値があるのは、ごく一部のコードだけです:
104(too_many_requests、HTTP 429):1分あたりの上限です。Retry-Afterヘッダーの秒数だけ待ってください。500、503、ネットワークエラー:指数バックオフで再試行します。- それ以外のコード:
101や102(キーがない、または無効)、105(月間クォータを使い切った)、106(エンドポイントがプランに含まれていない)、2xxのバリデーションエラーなどは、再試行せずにリクエスト、キー、プランを修正してください。
次の小さなモジュールは、このロジックをまとめたものです。1つのrequests.Sessionを使い回すので呼び出し間で接続が維持され、失敗時には内容を確認できる例外を送出します:
import os
import time
import requests
API_URL = "https://metalapi.com/api/v1"
session = requests.Session()
session.headers["X-API-Key"] = os.environ["METALAPI_KEY"]
class MetalAPIError(Exception):
def __init__(self, code, type_, info, retry_after=None):
super().__init__(f"MetalAPI {code} ({type_}): {info}")
self.code = code
self.type = type_
self.retry_after = retry_after
def _get(path, params):
r = session.get(f"{API_URL}/{path}", params=params, timeout=10)
try:
data = r.json()
except ValueError: # JSONではない(プロキシが返すHTMLのエラーページなど)
raise MetalAPIError(r.status_code, "http_error", r.reason) from None
if not data.get("success"):
err = data["error"]
retry_after = r.headers.get("Retry-After")
raise MetalAPIError(err["code"], err["type"], err["info"], int(retry_after) if retry_after else None)
return data
def call(path, params=None, retries=3):
"""エンドポイントにGETする。再試行するのはレート制限、サーバーエラー、ネットワーク障害のみ。"""
for attempt in range(retries + 1):
try:
return _get(path, params or {})
except (MetalAPIError, requests.RequestException) as exc:
code = getattr(exc, "code", None) # ネットワークエラーの場合はNone
if attempt == retries or (code is not None and code != 104 and code < 500):
raise
time.sleep(getattr(exc, "retry_after", None) or 2**attempt)バックオフでは、あきらめる前に1秒、2秒、4秒待ちます。104やサーバーエラーで拒否されたリクエストは課金されないため、ここでの再試行にコストはかかりません。一方、バリデーションエラーは課金されます。これも、バリデーションエラーをループで再試行してはいけない理由の1つです。エラーハンドリングガイドには、すべてのコードとそのHTTPステータスが掲載されています。
金価格をキャッシュしてリクエストを節約する
価格は、プランごとに決まった間隔で更新されます。Freeでは1日1回、Liteでは30分ごと、Standardでは10分ごと、AdvancedとProでは60秒ごとです。更新はUTCの00:00から数えて、その間隔の倍数の時刻に行われます。そのため、次の更新より前に再度リクエストしても同じ数値が返され、クォータを無駄に消費するだけです。次のキャッシュは、各レスポンスを次の更新まで保持し、APIに到達できない場合は最後に取得できた価格を返します:
import math
import time
import requests
from metalapi import MetalAPIError, call
# 秒。Free:86_400、Lite:3_600(1時間ごとなら1,000リクエストに収まる)、Standard:600
UPDATE_INTERVAL = 600
_cache = {} # (base, symbols, unit) -> (expires_at, data)
def latest(base="USD", symbols="XAU,XAG", unit="troy_oz"):
key = (base, symbols, unit)
now = time.time()
hit = _cache.get(key)
if hit and now < hit[0]:
return hit[1]
try:
data = call("latest", {"base": base, "currencies": symbols, "unit": unit})
except (MetalAPIError, requests.RequestException):
if not hit:
raise
_cache[key] = (now + 60, hit[1]) # 最後に取得できた価格を返し、1分後に再試行
return hit[1]
next_update = (math.floor(now / UPDATE_INTERVAL) + 1) * UPDATE_INTERVAL
_cache[key] = (next_update + 5, data) # 次の更新の数秒後に期限切れにする
return datalatest("EUR", "XAU", "gram")["rates"]["EURXAU"]は、1グラムあたりのユーロ建て価格を返します。必要なシンボルはすべて1回の呼び出しで取得してください。XAU,XAG,XPT,XPDなら、4リクエストではなく1リクエストで済みます。なお、このキャッシュはメモリ上にあります。cronで起動されるスクリプトは毎回空のキャッシュから始まるため、その場合はスケジュールそのものがキャッシュの役割を果たします。実行は更新間隔ごとに最大1回にしてください。クォータを確認するには/usageを呼び出します。このエンドポイントはクォータにカウントされません。
金価格の時系列をpandasに読み込む
過去データには、/timeframeエンドポイント(エイリアス/timeseries)を使います。start_dateからend_dateまでの日次終値を、日付をキーとして返します。1リクエストでカバーできるのはFreeプランで最大30日、有料プランで最大365日で、それより長い期間はエラー207になります。次の関数は、任意の期間を許可された長さに分割し、pandasのSeriesを返します:
from datetime import date, timedelta
import pandas as pd
from metalapi import call
def gold_history(start, end, base="USD", max_days=30): # 有料プランではmax_days=365
rates = {}
chunk_start = start
while chunk_start <= end:
chunk_end = min(chunk_start + timedelta(days=max_days - 1), end)
data = call("timeframe", {
"start_date": chunk_start.isoformat(),
"end_date": chunk_end.isoformat(),
"currencies": "XAU",
"base": base,
})
rates.update(data["rates"])
chunk_start = chunk_end + timedelta(days=1)
df = pd.DataFrame.from_dict(rates, orient="index")
df.index = pd.to_datetime(df.index)
return df[f"{base}XAU"].sort_index().rename(f"gold_{base.lower()}")
gold = gold_history(date(2026, 1, 1), date(2026, 10, 23))
gold.to_csv("gold_usd.csv")
print(f"{len(gold)} trading days, high {gold.max():,.2f}, low {gold.min():,.2f}")Freeプランでは、この10か月分の取得に10リクエストかかります。Seriesは取引日ごとに1行で、週末や市場の休日は補完されずに欠落するため、行数は暦日数より少なくなります。毎日の値が必要な場合は、gold.asfreq("D").ffill()で金曜日の終値を週末に引き継げます。ここから先は、日次リターンならgold.pct_change()、移動平均ならgold.rolling(20).mean()のように、通常のpandasのツールを使えます。
過去の終値は変わらないため、CSVファイルが過去データのキャッシュになります。次回は同じ期間を再度リクエストせず、このファイルを読み込んでください。2000年以降の全データをデータベースに一括で取り込む方法は、APIで金・銀の過去価格を取得するをご覧ください。
matplotlibで金価格をグラフにする
グラフはCSVファイルを読み込むので、何度描き直してもリクエストは消費しません。日次終値と20日移動平均を一緒にプロットします:
import matplotlib.pyplot as plt
import pandas as pd
gold = pd.read_csv("gold_usd.csv", index_col=0, parse_dates=True)["gold_usd"]
sma20 = gold.rolling(20).mean()
fig, ax = plt.subplots(figsize=(10, 5))
ax.plot(gold.index, gold, linewidth=1, label="Gold, USD per troy oz")
ax.plot(sma20.index, sma20, linewidth=2, label="20-day moving average")
ax.set_title(f"Gold price, {gold.index[0]:%d %b %Y} to {gold.index[-1]:%d %b %Y}")
ax.yaxis.set_major_formatter("{x:,.0f}")
ax.grid(alpha=0.3)
ax.legend()
fig.autofmt_xdate()
fig.tight_layout()
fig.savefig("gold.png", dpi=150)インデックスが実際の日付を保持しているため、matplotlibは点を時間の間隔に沿って配置し、移動平均は20取引日目から始まります。別の通貨建てで金価格をグラフにするには、base="EUR"を指定してgold_historyを呼び出します。レートのキーは同じパターンに従うので、関数を変更する必要はありません。
次のステップ
これで、ほとんどのPython連携に必要なものがそろいました。認証付きのリクエスト、適切な再試行を伴うエラー処理、プランの更新間隔に合わせたキャッシュ、そしてpandasでの過去データの扱いです。まずはFreeプランで構築してテストしましょう。より新しい価格が必要になったら、更新間隔とリクエスト数でプランを選んでください。10分ごとに実行するジョブは月に約4,500リクエストを使うため、Standardの10,000に収まります。プランの比較は料金ページをご覧ください。
価格をウェブサイトに表示する場合は、JavaScriptで金価格APIを使うで、キーを公開せずにNode.jsのルートから価格を配信する方法を紹介しています。
よくある質問
Pythonで使える無料の金価格APIはありますか?
はい。MetalAPIのFreeプランには月100リクエストと毎日の価格更新が含まれ、クレジットカードは不要です。この記事のコードはすべてFreeプランで動作します。最新価格は前日の終値で、1回の/timeframeリクエストで最大30日分を取得できます。無料プランの詳細は無料の金価格APIで説明しています。
XAUのレートが非常に小さな数値なのはなぜですか?
rates["XAU"]は逆数のレートで、基準通貨1単位あたりの金のトロイオンス数です。価格は接頭辞付きのキーにあり、基準通貨がUSDならrates["USDXAU"]、EURならrates["EURXAU"]です。例の値では、1÷0.0002501319で3997.89に戻ります。
Pythonで1グラムあたりの金価格を取得するには?
リクエストにunit=gramを追加します。たとえばcall("latest", {"base": "EUR", "currencies": "XAU", "unit": "gram"})のようにします。このときrates["EURXAU"]は1グラムあたりのユーロ建て価格になり、レスポンスには"unit": "gram"が含まれます。unit=ounceは避けてください。これは常衡オンスで、金の価格に使われるトロイオンスではありません。
金価格の過去データはどこまでさかのぼれますか?
金の日次価格は2000年から提供されており、どのプランでも全期間を照会できます。プランによって異なるのは、1回の/timeframeリクエストでカバーできる日数だけで、Freeでは30日、有料プランでは365日です。max_days=365なら、2000年から現在までのデータを27リクエストで取得できます。
PythonスクリプトからAPIをどのくらいの頻度で呼び出すべきですか?
プランの価格更新より頻繁に呼び出す必要はありません。Freeでは1日1回(UTCの00:00以降)、Liteでは1時間ごと(30分ごとだと月に約1,490リクエストとなり、1,000のクォータを超えるため)、Standardでは10分ごとです。週末は価格が動かないものと考えてください。スポット金は、ニューヨーク時間の金曜17:00から日曜17:00まで取引されないためです。
関連記事
XAU/USDの意味:XAU・XAG・XPT・XPDが表すもの
XAU/USDの意味を解説します。金にISO 4217の通貨コードがある理由、XAG・XPT・XPDの意味、価格やAPIデータでの表示方向の読み方までわかります。
無料の金価格API:月100リクエストでできること
無料の金価格APIを正直に解説します。MetalAPIのFreeプランの内容、キャッシュとまとめ取得で月100リクエストを有効に使う方法、アップグレードの目安を紹介します。
JavaScriptで金価格APIを使う:Node.js、TypeScript、Next.js
JavaScriptで金価格APIから金価格を取得する方法。async/awaitとfetch、TypeScriptのレスポンス型、キャッシュ付きのExpress・Next.jsルート、APIキーの安全な扱い方を解説します。