データを取ってくるコードは、最初の1行が一番簡単です。`requests.get(url).json()` と書けば、たいていのデータは手に入ります。問題はそのあとで、相手が落ちていたとき、件数が想定の100倍あったとき、同じものを明日また取りたくなったときに、最初の1行では足りなくなります。
後から足すのは大変です。取得部分は他の処理から呼ばれるので、あとで引数が増えたり例外の種類が変わったりすると、呼び出し側も全部直すことになります。最初に決めておけば済む話が、動いてしまったあとだと数日の手戻りになる。この回では、書き始める前に決めておく5つを挙げます。
1. タイムアウトを必ず書く
が返ってこないまま、いつまでも待ち続けるのが、いちばん厄介な失敗です。エラーで落ちてくれれば気づけますが、待ち続けるコードは黙って止まっているだけなので、翌朝まで誰も気づきません。
Python の requests は、タイムアウトを指定しないと無期限で待ちます。これは既定値としては危険な側に倒れているので、書く側が毎回指定する必要があります。接続までの待ちと、応答本体の待ちは分けて指定できます。
import requests
# (接続タイムアウト, 読み取りタイムアウト) の順resp = requests.get( "https://example.com/api/items", timeout=(5, 30), headers={"User-Agent": "hukuhuku-data-fetch/1.0 (+https://hukuhuku.co.jp)"},)resp.raise_for_status() # 4xx / 5xx をここで例外にするdata = resp.json()requests は 404 でも 500 でも例外を投げません。`resp.json()` がたまたま成功して、エラーメッセージの JSON をデータとして処理してしまうことがあります。取得した瞬間に status を確認するのを習慣にしてください。
2. どの失敗をやり直すかを決める
通信は一定の確率で失敗します。を組み込むこと自体は当然として、やり直してよい失敗と、やり直しても無駄な失敗を分けておく必要があります。ここを分けずに全部やり直すと、認証が間違っているだけなのに何十回も叩き続けることになります。
| コード | 意味 | やり直す? |
|---|---|---|
| 429 | 要求が多すぎる | する。待ち時間を空けて |
| 500 / 502 / 503 / 504 | サーバ側の異常 | する |
| 408 | サーバ側でのタイムアウト | する |
| 401 / 403 | 認証・権限の問題 | しない。設定を直す |
| 404 | 存在しない | しない。URL を疑う |
| 400 / 422 | 要求の内容が不正 | しない。パラメータを直す |
待ち方も決めておきます。 — 1秒、2秒、4秒と間隔を倍にしていく方式が一般的です。ここに少しゆらぎを足すのが定石で、理由は次の回で扱います。
3. 名乗りを決める
を既定のままにしないでください。Python の requests は `python-requests/2.x` と名乗りますが、これを一律で拒否している配信元があります。逆に、公的機関の では「問い合わせ先が分かる名乗り」を求めていることがあります。
組織名と連絡先を含めておくと、相手が困ったときに連絡できます。過剰な取得をしてしまったときに、いきなり遮断されるかわりに一報が来る可能性が上がる、という実利もあります。
4. 並列度の上限を決める
速く取りたいので並列にする、というのは自然な発想ですが、上限を決めずに並列化すると相手のサーバを叩き潰します。無料で公開してくれている配信元に対してこれをやると、次に困るのは自分です。遮断されれば取得そのものができなくなります。
が明記されている場合はそれに従います。書いていない場合は、まず直列で試して、必要になってから上げるほうが安全です。「何並列まで大丈夫か」は相手にしか分からないので、こちらが遠慮する側に倒すのが基本になります。
5. 生のまま保存する場所を決める
取得したデータを、その場で加工して保存したくなります。要らない列を落とし、日付を変換し、必要な形にしてから書き出す。ところが後になって「落とした列が要る」「変換の仕方を間違えていた」と分かったとき、取り直しになります。相手が過去分を配信していなければ、取り直せません。
受け取ったものをそのまま置く場所と、加工したものを置く場所を分けてください。保存形式は生のままでよく、なら のままで構いません。分析用に整えたものは などに変換して別に置きます。
import jsonfrom datetime import datefrom pathlib import Path
RAW = Path("data/raw")
def save_raw(source: str, name: str, payload) -> Path: """受け取ったものを加工せずに残す。加工は別の層でやる。""" d = RAW / source / date.today().isoformat() d.mkdir(parents=True, exist_ok=True) path = d / f"{name}.json" path.write_text(json.dumps(payload, ensure_ascii=False), encoding="utf-8") return path相手が過去分をいつでも配信しているなら、生データを残す価値は下がります。逆に最新の状態しか返さないAPI(在庫、順位、板情報など)は、その時点の値を残さないと二度と手に入りません。
この5つを決めた状態から始める
タイムアウト、やり直す失敗の範囲、名乗り、並列度、生データの置き場所。どれも実装としては数行ですが、決めずに書き始めると後から入れ直すのが高くつく種類のものです。次回は、この中でも取りこぼしが起きやすいを扱います。
取得したデータをどう検証し、どう保存するかについては 壊れないデータ基盤の作り方 でも扱っています。この連載は、その手前の「取ってくる」部分だけを細かく見ていきます。
この記事の感想を教えてください
あなたの 1 クリックで、本当にこの記事は更新されます。「もっと詳しく」「続編希望」が一定数集まった記事は、 ふくふくが 実際に内容を拡充したり続編記事を公開 します。 送信したリアクションはお使いのブラウザに記録され、再カウントされません。