「api」は統計データ分析の文脈で扱う重要概念のひとつ。 本ページでは「api」を取り巻く中核キーワードを以下にチップで一覧化する。 各キーワードは関連する概念・手法・道具立てを含み、 文献検索や学習計画の起点になる。
これらのキーワードは「api の理解 → 適用 → 検証」のプロセスを構成する。 各章で詳しく解説する。
🍰 まずはやさしく
APIはプログラム同士の約束事です。
データをやり取りするために使います。
SNSで情報を集める時に役立ちます。
APIの基本ルールについて学びます。
API ── プログラム間のやり取り規約
GET(取得)/POST(作成)/PUT(更新)/DELETE(削除)🍰 まずはやさしく
APIはデータの取得に欠かせません。
効率よくデータを集めるために使います。
スマホアプリの裏側で動いています。
APIを学ぶ流れについて説明します。
e-Stat、 気象庁、 Twitter/X、 Slack、 OpenAI など、 現代のデータ取得は API 経由が標準。 Excel ダウンロードから卒業するならまずAPIを覚えるのが効率的です。
本ページでは「api」を扱う。 統計データ分析コンペティション (2026) の教材で、 SSDSE-B-2026 (47 都道府県 × 複数年 × 100 超列) の実データを使った再現可能な学習を目指す。
「api」は統計・データサイエンスの体系における重要概念のひとつ。 本ページは「定義・直感・数式・実装・落とし穴・関連手法」の 6 視点で構成され、 各視点は独立して読めるが順序通り読むと体系的な理解が得られる。
🍰 まずはやさしく
APIは注文窓口のようなものです。
中身を知らなくても正しく頼むためです。
コンセントにプラグを刺す感覚に似ています。
具体的な仕組みと例を見ていきましょう。
「レストランの注文窓口」が API の良い比喩:
これにより、 キッチン側は内部を変えても窓口さえ守れば客に影響が出ない。
API のもう一つの掴み方は「電気のコンセント」アナロジー。 家電メーカー (クライアント) は冷蔵庫の中身を自前で発電せず、 規格化された 100V/50Hz の差し込み口 (API 仕様) に従えば電力会社 (サーバ) から電気を取得できる。 重要なのは「電力会社が水力から原子力に発電方式を変えても、 差し込み口の規格が同じならコンセント側の家電は無改修で動く」点で、 これが API における「実装の隠蔽 (encapsulation)」と「契約 (interface contract)」の本質である。
本ページでは API を、 SSDSE-B-2026 を提供する e-Stat API や、 気象庁 JSON API のような実例に紐づけて (1) リクエスト URL とメソッド、 (2) パラメータと認証キー、 (3) JSON レスポンス、 (4) Python クライアントでの解釈の 4 段階で具体化する。 政府統計を curl 一発で取得できる体験を通じ、 「データ取得 = ファイルダウンロード」の固定観念から「データ取得 = API コール」へ視点を移す。
具体例として SSDSE-B-2026 を e-Stat 風の URL `GET /api/stats/B/2026?pref=13` で取得するイメージを次節以降で示す。 ステータスコード 200/404/429、 レスポンスタイム、 ページネーションの cursor といった「データ分析以前のインフラ概念」を、 Python の requests と pandas で実演する。
🍰 まずはやさしく
APIは決まった形式の命令書です。
正確にデータをやり取りするために使います。
Webサイトから情報を取る時に使われます。
リクエストとレスポンスの形を学びます。
GET https://api.example.com/v1/users/42?lang=jaAuthorization: Bearer <token>
{ "id": 42, "name": "Taro", "lang": "ja" }
| 方式 | データ形式 | キャッシュ | 双方向 | SSDSE 配信に向く? |
|---|---|---|---|---|
| REST | JSON / XML | HTTP ヘッダで効く | 不可 | ◎ 単純配信 |
| GraphQL | JSON | 難しい | Subscription で可 | ○ クエリ柔軟 |
| gRPC | Protobuf (バイナリ) | 不向き | 双方向ストリーム | ○ マイクロサービス間 |
| WebSocket | 任意 | 不向き | 双方向 | △ リアルタイム不要 |
| SSE | テキストストリーム | 不向き | サーバ→クライアント片方向 | △ 更新通知用 |
| SOAP | XML | 困難 | 不可 | × レガシー |
| Webhook | JSON POST | — | サーバ→クライアント Push | ○ データ更新通知 |
| OData | JSON / XML + クエリ | HTTP | 不可 | ○ クエリ可能 REST |
| 方式 | 難易度 | セキュリティ | 用途 |
|---|---|---|---|
| なし(公開 API) | ★ | 低 | SSDSE のような公開統計 |
| API キー(ヘッダ) | ★★ | 中 | 軽量サービス、 個別開発者 |
| HTTP Basic 認証 | ★ | 低(HTTPS 必須) | レガシー、 社内 |
| OAuth 2.0 (Client Credentials) | ★★★ | 高 | サーバ間 (M2M) |
| OAuth 2.0 (Authorization Code) | ★★★★ | 高 | ユーザ代理 |
| JWT (HS256) | ★★★ | 中 | マイクロサービス内 |
| JWT (RS256) | ★★★★ | 高 | クロスサービス |
| mTLS | ★★★★★ | 最高 | 金融、 ヘルスケア |
| Cookie + CSRF Token | ★★★ | 中 | Web ブラウザ |
| API 鍵 + 署名 (AWS SigV4 等) | ★★★★★ | 最高 | クラウド API |
| コード | 意味 | SSDSE API での例 |
|---|---|---|
| 200 | 成功 | 47 件取得 OK |
| 201 | 作成成功 | POST /prefectures で新規追加 |
| 204 | 成功(本文なし) | DELETE /prefectures/R13000 |
| 301 / 308 | 恒久リダイレクト | API v1 → v2 移行 |
| 304 | 未変更(キャッシュ有効) | If-None-Match の応答 |
| 400 | リクエスト不正 | year=abc など型違い |
| 401 | 認証必要 | Authorization ヘッダ無し |
| 403 | 権限なし | 有料データ未契約 |
| 404 | 見つからない | code=R99999 |
| 409 | 競合 | 同時更新衝突 |
| 422 | 意味的に無効 | JSON は valid だがフィールド組合せ NG |
| 429 | レート超過 | 1 秒に 100 req |
| 500 | サーバ内部エラー | 例外未処理 |
| 502 | ゲートウェイ不正 | upstream nginx エラー |
| 503 | サービス停止 | メンテナンス中 |
| 504 | ゲートウェイタイムアウト | upstream 5 秒超 |
API は「ドキュメントこそ最初に書く(API-first 設計)」が現代流。 OpenAPI 3.0 (旧 Swagger) で SSDSE API を最小定義すると以下のようになる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | openapi: 3.0.3 info: title: SSDSE-B-2026 API version: 1.0.0 description: 47 都道府県の年度別統計(人口・婚姻ほか 112 列) paths: /prefectures: get: summary: 47 都道府県の一覧 parameters: - name: year in: query schema: { type: integer, default: 2023 } responses: '200': description: 成功 content: application/json: schema: type: object properties: count: { type: integer, example: 47 } items: type: array items: { $ref: '#/components/schemas/Prefecture' } components: schemas: Prefecture: type: object properties: code: { type: string, example: 'R13000' } name: { type: string, example: '東京都' } population: { type: integer, example: 14086000 } |
この YAML を Swagger UI に食わせるとブラウザでインタラクティブな API ドキュメントが自動生成される。 SSDSE データを「自前 API で配信する」教材を作るとき、 まずこのドキュメントを書いてからコードに落とすのが王道。
SSDSE は毎年更新される(B-2024, B-2025, B-2026, ...)ので、 API も「データ年度」と「API 仕様年度」の 2 軸でバージョン管理する必要がある。
| 方式 | URL 例 | 長所 | 短所 |
|---|---|---|---|
| URL パス (v1, v2) | /api/v2/prefectures | キャッシュしやすい、 視認性高 | URL が増える |
| クエリパラメータ | /api/prefectures?api_version=2 | シンプル | キャッシュキーに含めにくい |
| ヘッダ | Accept: application/vnd.ssdse.v2+json | URL がクリーン | クライアント実装複雑 |
| サブドメイン | v2.api.ssdse-example.go.jp | 負荷分散しやすい | CORS / 証明書管理が増える |
total_population → population_total。 旧クライアントが KeyErroryear パラメータが必須に。 旧クライアントが 400 エラーpopulation が int → string に。 数値計算が文字列連結に/api/v1/prefectures 廃止。 旧クライアント全停止これらを避けるには (1) 新フィールドは追加のみ、 (2) 削除前に Deprecation ヘッダで予告、 (3) 最低 1 年は旧版を維持、 (4) OpenAPI で差分をレビュー、 が定石。
| API | 提供元 | SSDSE との関係 |
|---|---|---|
| e-Stat API | 総務省統計局 | SSDSE-B-2026 の原データを提供 |
| RESAS API | 内閣府 | 都道府県別の経済・人口可視化 |
| 気象庁 API | 気象庁 | SSDSE-B-2026 の気温・降水量列の原典 |
| 国土地理院 API | 国土地理院 | 地域コード R01000 〜 R47000 の地理情報 |
| マイナポータル API | デジタル庁 | 個人情報結合の入口 |
| EDINET API | 金融庁 | 都道府県別経済(補助) |
| e-Gov API | デジタル庁 | 法令データ |
| OpenWeatherMap | OpenWeather Ltd. | 気象データ補完 |
| 原則 | OK 例 | NG 例 |
|---|---|---|
| 名詞を使う | /prefectures | /getPrefectures |
| 複数形を使う | /prefectures | /prefecture |
| ハイフン区切り | /data-sources | /data_sources, /dataSources |
| 小文字統一 | /prefectures | /Prefectures |
| 動詞は HTTP メソッド | GET /prefectures | /getAllPrefectures |
| 階層構造で表現 | /prefectures/R13000/years/2023 | /prefecture-year?p=R13000&y=2023 |
| ID は意味のある値 | /prefectures/R13000 | /prefectures/8af3-... |
| クエリでフィルタ | ?year=2023&sort=population | URL に詰める |
| 動詞アクションは例外的 | POST /prefectures/R13000/recalculate | — |
| メソッド | 用途 | 冪等 | SSDSE 例 |
|---|---|---|---|
| GET | 取得 | ○ | 都道府県データの読み出し |
| POST | 作成 | × | 新しい年度のデータ追加 |
| PUT | 全体更新(冪等) | ○ | 都道府県データの全置換 |
| PATCH | 部分更新 | ○ or × | 人口だけ更新 |
| DELETE | 削除 | ○ | 誤入力データの削除 |
| HEAD | ヘッダのみ取得 | ○ | CSV サイズ確認 |
| OPTIONS | 許可メソッド確認 | ○ | CORS preflight |
{"id": ..., "name": ..., ...} をそのまま{"count": N, "items": [...], "next": "..."} でメタ情報を付帯{"error": {"code": "NOT_FOUND", "message": "...", "details": [...]}} 構造化{"_links": {"self": {"href": "..."}, "next": {"href": "..."}}} ナビゲーション情報/prefectures/R13000 を R47000 に書き換えて他人のデータが見えないか?is_admin: true を渡されて権限昇格しないかSSDSE のような公開データ API でも、 1, 4, 7, 9, 10 は必ず該当する。 ペネトレーションテストで定期的にスキャン。
API そのものは「契約・規約」なので厳密な閉形式の数式はないが、 性能・信頼性・スループットを評価するときには明確な数式が出てくる。 ここでは「API のレート制御・スループット・遅延」を支える 3 つの数式を、 SSDSE-B-2026 を例に解読する。
API サーバの並行処理数 $L$、 スループット $\lambda$(req/秒)、 平均応答時間 $W$ の関係。
$$ L = \lambda \cdot W $$
SSDSE 取得 API なら「47 都道府県のクエリを 1 秒で返す」場合、 $\lambda$=47、 $W$=1 秒 → $L$=47 並行スレッドが必要。 これがサーバ設計の基本数式。
1 秒あたり $r$ トークンを補充し、 バケット容量 $b$ まで貯められる。 リクエスト 1 回ごとに 1 トークン消費。
$$ \mathrm{tokens}(t) = \min(b,\, \mathrm{tokens}(t-\Delta) + r \cdot \Delta) $$
e-Stat API は「1 秒あたり 5 req、 バースト 50 req まで」のような制限。 47 県分を取りたいなら $b \geq 47$ が必要、 さもなくば $47 / 5 \approx 9.4$ 秒の sleep が必要。
$n$ 回目のリトライまでの待ち時間。
$$ T_n = \min(T_{\max},\; T_0 \cdot 2^{n-1}) + U(0,J) $$
API 呼び出しが 429 (Too Many Requests) で失敗したら、 1 秒 → 2 秒 → 4 秒 → 8 秒 …と倍々で待つ。 これが「指数バックオフ + ジッタ」の標準実装。
e-Stat API で「2023年人口」を取得する流れ:
http://api.e-stat.go.jp/rest/3.0/app/json/getStatsData?appId=...&statsDataId=0003448237合成データ (7 回の API リクエストの応答時間、 ms 単位、 多様な整数) を使い、 平均レイテンシの数式に値を代入して Step 1〜3 で展開する。 同じ計算を Python (numpy) で再現し、 手計算と完全一致を確認する。
$$ \bar{L} = \frac{1}{N} \sum_{i=1}^{N} L_i \qquad (L_i:\,\text{第 }i\,\text{回の応答時間、 } N:\,\text{試行回数}) $$
| 試行 i | 応答時間 L_i (ms) |
|---|---|
| 1 | 120 |
| 2 | 85 |
| 3 | 240 |
| 4 | 95 |
| 5 | 310 |
| 6 | 180 |
| 7 | 70 |
| 項目 | 計算 | 結果 |
|---|---|---|
| Σ L_i | 120 + 85 + 240 + 95 + 310 + 180 + 70 | 1100 |
| N | 試行回数 | 7 |
| 指標 | 計算 | 結果 (ms) |
|---|---|---|
| 平均 L̄ | 1100 / 7 | 157.14 |
| 最大 (実質 p95 相当) | max(L_i) | 310 |
| 最小 | min(L_i) | 70 |
1 2 3 4 5 6 7 8 | import numpy as np latency = np.array([120, 85, 240, 95, 310, 180, 70]) mean_l = latency.mean() p95 = np.percentile(latency, 95) print(f"平均レイテンシ L̄ = {mean_l:.2f} ms") print(f"最大 = {latency.max()} ms") print(f"最小 = {latency.min()} ms") print(f"p95 = {p95:.2f} ms") |
💬 手計算 Step 3 の L̄ = 157.14 ms と Python 出力が完全一致。 平均は外れ値 (310 ms) に引っ張られやすいため、 API SLO の評価では平均だけでなく p95/p99 を併用するのが定石。 SLO 「p95 ≤ 300 ms」の例だと、 この 7 サンプルの p95=289 ms はギリギリ合格水準。
e-Stat 政府統計 API から SSDSE-B-2026 相当のデータを取得すると仮定し、 47 都道府県を順に呼び出すときの実数感覚を計算する。
| シナリオ | パラメータ | 所要時間(実値) |
|---|---|---|
| 1 県 1 リクエスト、 直列 | $N$=47, $W$=0.3 秒 | 47 × 0.3 = 14.1 秒 |
| 10 並列スレッド | $N$=47, $W$=0.3 秒, 並列 10 | 47/10 × 0.3 = 1.41 秒 |
| レート制限 5 req/秒 | $N$=47, レート=5 | 47/5 = 9.4 秒(並列効果なし) |
| バルク 1 リクエスト | $N$=1(全 47 県を一括) | 0.5 秒(最速) |
| レスポンス JSON サイズ | 1 県あたり 4 KB | 47 × 4 = 188 KB |
| CSV 直接取得 | SSDSE-B-2026.csv | 1 リクエスト、 359,821 byte |
→ 47 県を取りたいときは、 個別 API より「バルクエンドポイント」もしくは「CSV 直接 DL」が桁違いに高速。 API 設計時はバルクと個別を必ず両方用意するのが定石。
| 項目 | 値 |
|---|---|
| 都道府県数 | 47 |
| 全 47 県合計人口 | 124,353,000 人 |
| 東京都人口 | 14,086,000 人 |
| 北海道人口 | 5,092,000 人 |
| 鳥取県人口 | 537,000 人(最小) |
| 列数 | 112 |
| CSV サイズ | 359,821 byte |
| SHA-256 | 0fdbe5f603bb... |
API レスポンスを受け取った後、 上記の値(特に「47 県」「合計 124,353,000 人」)が一致しているかを受信側で必ず検証するのがロバストな設計。
最小限のスニペットで動作確認できる例。 公的データ(SSDSE 等)を想定しています。
1 2 3 4 5 6 7 8 9 10 11 12 | import requests import pandas as pd url = "https://api.example.com/v1/data" params = {"prefecture": "all", "year": 2023} headers = {"Authorization": "Bearer YOUR_TOKEN"} resp = requests.get(url, params=params, headers=headers, timeout=10) resp.raise_for_status() # 4xx/5xx で例外 data = resp.json() df = pd.DataFrame(data["results"]) print(df.head()) |
🎯 このコードでやること: SSDSE-B-2026 を読み込み、 47 都道府県のデータを GET /prefectures/{code} エンドポイントで返す REST API を FastAPI で 30 行で実装する。
📥 入力データ: data/raw/SSDSE-B-2026.csv (359,821 byte, 564 行, 112 列, cp932)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | from fastapi import FastAPI, HTTPException import pandas as pd app = FastAPI(title="SSDSE-B-2026 API", version="1.0.0") # 起動時に CSV を読み込む(メモリに保持) hdr = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', nrows=2, header=None).iloc[1].tolist() df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=2, header=None, names=hdr) @app.get("/prefectures") def list_prefectures(year: int = 2023): sub = df[df['年度'] == year][['地域コード', '都道府県', '総人口']] return {"count": len(sub), "items": sub.to_dict(orient='records')} @app.get("/prefectures/{code}") def get_one(code: str, year: int = 2023): row = df[(df['年度'] == year) & (df['地域コード'] == code)] if len(row) == 0: raise HTTPException(404, detail=f"code {code} not found") return row.iloc[0].to_dict() # 起動: uvicorn api_server:app --host 0.0.0.0 --port 8000 |
📤 実行例 (curl http://localhost:8000/prefectures?year=2023):
💬 結果の読み方: わずか 30 行で 47 都道府県分のデータを返す REST API が完成。 count=47 で全県揃っていることを確認、 items に地域コード・都道府県名・総人口がリスト化。 /prefectures/R13000 なら東京都だけ取得。 これが「JSON で構造化されたデータをエンドポイント単位で配信する」最小実装。
🎯 このコードでやること: 上の FastAPI を呼び出して 47 都道府県を順番に取得し、 ネットワーク失敗時は指数バックオフでリトライ。 取得したデータを DataFrame にまとめて 47 件であることを検証する。
📥 入力データ: GET http://localhost:8000/prefectures → JSON
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | import requests, time, random import pandas as pd BASE = 'http://localhost:8000' def get_with_retry(url, max_retries=5): for n in range(max_retries): try: r = requests.get(url, timeout=5) if r.status_code == 429: # レート超過 wait = min(60, (2**n)) + random.uniform(0, 0.5) print(f'rate-limited, sleep {wait:.1f}s') time.sleep(wait) continue r.raise_for_status() return r.json() except requests.RequestException as e: print(f'attempt {n+1} failed: {e}') time.sleep(2**n) raise RuntimeError('max retries exceeded') # 1) 47 県分一括取得(推奨) data = get_with_retry(f'{BASE}/prefectures?year=2023') df = pd.DataFrame(data['items']) print('取得件数:', len(df)) print('合計人口:', int(df['総人口'].sum())) # 2) 個別取得(遅い、 比較用) codes = df['地域コード'][:3] for c in codes: one = get_with_retry(f'{BASE}/prefectures/{c}') print(c, one['都道府県'], one['総人口']) |
📤 実行例:
💬 結果の読み方: 47 件 + 合計 124,353,000 人 = SSDSE-B-2026 の 2023 年度全国人口と完全一致。 もし 47 件未満や合計が違えば「API が壊れている」「データが欠損している」と即検出できる。 受信側での件数 + 集計値検証は API 取得の最重要作法。
🎯 このコードでやること: OAuth 2.0 Client Credentials フローを模倣し、 POST /oauth/token で access_token を取得、 以後の API 呼び出しで Authorization: Bearer ... として再利用。 SSDSE 取得 API の認証付き版。
📥 入力データ: client_id, client_secret(環境変数)と API ベース URL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | import requests, os, time BASE = 'https://api.ssdse-example.go.jp' CLIENT = os.environ.get('SSDSE_CLIENT_ID', 'demo') SECRET = os.environ.get('SSDSE_CLIENT_SECRET', 'demo') _token_cache = {'token': None, 'expires_at': 0} def get_token(): if _token_cache['token'] and time.time() < _token_cache['expires_at'] - 30: return _token_cache['token'] r = requests.post(f'{BASE}/oauth/token', data={'grant_type': 'client_credentials', 'client_id': CLIENT, 'client_secret': SECRET}, timeout=5) r.raise_for_status() j = r.json() _token_cache.update(token=j['access_token'], expires_at=time.time() + j['expires_in']) return j['access_token'] def api_get(path): tok = get_token() r = requests.get(f'{BASE}{path}', headers={'Authorization': f'Bearer {tok}'}, timeout=5) r.raise_for_status() return r.json() # 47 県の取得 (トークン 1 回で全部済む) data = api_get('/prefectures?year=2023') print('取得件数:', data['count']) |
📤 実行例:
💬 結果の読み方: トークンキャッシュが効いているので、 2 回目以降の get_token() は HTTP 呼び出しゼロで即返る。 期限 30 秒前に自動更新するロジック付き。 これが本番 API 利用の最小作法 ── client_id / secret は環境変数化、 トークンはメモリキャッシュ、 期限管理。
🎯 このコードでやること: REST API は固定の JSON 構造を返すが、 GraphQL ならクエリで必要な列だけ指定できる。 SSDSE-B-2026 の 112 列のうち「都道府県名・人口・婚姻」3 列だけ要求するクエリを実装。
📥 入力データ: GraphQL エンドポイント POST /graphql
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | import requests, json ENDPOINT = 'https://api.ssdse-example.go.jp/graphql' QUERY = """ query SSDSE($year: Int!) { prefectures(year: $year) { code name population marriages } } """ res = requests.post(ENDPOINT, json={'query': QUERY, 'variables': {'year': 2023}}) data = res.json()['data']['prefectures'] print('件数:', len(data)) for d in data[:3]: print(d) |
📤 実行例:
💬 結果の読み方: クエリで code, name, population, marriages の 4 つだけ要求 → レスポンスもその 4 つだけ。 REST だと全 112 列を返してしまい帯域の無駄。 GraphQL は「クライアントが欲しい形を指定」できる点で REST より柔軟だが、 N+1 問題やキャッシュ設計が難しいというトレードオフ。
🎯 このコードでやること: SSDSE-B-2026 CSV のハッシュ値を ETag として返し、 クライアントが前回の ETag を If-None-Match ヘッダで送ってきたら 304 を返してデータ転送をスキップする。 帯域節約の標準テクニック。
📥 入力データ: SSDSE CSV (359,821 byte, SHA-256=0fdbe5f603bb...)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | from fastapi import FastAPI, Header, Response import hashlib, pathlib app = FastAPI() CSV = pathlib.Path('data/raw/SSDSE-B-2026.csv') ETAG = hashlib.sha256(CSV.read_bytes()).hexdigest()[:16] @app.get("/datasets/SSDSE-B-2026.csv") def download(if_none_match: str = Header(None)): if if_none_match == ETAG: return Response(status_code=304, headers={'ETag': ETAG}) return Response(content=CSV.read_bytes(), media_type='text/csv', headers={'ETag': ETAG, 'Cache-Control': 'public, max-age=31536000'}) # --- クライアント側 --- import requests url = 'http://localhost:8000/datasets/SSDSE-B-2026.csv' r1 = requests.get(url) print('1 回目:', r1.status_code, len(r1.content), 'byte') print('ETag:', r1.headers['ETag']) r2 = requests.get(url, headers={'If-None-Match': r1.headers['ETag']}) print('2 回目:', r2.status_code, len(r2.content), 'byte') |
📤 実行例:
💬 結果の読み方: 1 回目は 359,821 byte 全送信、 2 回目は If-None-Match ヘッダで「これ持ってる」と申告 → サーバが 304 を返して 0 byte。 帯域 100% 節約。 SSDSE のような年に 1 回しか更新されないデータには ETag が最適。
| シナリオ | REST | GraphQL | 推奨 |
|---|---|---|---|
| 47 都道府県の人口だけ欲しい | 112 列すべて返す(無駄) | 必要 1 列だけ返す | GraphQL |
| 東京都 1 件のフル情報 | 1 リクエストで完結 | 1 リクエストで完結 | 同等 |
| SSDSE 全体 (CSV) | 1 リクエスト | クエリ複雑 | REST |
| キャッシュ重視 | HTTP ヘッダで自然 | 難しい | REST |
| モバイル省帯域 | JSON 全部返る | フィールド絞れる | GraphQL |
| 仕様自動ドキュメント | OpenAPI | Introspection | 同等 |
| 初学者の学習コスト | 低 | 中 | REST |
マイクロサービス間で高速通信したい場合、 gRPC + Protocol Buffers が候補になる。 同じ SSDSE API を proto3 で書くと以下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | // ssdse.proto syntax = "proto3"; package ssdse; service SSDSEService { rpc GetPrefecture (GetReq) returns (Prefecture); rpc ListPrefectures (ListReq) returns (stream Prefecture); } message GetReq { string code = 1; int32 year = 2; } message ListReq { int32 year = 1; } message Prefecture { string code = 1; string name = 2; int64 population = 3; int64 marriages = 4; } |
protoc でクライアント・サーバのコードを多言語自動生成。 バイナリ通信で REST/JSON の 5-10 倍高速。 ただしブラウザから直接呼べないので、 SSDSE のような一般公開 API には不向き。 内部マイクロサービスや IoT デバイス間で使う。
| 項目 | REST/JSON | gRPC/Protobuf |
|---|---|---|
| 通信形式 | テキスト (JSON) | バイナリ (Protobuf) |
| HTTP バージョン | HTTP/1.1 or HTTP/2 | HTTP/2 必須 |
| スキーマ | OpenAPI (オプション) | .proto (必須) |
| ブラウザ対応 | ◎ | ×(grpc-web 必要) |
| 双方向ストリーム | × | ◎ |
| SSDSE 公開 API | ◎ | △ |
| 内部マイクロサービス | ○ | ◎ |
.env は .gitignore に追加。 GitHub Secrets / AWS Parameter Store で CI/CD に注入。Retry-After ヘッダがあればその秒数 sleep。 なければ指数バックオフ (1→2→4→8 秒)。 並列度を下げる。 バルクエンドポイントを使う。pytest + httpx でモック。 responses ライブラリで requests のレスポンスをスタブ化。 統合テストには vcr.py で本物のレスポンスを録画再生。/docs エンドポイントで Swagger UI が自動生成。 Flask なら flask-smorest、 Django なら drf-spectacular。 すべて OpenAPI 仕様準拠。| カテゴリ | ツール | 用途 |
|---|---|---|
| API 開発 | FastAPI / Flask / Django REST Framework | Python での REST 実装 |
| API 開発 | Express / NestJS | Node.js |
| API 開発 | Spring Boot | Java |
| API 仕様 | OpenAPI / Swagger UI / Redoc | ドキュメント自動生成 |
| API 仕様 | Stoplight / Postman Collection | 仕様駆動開発 |
| API テスト | Postman / Insomnia | GUI クライアント |
| API テスト | curl / HTTPie | CLI |
| API テスト | pytest + httpx / vcr.py | 自動テスト |
| 負荷試験 | Locust / k6 / Gatling | パフォーマンス検証 |
| API モック | WireMock / MSW / json-server | 開発時のスタブ |
| API ゲートウェイ | Kong / Tyk / AWS API Gateway | 認証・レート制限・ログ |
| API モニタリング | Datadog / New Relic / Grafana | レイテンシ・エラー率 |
| 分散トレーシング | Jaeger / Zipkin / OpenTelemetry | マイクロサービス間追跡 |
| API セキュリティ | OWASP ZAP / Burp Suite | 脆弱性スキャン |
Accept-Encoding: gzip, br で JSON が 1/5 サイズ。requests.Session() で TLS ハンドシェイク再利用。asyncio + httpx で 47 県を 1 秒以内に取得。?fields=name,population でクライアント指定。| 関連用語 | API との関係 | 遷移先 |
|---|---|---|
| JSON | API レスポンスの標準フォーマット | json |
| 暗号化 | TLS で通信路を保護、 JWT で認証 | encryption |
| 認証 | API キー・OAuth・JWT で誰かを証明 | authentication |
| なりすまし | 盗まれた API キーで攻撃される脅威 | impersonation |
| データ収集 | API は外部データ取得の主要手段 | data-collection |
| e-Stat | SSDSE-B-2026 を配布する公式 API | e-stat |
| RDB | API バックエンドの主要データソース | rdb |
| Webスクレイピング | API がない時の代替手段 | scraping |
| メタデータ | API レスポンスに含めるべき情報 | metadata |
| アクセス管理 | 誰がどの API を呼べるか | access-management |
🎯 このコードでやること: 同期版 (requests) と非同期版 (httpx + asyncio) で 47 都道府県を取得し、 実行時間を比較。 非同期版は約 10 倍速い実例を示す。
📥 入力データ: 47 個の地域コード(R01000, R02000, ..., R47000)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | import asyncio, httpx, time, requests BASE = 'http://localhost:8000' CODES = [f'R{i:02d}000' for i in range(1, 48)] # 47 県 # --- 同期版 --- t0 = time.time() for c in CODES: requests.get(f'{BASE}/prefectures/{c}', timeout=5) sync_t = time.time() - t0 # --- 非同期版 --- async def fetch_all(): async with httpx.AsyncClient(timeout=5) as cli: tasks = [cli.get(f'{BASE}/prefectures/{c}') for c in CODES] return await asyncio.gather(*tasks) t0 = time.time() results = asyncio.run(fetch_all()) async_t = time.time() - t0 print(f'同期: {sync_t:.2f} 秒') print(f'非同期: {async_t:.2f} 秒') print(f'高速化倍率: {sync_t/async_t:.1f} 倍') |
📤 実行例:
💬 結果の読み方: 同期版は 47 リクエストを順番に処理(約 4.8 秒)。 非同期版は asyncio で並列実行(約 0.4 秒)。 ネットワーク I/O が支配的なら非同期化で 10 倍以上高速化できる。 ただしサーバ側のレートリミットに引っかかりやすいので注意。
🎯 このコードでやること: OpenAPI YAML をもとに openapi-python-client で型付きクライアントを自動生成し、 SSDSE-B-2026 API を IDE 補完付きで呼ぶ。
📥 入力データ: OpenAPI YAML(上の api-openapi-design セクション参照)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | # 1) クライアント自動生成 (CLI 1 行) # pip install openapi-python-client # openapi-python-client generate --url http://localhost:8000/openapi.json # 2) 生成されたクライアントの使用 from ssdse_b_2026_api_client import Client from ssdse_b_2026_api_client.api.default import list_prefectures, get_one client = Client(base_url='http://localhost:8000') # 型補完が効く呼び出し result = list_prefectures.sync(client=client, year=2023) print('count:', result.count) print('tokyo:', [i for i in result.items if i.code == 'R13000'][0].population) tokyo = get_one.sync(client=client, code='R13000', year=2023) print('tokyo full:', tokyo) |
📤 実行例:
💬 結果の読み方: 自動生成クライアントは list_prefectures や get_one など、 OpenAPI YAML から名前・型・引数を全自動で生成。 IDE で補完が効き、 引数型エラーもコンパイル時に検出。 47 県を返す count: 47 と東京都人口 14,086,000 で SSDSE 実値と一致。
| パターン | 用途 | SSDSE 拡張例 |
|---|---|---|
| API Gateway | 外部公開の統一エントリ | Kong / Tyk で SSDSE API を外部公開 |
| BFF (Backend for Frontend) | UI 別に最適化された API | モバイル用 SSDSE BFF、 デスクトップ用 BFF |
| Saga パターン | 分散トランザクション | SSDSE 年度更新時の連鎖更新 |
| CQRS | 読み書き分離 | SSDSE 読み専 API + 編集 API を分離 |
| イベントソーシング | すべての変更をイベントとして保存 | SSDSE 列追加・修正のイベント履歴 |
| サーキットブレーカー | 下流サービス障害時の自動遮断 | e-Stat 障害時に SSDSE API をフェイルオーバ |
| サービスメッシュ (Istio) | マイクロサービス間通信制御 | SSDSE 集計 API + 可視化 API 間の自動 mTLS |
SSDSE-B-2026 を API 化したときの具体的な数値感覚をまとめる。
| 項目 | 値 | 意味 |
|---|---|---|
| 全データサイズ (CSV) | 359,821 byte (≒ 351 KB) | 1 リクエストで配信可能 |
| 全データサイズ (JSON) | ≒ 750 KB | JSON 化で 2 倍に膨張 |
| 全データサイズ (gzip 圧縮) | ≒ 80 KB | 1/5 に圧縮 |
| 1 都道府県の JSON サイズ | ≒ 4 KB | 112 列 × 12 年 |
| 個別 47 県取得(直列) | ≒ 14 秒 | NG パターン |
| 個別 47 県取得(10 並列) | ≒ 1.5 秒 | 並列化で改善 |
| 個別 47 県取得(非同期) | ≒ 0.4 秒 | asyncio 推奨 |
| バルク 1 リクエスト | ≒ 0.5 秒 | 最も望ましい |
| CSV 直接 DL | ≒ 0.3 秒 | 最速(静的ファイル) |
| FastAPI で 1 req 処理 | ≒ 5 ms | JSON 生成込み |
| nginx 静的配信 | ≒ 0.1 ms | API 不要なら最速 |
| SSDSE 12 年分 | 564 行 × 112 列 | = 63,168 セル |
| SSDSE 2023 年 47 県合計人口 | 124,353,000 人 | レスポンス検証用 |
| SHA-256 ダイジェスト | 0fdbe5f603bb... | ETag に使用 |
たった 1 行の requests.get(url) の裏で動くプロトコル層を整理する。
| 層 | プロトコル | 役割 |
|---|---|---|
| アプリケーション (HTTP) | HTTP/1.1, HTTP/2, HTTP/3 | リクエスト・レスポンス構文 |
| API スタイル | REST, GraphQL, gRPC | 意味論 |
| セキュリティ | TLS 1.3 | 暗号化・サーバ認証 |
| トランスポート | TCP / QUIC | 信頼性のあるストリーム |
| ネットワーク | IPv4 / IPv6 | パケットルーティング |
| リンク | Ethernet / Wi-Fi | 物理デバイス間転送 |
| 名前解決 | DNS / DoH / DoT | api.example.com → IP |
| キャッシュ | CDN (Cloudflare 等) | 静的レスポンスをエッジ配信 |
| 負荷分散 | L4 / L7 ロードバランサ | リクエスト分散 |
| 監視 | Prometheus + Grafana | メトリクス収集 |
| 日本語 | 英語 | 備考 |
|---|---|---|
| API | Application Programming Interface | — |
| エンドポイント | Endpoint | — |
| リクエスト | Request | — |
| レスポンス | Response | — |
| ヘッダ | Header | — |
| 本体 | Body | — |
| クエリパラメータ | Query parameter | ?key=value |
| パスパラメータ | Path parameter | {id} |
| 認証 | Authentication | 誰か |
| 認可 | Authorization | 何ができるか |
| アクセストークン | Access token | — |
| リフレッシュトークン | Refresh token | — |
| レートリミット | Rate limit | — |
| スロットリング | Throttling | — |
| 指数バックオフ | Exponential backoff | — |
| ジッタ | Jitter | — |
| サーキットブレーカー | Circuit breaker | — |
| べき等 | Idempotent | GET, PUT, DELETE |
| 非べき等 | Non-idempotent | POST |
| ステートレス | Stateless | セッション持たない |
| HATEOAS | Hypermedia as Engine of App State | リンク埋め込み |
| OpenAPI | OpenAPI Specification | 旧 Swagger |
| 仕様駆動開発 | API-first / Spec-first development | — |
| ペネトレーションテスト | Penetration test | — |
| 負荷試験 | Load test | Locust, k6 |
| API ゲートウェイ | API Gateway | — |
| サービスメッシュ | Service mesh | Istio |
| SDK | Software Development Kit | — |
| 分散トレーシング | Distributed tracing | Jaeger, Zipkin |
| ペイロード | Payload | 本体データ |
本ページで「API とは何か」「SSDSE-B-2026 をどう配信するか」「セキュリティ・パフォーマンス・設計はどうあるべきか」を一気通貫で整理した。 次の学習経路は以下。
SSDSE データ解析論文を書くとき、 「データ取得」の節に「e-Stat API から HTTPS で取得し、 SHA-256 で改ざん検知した」と 2 行入れるだけで、 査読者に「データ取得の作法を理解した研究者」と認識される。
API のパフォーマンスを「平均応答時間」で語るのは誤解の元。 実際はパーセンタイル分布で見るのが定石。
| 指標 | 意味 | SSDSE API の目標例 |
|---|---|---|
| p50 (中央値) | 半数のリクエストがこれ以下 | 50 ms 以下 |
| p90 | 9 割のリクエストがこれ以下 | 200 ms 以下 |
| p95 | 95% がこれ以下 | 500 ms 以下 |
| p99 | 99% がこれ以下 | 1,000 ms 以下 |
| p99.9 | 99.9% がこれ以下 | 3,000 ms 以下(テールレイテンシ) |
| 最大値 (max) | 最遅リクエスト | 監視対象、 タイムアウト未満 |
「平均 50 ms」と言われても、 もし p99 が 5 秒だったら 1% のユーザは耐えられない遅延を体験する。 SLA (Service Level Agreement) は通常 p99 で記述する。
| パーセンタイル | レイテンシ | 備考 |
|---|---|---|
| p50 | 8 ms | キャッシュヒット |
| p90 | 25 ms | — |
| p95 | 45 ms | — |
| p99 | 180 ms | キャッシュミス + DB クエリ |
| p99.9 | 850 ms | GC 一時停止 |
| max | 2,300 ms | 外れ値(要調査) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | # 47 県の一覧取得 curl http://localhost:8000/prefectures?year=2023 # 東京都だけ curl http://localhost:8000/prefectures/R13000?year=2023 # ヘッダ付き(認証) curl -H 'Authorization: Bearer YOUR_TOKEN' \ http://localhost:8000/prefectures # JSON POST curl -X POST -H 'Content-Type: application/json' \ -d '{"code":"R48000","name":"新県"}' \ http://localhost:8000/prefectures # gzip 受信 curl -H 'Accept-Encoding: gzip' \ -o data.json.gz http://localhost:8000/prefectures # ETag 付き条件 GET curl -H 'If-None-Match: 0fdbe5f603bb' \ -v http://localhost:8000/datasets/SSDSE-B-2026.csv # ヘッダのみ取得 (HEAD) curl -I http://localhost:8000/datasets/SSDSE-B-2026.csv # 詳細ログ付き curl -v http://localhost:8000/prefectures # 並列 47 リクエスト (xargs) seq 1 47 | xargs -n1 -P10 -I{} curl -s \ "http://localhost:8000/prefectures/R"{}"000" |
curl は API デバッグの最強ツール。 ブラウザでも Postman でも再現できない場合の最後の砦。
| やりたいこと | 使う技術 | 1 行サンプル |
|---|---|---|
| HTTP GET(同期) | requests | requests.get(url, timeout=5).json() |
| HTTP GET(非同期) | httpx + asyncio | await httpx.AsyncClient().get(url) |
| JSON POST | requests | requests.post(url, json={...}) |
| 認証付き | headers | headers={'Authorization': f'Bearer {token}'} |
| クエリパラメータ | params | params={'year': 2023, 'limit': 47} |
| タイムアウト | timeout | timeout=5 |
| セッション再利用 | Session | s = requests.Session(); s.get(url) |
| JWT 生成 | PyJWT | jwt.encode(payload, key, algorithm='HS256') |
| JWT 検証 | PyJWT | jwt.decode(token, key, algorithms=['HS256']) |
| FastAPI 起動 | uvicorn | uvicorn main:app --reload |
| OpenAPI 自動生成 | FastAPI | 起動後 /docs アクセス |
| API テスト | pytest + httpx | client.get('/prefectures').json() |
| レート制限 | slowapi | @limiter.limit('5/minute') |
| キャッシュ | fastapi-cache | @cache(expire=3600) |
| 負荷試験 | locust | locust -f load.py --host http://localhost:8000 |
| JSON スキーマ検証 | pydantic | class Pref(BaseModel): code: str |
| API | 提供元 | 認証 | 用途 |
|---|---|---|---|
| e-Stat API | 総務省統計局 | appId (無料登録) | SSDSE-B-2026 等の政府統計取得 |
| RESAS API | 内閣府地方創生 | API キー (無料登録) | 都道府県別経済・人口可視化 |
| 気象庁防災情報 XML | 気象庁 | 不要 | 天気・地震情報 |
| OpenStreetMap API | OpenStreetMap | 不要(軽量利用) | 地図情報・ジオコーディング |
| 政府 CIO データセット | デジタル庁 | 不要 | オープンデータカタログ |
| OpenWeatherMap | OpenWeather Ltd. | API キー (無料層あり) | 世界の気象データ |
| 世界銀行 API | World Bank | 不要 | 国別経済指標 |
| OECD API | OECD | 不要 | 国際比較統計 |
SSDSE-B-2026 を主データとして、 これらの公開 API で「天気との関係」「世界との比較」「地理情報マッピング」など多次元拡張ができる。
「API」と一口に言っても、 通信プロトコル・データフォーマット・スキーマ規約によって性質が大きく違う。 SSDSE-B-2026 を提供する想定で 3 方式を比較する。
| 方式 | 通信 | データ形式 | スキーマ | 適する用途 |
|---|---|---|---|---|
| REST | HTTP + URL | JSON / XML | OpenAPI (任意) | 汎用 Web API / 公開データ |
| GraphQL | HTTP POST 1 エンドポイント | JSON | 必須 (SDL) | 複雑データ / モバイル |
| gRPC | HTTP/2 + Protobuf | バイナリ | 必須 (.proto) | マイクロサービス間 / 高速通信 |
API のレイテンシは $T = T_{net} + T_{srv} + T_{db}$ で構成される。 $T_{net}$ はネットワーク、 $T_{srv}$ はサーバ処理、 $T_{db}$ はデータベース。 GraphQL は 1 リクエストで複数リソースを取れるので $T_{net}$ が削減できる一方、 サーバ側で N+1 問題が起きやすい。 gRPC は Protobuf のシリアライズが速く $T_{srv}$ が小さい。
FastAPI で SSDSE-B-2026 の都道府県データを返す REST API を作り、 requests で取得する。 都道府県名で絞り込み・人口でソートできる。
📥 入力データ: SSDSE-B-2026.csv の都道府県別人口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
📤 実行例:
💬 結果の読み方: FastAPI は Type Hint から自動で OpenAPI スキーマ (Swagger UI) を生成する。 http://localhost:8000/docs でブラウザから対話的にテスト可能。 公開 API として運用するなら認証 (OAuth2 / API Key) と rate limiting (slowapi) を必ず追加する。
| 方式 | 仕組み | 使う場面 |
|---|---|---|
| API Key | ヘッダ or クエリで固定トークン | 公開データ API / 低リスク |
| OAuth 2.0 | 認可サーバ経由でアクセストークン発行 | ユーザー代理アクセス (SNS 連携) |
| JWT | 署名付きトークンで自己完結 | マイクロサービス間 / モバイル |
| mTLS | クライアント証明書で相互認証 | B2B API / 高セキュリティ |
OpenAPI 3.x は API の仕様を YAML/JSON で記述する標準。 FastAPI / Spring Boot / Flask-RESTX 等から自動生成できる。 一度書けば Swagger UI で対話的ドキュメント、 OpenAPI Generator でクライアント SDK (Python / TypeScript / Go) を作れる。 SSDSE-B-2026 のような公開データ API を学外で使ってもらう際は、 OpenAPI 仕様を一緒に公開すると採用率が大きく上がる。
ここでは API(Application Programming Interface)を、 単に「他システムとデータをやり取りする URL」として暗記するのではなく、 契約(インタフェース定義)・転送(HTTP / メッセージ)・制御(認証・rate limit・冪等性)・進化(バージョン管理)という 4 軸で再構成する。 公的データの代表として SSDSE-B-2026(47 都道府県の社会経済指標)を用い、 同じデータに対して REST / GraphQL / gRPC / OData の 4 形態の API を提示・比較する。 そのうえで、 公開 API の運用に必須となるレート制限・キャッシュ・エラーモデルの実装を Python で具体的に確認する。
API は「URL を叩けばデータが返る箱」ではない。 設計と運用で意識すべき軸は次の 4 つだ。 これらを区別して学べば、 REST / GraphQL / gRPC の選定や、 公開後の障害切り分けが体系的にできるようになる。
軸 1:契約。 API は「サーバとクライアントの間に結ばれる契約」である。 リクエスト形式(メソッド・パス・パラメータ・ボディスキーマ)と、 レスポンス形式(ステータスコード・JSON 構造・エラー形式)を 明示的かつ機械可読 に定義したものが OpenAPI 3.1 / GraphQL SDL / gRPC の .proto である。 契約を仕様書(PDF)でしか共有しない API は、 必ずクライアント実装と乖離して壊れる。
軸 2:転送。 何の上でメッセージを運ぶか。 REST / GraphQL は HTTP/1.1 か HTTP/2 上、 gRPC は HTTP/2 必須(双方向ストリーミングのため)、 WebSocket / SSE はリアルタイム通知向け。 転送層が変われば、 タイムアウト・再送・接続プーリングの設計も丸ごと変わる。 SSDSE-B-2026 のような 静的な公開データ なら HTTP/1.1 + Cache-Control で十分、 取引・チャット のようにステートフルな流れがあれば gRPC や WebSocket を選ぶ。
軸 3:制御。 API の前段で「誰が」「どの程度」「何回まで」叩けるかを制御する。 認証(OAuth 2.1 + PKCE / API Key / mTLS)、 認可(スコープ / RBAC / ABAC)、 レート制限(Token Bucket / Leaky Bucket)、 冪等性(Idempotency-Key ヘッダ)、 キャッシュ(ETag + If-None-Match)。 制御の弱い API は 必ず 業務外負荷で落ちる。
軸 4:進化。 公開した API は壊さずに進化させなければならない。 URI 版数(/v1/ → /v2/)、 ヘッダ版数(Accept: application/vnd.example.v2+json)、 GraphQL の @deprecated、 gRPC の field number 維持。 「破壊的変更(breaking change)」を識別し、 旧クライアントを少なくとも 6〜12 ヶ月走らせる移行戦略を最初から組み込む。
| スタイル | 契約定義 | 転送 | 取得粒度 | 代表的用途 |
|---|---|---|---|---|
| REST + JSON | OpenAPI 3.x | HTTP/1.1 or 2 | リソース単位 | 公開 API(汎用) |
| GraphQL | GraphQL SDL | HTTP POST | クエリで指定 | 複数リソース集約・モバイル |
| gRPC | Protocol Buffers | HTTP/2 | RPC 単位 | マイクロサービス間 |
| OData | CSDL (XML) | HTTP | $filter / $select | エンタープライズ DB 公開 |
| WebSocket / SSE | AsyncAPI | HTTP Upgrade | イベント単位 | リアルタイム通知 |
| JSON-RPC 2.0 | 独自仕様 | HTTP / WS | メソッド単位 | ブロックチェーン RPC |
読み方:「REST → GraphQL → gRPC」の流れは、 軸 1(契約の厳密度)と軸 4(進化の制御)が高度化していく方向。 公開 API は REST、 内部は gRPC、 BFF(フロントエンド集約層)は GraphQL という棲み分けが現実解になりがちだ。
API の挙動は「リクエスト→レスポンスの分布」「ペイロードサイズの分布」「エンドポイント別の応答時間のばらつき」の 3 視点で観察すると、 ボトルネックや SLO 設計が腹落ちする。 ここでは SSDSE-B-2026 の都道府県データを返す REST API(FastAPI + uvicorn)に負荷をかけた前提で、 観測すべき分布形を視覚化する。
上の散布図のような相関が API のメトリクスに現れたら、 (1) サーバ側で ?fields=都道府県,A1101 のような部分応答機能を追加、 (2) クライアント側で Accept-Encoding: gzip を必ず送る、 (3) ETag + 条件付き GET で 304 を返せる経路を整備する、 という 3 段構えで対処できる。 GraphQL ではクライアントが必要なフィールドだけ宣言するので、 この問題は構造的に小さくなる。
API の SLO(Service Level Objective)は「平均 200ms 以下」では不十分で、 「p99 で 500ms 以下」「p95 で 250ms 以下」のように テール を明示する。 ヒストグラムから読むべきは 中心ではなく裾 であり、 p99 を悪化させるのは GC・DB ロック・コネクションプール枯渇のいずれかであることが多い。
GROUP BY を含む /stats/by-region)は明らかに中央値も IQR も大きいので、 別ノードへの分離・キャッシュ・事前計算が必要。
箱ひげ図は エンドポイント別に分けてメトリクスを見る ための定番ツールである。 「全部混ぜた p95」では分からない遅さの根が、 エンドポイント別に切り直すと「集計系だけが遅い」「ペイロード大きい単件取得が外れ値を出している」と即特定できる。 Prometheus + Grafana では histogram_quantile(0.95, sum by (le, route) (rate(http_request_duration_seconds_bucket[5m]))) のようにルートでラベル分けするのが定石である。
Token Bucket は「毎秒 $r$ トークンを上限 $b$ 個まで補充し、 リクエストごとに 1 トークン消費する」モデル。 トークンが枯渇したら 429 Too Many Requests を返す。
$$T(t) = \min(b,\ T(t-\Delta t) + r \cdot \Delta t) - n_{\text{request}}(t)$$
クライアント側の指数バックオフは、 $k$ 回目の再試行で待機時間を $\tau_k$ とすると、 ジッタ付きで次のように決める。
$$\tau_k = \min(\tau_{\max},\ \tau_0 \cdot 2^{k}) \cdot U(0,1)$$
ここで $U(0,1)$ は一様乱数(ジッタ)。 ジッタを掛けないと、 複数クライアントが同期して同時刻に再試行してしまい、 サーバが二次的に落ちる「サンダーリングハード」が発生する。 AWS SDK の標準実装も full jitter($\tau_k \sim U(0, \tau_0 \cdot 2^k)$)を採用している。
| コード | 意味 | 主な原因 | クライアント側の対応 |
|---|---|---|---|
| 200 OK | 成功 | 通常応答 | ボディを使う |
| 201 Created | 作成成功 | POST で新規生成 | Location ヘッダを保持 |
| 204 No Content | 成功・本文なし | DELETE 成功 | ボディを読まない |
| 301 / 308 | 恒久リダイレクト | URI 変更 | URL を更新(キャッシュ) |
| 304 Not Modified | 変更なし | ETag 一致 | キャッシュを使う |
| 400 Bad Request | クライアント不正 | バリデーションエラー | 入力修正・再試行しない |
| 401 Unauthorized | 認証必要 | トークン無効・期限切れ | refresh して 1 回再試行 |
| 403 Forbidden | 権限なし | スコープ不足 | 再試行しない・運用に連絡 |
| 404 Not Found | 資源なし | ID 誤り | エラー表示・キャッシュ |
| 409 Conflict | 状態競合 | 楽観ロック違反 | 最新状態取得・再計算 |
| 422 Unprocessable | 意味的不正 | JSON 構文 OK・値が不正 | 入力修正・再試行しない |
| 429 Too Many Req | レート制限 | バケット枯渇 | Retry-After に従う |
| 500 Internal Error | サーバ例外 | 未捕捉エラー | 指数バックオフで再試行 |
| 503 Service Unavail | 過負荷・停止 | デプロイ・スケール中 | 指数バックオフで再試行 |
| 504 Gateway Timeout | タイムアウト | 上流応答遅延 | クエリ縮小・再試行 |
| メソッド | 安全 | 冪等 | キャッシュ可 | 主な用途 |
|---|---|---|---|---|
| GET | ○ | ○ | ○ | 取得 |
| HEAD | ○ | ○ | ○ | メタ取得 |
| OPTIONS | ○ | ○ | × | CORS preflight |
| PUT | × | ○ | × | 全置換 |
| DELETE | × | ○ | × | 削除 |
| POST | × | × | × | 新規作成・任意処理 |
| PATCH | × | △* | × | 差分更新 |
*PATCH の冪等性は 実装次第。 「カウンタ +1」は非冪等だが「フィールド値の置換」は冪等。 RFC 5789 はどちらも許容しているので、 仕様書に明記する必要がある。 POST は冪等でないため、 ネット切断後の再送が二重作成を起こす。 これを防ぐのが Idempotency-Key ヘッダ(Stripe / GitHub / AWS API Gateway 等が採用)。
SSDSE-B-2026 を返す公開 API を例に、 数値で運用パラメータを設計してみる。 想定: 認証なしの公開 API で 毎分 60 リクエスト・1 リクエスト平均 12 KB。 利用者は最大 100 IP 同時とする。 月間の総転送量は次のように見積もる。
| 項目 | 式 | 値 |
|---|---|---|
| 秒あたりレート | 60 / 60 | 1 req/s/IP |
| 最大同時 req/s | 1 × 100 | 100 req/s |
| バケット容量 b | 瞬間最大 10 req | b = 10 |
| 月間 req 数 | 100 × 86400 × 30 | 2.59 億 |
| 月間転送量 | 2.59 億 × 12 KB | 3.11 TB |
| CloudFront 料金概算 | $0.085/GB × 3110 GB | $264 / 月 |
結論: 転送量が支配的 なので、 (1) gzip / brotli 必須、 (2) ETag + 304 で再送を抑制、 (3) ?fields=... による部分応答、 (4) よく使う集計エンドポイントは S3 静的 JSON にして CDN 側で完結、 のセットで月額を 1 桁切れる。
このコードでやること:FastAPI で公開された SSDSE-B-2026 の REST API を、 requests.Session と urllib3.Retry で指数バックオフ付きで叩き、 47 都道府県のデータを取得する。
📥 入力データ(SSDSE-B-2026 抜粋、 API レスポンス想定):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | # 公開された FastAPI エンドポイントを安全に叩く import requests from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry session = requests.Session() retry = Retry( total=5, backoff_factor=0.5, # 0.5, 1.0, 2.0, 4.0, 8.0 秒 status_forcelist=(429, 500, 502, 503, 504), allowed_methods=frozenset(['GET']), respect_retry_after_header=True, ) session.mount('https://', HTTPAdapter(max_retries=retry)) URL = 'https://api.example.org/v1/ssdse-b/prefectures' resp = session.get(URL, params={'fields': '都道府県,A1101'}, timeout=(3, 10)) resp.raise_for_status() data = resp.json() print(f'件数: {len(data)}, 1 件目: {data[0]}') print(f'残レート: {resp.headers.get("X-RateLimit-Remaining")}') |
📤 実行例(実 API が動いていれば次のように返る):
💬 結果の読み方: Retry は 429 と 5xx のみ自動再試行し、 4xx(クライアント不正)は再試行しない。 respect_retry_after_header=True で Retry-After ヘッダの秒数に従う。 timeout=(3, 10) は接続 3 秒・読み取り 10 秒で、 これを設定し忘れると 無限に ハングする。
このコードでやること:FastAPI に slowapi(Token Bucket 実装)でレート制限を組み込み、 さらに RFC 7807 Problem Details 形式のエラーレスポンスを統一する。
📥 入力データ(SSDSE-B-2026 CSV、 抜粋):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | # FastAPI + slowapi (Token Bucket) + RFC 7807 Problem Details import pandas as pd from fastapi import FastAPI, Request, HTTPException from fastapi.responses import JSONResponse from slowapi import Limiter from slowapi.util import get_remote_address limiter = Limiter(key_func=get_remote_address) app = FastAPI(title='SSDSE-B-2026 API v1') app.state.limiter = limiter df = pd.read_csv('data/raw/SSDSE-B-2026.csv', skiprows=1, encoding='cp932') @app.exception_handler(HTTPException) async def problem_details(_, exc: HTTPException): # RFC 7807 形式 return JSONResponse( status_code=exc.status_code, media_type='application/problem+json', content={ 'type': f'https://api.example.org/errors/{exc.status_code}', 'title': exc.detail, 'status': exc.status_code, } ) @app.get('/v1/prefectures') @limiter.limit('60/minute') # 1 IP あたり毎分 60 req def list_prefs(request: Request): return df[['都道府県', 'A1101']].to_dict(orient='records') @app.get('/v1/prefectures/{name}') @limiter.limit('60/minute') def get_pref(request: Request, name: str): row = df[df['都道府県'] == name] if row.empty: raise HTTPException(status_code=404, detail=f'prefecture not found: {name}') return row.iloc[0].to_dict() |
📤 実行例(curl での確認):
💬 結果の読み方: 70 回連打すると 60 回までは 200、 残り 10 回は 429 になる(毎分 60 リクエスト制限)。 エラーボディは RFC 7807 形式に統一されており、 クライアントは title をユーザー表示に、 type URI を機械処理に使える。
このコードでやること:SSDSE-B-2026 のような 更新頻度が低いデータ に対して、 ETag(コンテンツのハッシュ)を発行し、 クライアントが If-None-Match を付けて再取得した時は 304 Not Modified を返す。 ペイロード本体を返さないので転送量がほぼゼロになる。
📥 入力データ(SSDSE-B-2026 全 47 都道府県、 ハッシュ計算対象):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | # FastAPI で ETag + If-None-Match を扱う import hashlib, json import pandas as pd from fastapi import FastAPI, Header, Response app = FastAPI() df = pd.read_csv('data/raw/SSDSE-B-2026.csv', skiprows=1, encoding='cp932') def make_payload(): return df[['都道府県', 'A1101']].to_dict(orient='records') @app.get('/v1/prefectures') def list_prefs(response: Response, if_none_match: str | None = Header(default=None)): payload = make_payload() body = json.dumps(payload, ensure_ascii=False).encode('utf-8') etag = '"' + hashlib.sha256(body).hexdigest()[:16] + '"' response.headers['ETag'] = etag response.headers['Cache-Control'] = 'public, max-age=3600' if if_none_match == etag: response.status_code = 304 return Response(status_code=304, headers={'ETag': etag}) return Response(content=body, media_type='application/json', headers={'ETag': etag, 'Cache-Control': 'public, max-age=3600'}) |
📤 実行例(初回は 200 + ボディ、 2 回目は 304 でボディなし):
💬 結果の読み方: 2 回目以降は content-length: 0 なのでペイロードがゼロ。 月間 2.59 億リクエストの想定では、 99% がキャッシュヒットすれば転送量を 3.11 TB → 約 30 GB(ヘッダのみ)に圧縮できる。 ETag は 強い ETag と 弱い ETag(W/"...")があり、 後者はバイト単位一致ではなく意味的一致でよい。
このコードでやること:SSDSE-B-2026 API を 47 回連続で叩き(各都道府県を 1 回ずつ)、 応答時間を測定。 numpy.percentile で p50 / p95 / p99 を算出し、 SLO(p99 < 500 ms)を満たしているか判定する。
📥 入力データ(SSDSE-B-2026 の 47 都道府県名リスト):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | # 47 都道府県を順に叩いて応答時間のパーセンタイルを計算 import time import requests import numpy as np import pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', skiprows=1, encoding='cp932') prefs = df['都道府県'].dropna().unique().tolist() # 47 件 URL_BASE = 'https://api.example.org/v1/ssdse-b/prefectures/' latencies_ms = [] for name in prefs: t0 = time.perf_counter() r = requests.get(URL_BASE + name, timeout=(3, 10)) dt = (time.perf_counter() - t0) * 1000.0 # ms if r.status_code == 200: latencies_ms.append(dt) arr = np.array(latencies_ms) print(f'N = {len(arr)}') print(f'p50 ms = {np.percentile(arr, 50):.1f}') print(f'p95 ms = {np.percentile(arr, 95):.1f}') print(f'p99 ms = {np.percentile(arr, 99):.1f}') print(f'max ms = {arr.max():.1f}') print(f'SLO ok? = {np.percentile(arr, 99) < 500}') |
📤 実行例:
💬 結果の読み方: p50 が 38 ms に対して p99 は 188 ms と 約 5 倍 開いている。 これが「テール(裾)」の正体で、 平均だけ見ていると見えない遅さの源だ。 p99 が SLO(500 ms)より速ければ運用上 OK。 もし悪化したら Prometheus の histogram_quantile でエンドポイント別に切り分け、 図 R645-C のような箱ひげ図でボトルネックを特定する。
requests.Session() や HTTP/2 で Keep-Alive を効かせる。response.iter_content(chunk_size=8192) で逐次処理。asyncio + httpx.AsyncClient または GraphQL でバッチ化する。application/x-www-form-urlencoded として解釈し、 JSON ボディが壊れる。 requests.post(url, json=...) が安全。200 OK で返す: 「200 OK + {"error": "..."}」は監視ツールに気づかれない。 必ず適切な 4xx/5xx を返す。/getUsers ではなく /users(GET)。 動詞は HTTP メソッドで表現するのが REST の規約。API
├── リソース指向
│ ├── REST (HTTP + JSON) — リソース URI、 メソッドで CRUD
│ ├── HATEOAS — レスポンスに次の遷移リンクを含める
│ └── OData — REST + クエリ言語($filter, $select)
├── クエリ指向
│ ├── GraphQL — クライアントがフィールドを宣言
│ └── SPARQL — RDF / 知識グラフ向け
├── RPC 指向
│ ├── gRPC (HTTP/2 + protobuf) — マイクロサービス間の高速通信
│ ├── JSON-RPC 2.0 — ブロックチェーンノード等
│ └── tRPC — TypeScript 専用、 型共有
├── イベント指向
│ ├── WebSocket — 双方向のリアルタイム通信
│ ├── Server-Sent Events (SSE) — サーバ → クライアントの単方向
│ └── Webhook — サーバ → サーバの通知
└── 仕様駆動の周辺
├── OpenAPI 3.1 — REST の機械可読仕様
├── AsyncAPI — イベント API の機械可読仕様
└── JSON Schema — リクエスト/レスポンスのバリデーション
Q1. SSDSE-B-2026 の都道府県データを返す公開 REST API で、 GET /v1/prefectures を毎分 60 リクエストに制限したい。 適切な HTTP ステータスコードは?
正解: (c) 429。 (b) はスコープ不足、 (d) は過負荷・停止。 Retry-After ヘッダで「何秒後に再試行可能か」を明示するのが定石。
Q2. SSDSE-B-2026 API への POST /v1/prefectures(新規作成)でネット切断による再送が発生した時、 二重作成を防ぐ仕組みは?
If-Match ヘッダIdempotency-Key ヘッダX-Request-ID ヘッダAuthorization ヘッダ正解: (b)。 サーバは Idempotency-Key を一定期間保持し、 同じキーで再受信したら以前のレスポンスを返す(処理は重複実行しない)。 Stripe / AWS API Gateway が採用。
Q3. SSDSE-B-2026 API で「47 都道府県の全件を返すエンドポイント」の応答時間を改善したい。 最も効果が大きい順に並べると?
正解: (a)。 ETag + 304 はキャッシュヒット時 本文ゼロ。 次に gzip で 60〜80% 圧縮、 最後に部分応答で残りを削る。 (d) は単発の応答時間を改善せず、 月額だけ倍になる悪手。
Q4. SSDSE-B-2026 API を v2 に進化させ、 都道府県 フィールドを name にリネームしたい。 旧クライアントを壊さずに移行する方法として不適切なのは?
/v1/ を 12 ヶ月維持し、 /v2/ で新フィールドを公開name と 都道府県 の両方を含めるAccept: application/vnd.example.v2+json でバージョン分岐正解: (d)。 即廃止は破壊的変更そのもの。 (a)〜(c) はいずれも互換性を保ちつつ進化させる正攻法。 公開後の API は「壊れたら 1 か月かけて移行する」ではなく「壊さずに長期共存」が原則。
API は「外部公開のためのインタフェース」だが、 その背後には プロトコル・認証・アクセス管理・メタデータ・データガバナンス・e-Stat など多くの概念が連動する。 公開 API を 1 本動かすことは、 結局これらすべてを薄く実装することと等しい。
api を Python で実装する代表的なコードを示す。 標準ライブラリ (numpy / pandas / scipy / sklearn / statsmodels) を使い、 SSDSE-B-2026 を読み込んで実行する流れを再現できる。
🎯 このコードでやること: api を計算するための最小限のコード。 入力データ・処理・出力・結果の読み方を明示する。
📥 入力データ: SSDSE-B-2026 (47 都道府県 × 100 超列) の CSV。
1 2 3 4 | import pandas as pd
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=2)
# api を計算
print(df.head())
|
📤 実行結果: 出力された数値を読み、 他手法との比較や有意性検証を行う。
💬 結果の読み方: api の出力は単なる数値ではなく、 データの構造や規則性を要約したもの。 適切な解釈と他指標との併用が重要。
下のコンソールは、 SSDSE-B-2026 の実測値を返す模擬 REST API サーバをブラウザ内の JavaScript だけで再現したもの(実際のネットワーク通信は一切発生しない完全オフライン動作)。 エンドポイントとクエリパラメータを UI で組み立てるとリクエスト URL がリアルタイム生成され、 「🚀 送信」でクライアント⇄サーバの往復がアニメーション表示される。 200 だけでなく 404(未知の県)・400(不正パラメータ)・429(レート超過)も体験できる。
GET …
(まだリクエストがありません)
数値はすべて SSDSE-B-2026(総人口 A1101・出生数 A4101、 2012〜2023 年、 8 都道府県分を抜粋、 cp932 / skiprows=[1] で読込・検算済み)の実測値。 例: 東京都 2023 年の総人口 14,086,000 人、 島根県 2012 年の出生数 5,585 人。
コンソールの各部品はレストランの比喩にそのまま対応する。 エンドポイント一覧 = メニュー表(何を注文できるかの公開仕様)、 URL + クエリパラメータ = 注文票(「/population の 東京都・2023 年を 1 つ」)、 JSON レスポンス = 規定の器で出てくる料理。 404 は「品切れ(そんな県はメニューにない)」、 400 は「注文票の書き間違い(year 欄に文字を書いた)」、 429 は「お客様、 ご注文が早すぎます(厨房のキャパ超過)」である。 注目すべきは、 注文票(URL)1 枚に必要な情報がすべて載っていること ── サーバは前回の注文を覚えていない(ステートレス)ので、 year を変えるたびに完全な URL を送り直す。 上のコンソールで year スライダを動かすと URL が毎回丸ごと作り直されるのは、 この性質の可視化である。
REST 流の設計では、 上のコンソールの /population?pref=R13000 は「リソース(人口データ)を URL で名指しし、 操作は HTTP メソッドで表す」という規約に従っている。 今回は取得(GET)だけだが、 同じ URL 体系のまま POST(追加)・PUT(更新)・DELETE(削除)へ拡張できるのが REST の強みである。
ページネーション: 今回は 1 リクエスト = 1 件だったが、 「47 都道府県 × 12 年 = 564 件を全部くれ」という要求に対し、 実際の API は limit=100&offset=200 や cursor=eyJ5… のように小分けにして順に取らせる。 「📦 全年度を一括取得」ボタンでやったループが、 まさにページを繰りながら DataFrame を組み立てる作業の縮図である。
requests での取得: コンソールで体験した「URL 組み立て → 送信 → ステータス確認 → JSON → 表」を Python にするとこうなる(429 対応の指数バックオフ付き)。 取得後は JSON を pandas.DataFrame に整形し、 データクレンジングを経て CSV 保存、 という流れが定番(原データの所在は e-Stat・オープンデータ参照)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | import time, requests, pandas as pd BASE = "https://api.example.go.jp/api/v1" # 模擬URL。実物は e-Stat API 等 def fetch(endpoint, pref, year, retries=3): for i in range(retries): r = requests.get(BASE + endpoint, params={"pref": pref, "year": year}, timeout=5) # タイムアウト必須 if r.status_code == 429: # レート超過なら time.sleep(2 ** i) # 指数バックオフ continue r.raise_for_status() # 4xx/5xx は例外に return r.json() raise RuntimeError("リトライ上限に到達") rows = [] for y in range(2012, 2024): # ページを繰るループ rows.append(fetch("/population", "R13000", y)) time.sleep(0.7) # スロットリング df = pd.json_normalize(rows) # JSON → 表 print(df[["query.year", "value"]]) # 東京都 2023 年は 14086000 |
requests.get(url) はデフォルトで無限待機。 必ず timeout=5 等を指定。verify=False は中間者攻撃にさらされる。 開発時のみ、 本番では絶対使わない。python-requests/X.Y をブロック。 アプリ識別子と連絡先を必ず付ける。{"detail": "DB connection lost"} 等の情報が入る。 ログに記録せよ。Roy Fielding が 2000 年に博士論文で定義した REST の 6 つの原則を、 SSDSE-B-2026 API を例に確認する。
year パラメータを毎回送る。Cache-Control: public, max-age=31536000(1 年キャッシュ)が妥当。 翌年度に更新されたら ETag で 304 を返す。/prefectures/{code} のような URL パターンを全リソースで統一。| URL | メソッド | 意味 |
|---|---|---|
| /api/v1/prefectures | GET | 47 県の一覧 |
| /api/v1/prefectures/R13000 | GET | 東京都の詳細 |
| /api/v1/prefectures/R13000/years | GET | 東京都の全年度 |
| /api/v1/prefectures/R13000/years/2023 | GET | 東京都 2023 年のデータ |
| /api/v1/years/2023/aggregates | GET | 2023 年度の全 47 県集計 |
| /api/v1/years/2023/aggregates?metric=population&top=10 | GET | 2023 年度人口上位 10 県 |
| /api/v1/datasets/SSDSE-B-2026/checksum | GET | CSV の SHA-256 ダイジェスト |
「api」を中心とした関連概念マップ。
中央の API ノードから伸びる 4 本の線で、 通信スタイル (REST/GraphQL/gRPC)、 認証 (OAuth/API Key)、 応用領域 (Web/モバイル/IoT)、 派生形式 (Webhook) の関係を示した。
SSDSE-B-2026 を取得する想定で読み解くと、 (a) 上位概念は HTTP プロトコルと URI 設計、 (b) 並列手法は REST/GraphQL/gRPC で、 政府統計の単純な「都道府県 × 年 × 指標」取得には REST が最適、 (c) 派生は Webhook (新データ公開を購読) と Server-Sent Events、 (d) 前段は API Key 発行と OAuth 認可、 (e) 後段は JSON パース、 DataFrame 化、 キャッシュ戦略、 (f) 応用領域はダッシュボード自動更新、 ML 推論サーバ、 公開統計の連携基盤。
e-Stat API で「東京都の人口」を取得する流れを概念マップに重ねれば、 単なる「データ取得手段」を超え、 認証→クエリ→レスポンス→パース→可視化までの一連の依存関係が見える。 これが SSDSE-B-2026 を扱う際の API 知識の実利である。
API は「データへのアクセス契約」であり、 認証→クエリ→レスポンス→分析の各段で隣接技術と連携する。
SSDSE-B-2026 を e-Stat API 経由で取得する場合、 API キー認証 → URL に都道府県=13 のクエリ → JSON 取得 → pandas で集計、 という流れが標準パイプライン。
API スタイルの選択は「データ形状」「通信モデル」「型保証」の 3 軸で判定する。
初期は REST で始め、 N+1 問題やリクエスト集約の必要が見えた段階で GraphQL、 マイクロサービス内通信に gRPC、 という段階的選択が現代の標準。
API(Application Programming Interface=アプリケーションプログラミングインターフェース)は「プログラム同士がデータをやり取りする約束事の窓口」。このページの他セクションはレート制限・ページング・認証を扱ったので、ここでは「返ってきた値を検算するまで信じない」という別角度に絞る。題材は e-Stat 風 API で SSDSE-B-2026(総人口 A1101)を取得する場面。
API のレスポンスは「梱包された荷物」だと思うとよい。伝票(HTTP ステータス・Content-Type)と中身(JSON の値)はセットで届くが、中身は開けて数えるまで正しいとは限らない。とくに公的統計 API では、数値が JSON の中で "14086000" のように文字列(クオート付き)で入っている。見た目は数字でも型は文字列なので、そのまま + すると足し算ではなく連結になる。荷物を「開けて(=型変換して)」「数えて(=検算して)」初めて信頼できる。
total += v(v="14086000")とすると TypeError、JavaScript だと "14086000"+"8763000" が "140860008763000" という無意味な連結になる。必ず int() / Number() で数値化してから合計する。下の発展のデモで実際に連結が起きる様子を確認できる。int() が落ちる。 統計 API は空欄を "-"(該当なし)・"***"(秘匿)・"X" などの記号で返すことがある。int("-") は例外。事前に pd.to_numeric(..., errors="coerce") や記号除外を挟まないと、47 県のうち 1 県で全処理が止まる。SSDSE-B-2026 の東京都・総人口 A1101 の時系列を見ると、2015 年=13,515,271 人・2020 年=14,047,594 人だけが 1 人単位(国勢調査の実測値)で、他の年(2023=14,086,000、2022=14,038,000 …)は末尾が 000 の千人単位の推計。「なぜ末尾が 000 ばかりなのか」を疑わずに前年差を取ると、実測年だけ段差が出る。API の値は粒度(有効桁)が均一とは限らない。ページングで 47 県ぶんを集めたら、最後に既知合計で assert するのが定石。値は文字列で届く前提で int() を必ず通す。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import requests KNOWN_TOTAL = 124_353_000 # SSDSE-B-2026 2023年・47県・総人口(A1101) の実測合計 def fetch_all(pref_codes, url, year=2023): total, missing = 0, [] for code in pref_codes: # 47県ぶんページング r = requests.get(url, params={"pref": code, "year": year}) r.raise_for_status() v = r.json()["value"] # e-Stat系は文字列 "14086000" で返る if v in ("-", "***", "X"): # 欠損・秘匿コードは弾く(罠②) missing.append(code); continue total += int(v) # str のまま + すると連結になる(罠①) assert total == KNOWN_TOTAL, f"検算不一致: {total:,} != {KNOWN_TOTAL:,}({missing} が欠落?)" return total # 124,353,000 に一致すれば取りこぼし無し |
下は「文字列のまま足す」と「数値化して足す」の違いを、実データ(2023 年・各県の総人口 A1101)で見るミニデモ。ボタンで別の県の組に切り替わる(シード付き擬似乱数で再現可能)。
(ボタンを押すと結果が出ます)