チュートリアル · Python · FastAPI
FastAPIでピナクルオッズプロキシ
回答: 環境変数の PINNFEED_KEY を読む小さなFastAPIアプリを動かし、httpx で /v1/odds を呼び、数秒キャッシュして必要なフィールドだけ返します。ブラウザはあなたのAPIと話し、キーはサーバーから出ず、キャッシュで多数の訪問者をレート制限内に収めます。
app.py#
import os, time
import httpx
from fastapi import FastAPI, HTTPException
KEY = os.environ["PINNFEED_KEY"] # ps_live_...
API = "https://pinnfeed.io/v1"
TTL = 5 # seconds
cache: dict[str, tuple[float, list]] = {}
app = FastAPI()
client = httpx.AsyncClient(headers={"x-api-key": KEY}, timeout=10)
@app.get("/odds/{sport}")
async def odds(sport: str, q: str | None = None):
ck = f"{sport}|{q}"
hit = cache.get(ck)
if hit and time.time() - hit[0] < TTL:
return hit[1]
params = {"sport": sport, "market": "moneyline", "main": "true", "limit": 100}
if q:
params["q"] = q
r = await client.get(f"{API}/odds", params=params)
if r.status_code != 200:
raise HTTPException(r.status_code, r.json().get("error", {}).get("message", "upstream error"))
rows = [
{"id": e["id"], "home": e["home"], "away": e["away"], "start": e["startTime"], "live": e["live"],
"prices": {o["name"]: o["decimal"] for m in e["markets"] for o in m["outcomes"]},
"maxStake": max((m["maxStake"] or 0) for m in e["markets"]) if e["markets"] else None}
for e in r.json()["events"]
]
cache[ck] = (time.time(), rows)
return rows
pip install fastapi uvicorn httpx の後 uvicorn app:app --port 8000 で起動し、/odds/soccer?q=arsenal を呼びます。
本番メモ#
- 公開するなら自前の認証やレート制限を追加。
- PinnFeedの
429とRetry-Afterを尊重。プランは Free 1、Hobby 2、Builder 10、Pro 30 req/s。 - ライブボードはサーバー側WebSocket(Builder)でfan-out:Python WebSocket。
CTA#
Builder(99€/月)。3日間トライアル、カード必須。無料REST:signup。 他ホスト:Cloudflare Worker、Supabase。関連:ブラウザJS / CORS。
よくある質問#
なぜJSから直接PinnFeedを呼ばない?#
CORSは通るがキーが公開される:ブラウザJSとCORS。
キャッシュでオッズは古くなりますか?#
最大TTL秒。プレマッチは1–5秒、ライブはWebSocket。
Flaskでもできますか?#
はい。requests とdictキャッシュで同じパターンです。
18歳以上の方を対象とした教育目的のコンテンツです。予想や利益の約束ではありません。日本では刑法により賭博は原則として禁止されています。