取得したデータは、そのまま使える形になっていることのほうが少ないです。列名が英語だったり、日付が文字列だったり、要らない項目が大量に付いてきたりします。だから取ってきた直後に整形したくなります。要る列だけ残して、型を直して、それを保存する。
この作りは、後から要件が変わった瞬間に破綻します。「やっぱりあの列も要る」「日付の変換を間違えていた」と分かったとき、手元には整形後のデータしか残っていません。配信元が過去分を出していれば取り直せますが、最新の状態しか返さないなら二度と手に入りません。
受け取ったものと、整えたものを分ける
対策は単純で、受け取ったものをそのまま置く場所()と、分析用に整えたものを置く場所を分けるだけです。整形の処理は、生データを入力として何度でも走らせられるようにしておきます。要件が変われば、整形だけをやり直します。
この考え方はや と呼ばれる設計と同じ発想です。先に溜めてから、あとで加工する。取得と加工を1つの処理にまとめると速く書けますが、変更に弱くなります。
生データ層の置き方
置き方の要点は3つです。いつ取ったか、どこから取ったか、受け取ったそのままか。この3つが分かれば、あとから何にでも使えます。フォルダを日付で切るのが最も単純な形で、これがにもなります。
日付で切っておくと、あとで範囲を限って読み直せます。「先月ぶんだけ整形をやり直す」「特定の日の取得が失敗していたので、その日だけ取り直す」といった操作が、フォルダを指定するだけで済みます。切らずに1つの場所へ積み上げると、全部を読まないと何もできなくなります。
ファイル名にも規則を持たせてください。取得の単位がそのまま名前になっていると、どのファイルが何に対応するかがコードを読まなくても分かります。逆に連番だけの名前にすると、後から追う手がかりが消えます。
import jsonfrom datetime import datetime, timezonefrom pathlib import Path
RAW = Path("data/raw")
def save_raw(source: str, name: str, payload, request_info: dict) -> Path: """受け取った本文と、それを取るのに使った条件を並べて残す。 条件が残っていないと、あとで「この数字はどの範囲か」が分からなくなる。""" fetched_at = datetime.now(timezone.utc) d = RAW / source / fetched_at.strftime("%Y-%m-%d") d.mkdir(parents=True, exist_ok=True)
path = d / f"{name}.json" path.write_text( json.dumps( { "fetched_at": fetched_at.isoformat(), "source": source, "request": request_info, # URL やパラメータ "payload": payload, # 受け取ったそのまま }, ensure_ascii=False, ), encoding="utf-8", ) return path本文だけ残すと、あとで「この数字はどの期間を指定して取ったのか」が分からなくなります。URL とパラメータを添えて残すと、再現も検証もできます。
整形は生データから何度でも
整形処理は、生データを読んで分析用の形を作るだけにします。この処理は何度走らせても同じ結果になるように書きます。そうしておけば、変換の誤りを見つけたときに整形だけ直して流し直せます。生データ側には決して書き戻さないでください。書き戻した瞬間に、元が何だったのか分からなくなります。
整形の途中で捨てる値についても、判断を残しておくと後が楽です。「この列は常に空だったので落とした」「この行は重複していたので片方を残した」。こうした判断は、書いた本人でも半年後には忘れます。コードのコメントでも、別の記録でも構いません。
保存形式は が扱いやすいです。列ごとに型を持ち、圧縮も効くので、より小さく速くなります。整形結果の検算には を使うと、ファイルに直接問い合わせを書けます。
import jsonfrom pathlib import Path
import pandas as pd
def build_table(raw_dir: Path) -> pd.DataFrame: rows = [] for path in sorted(raw_dir.glob("*/*.json")): doc = json.loads(path.read_text(encoding="utf-8")) for item in doc["payload"].get("items", []): rows.append( { "id": item["id"], "name": item.get("name"), # 元の値も残す。変換を間違えたときに突き合わせられる "price_raw": item.get("price"), "price": pd.to_numeric(item.get("price"), errors="coerce"), "fetched_at": doc["fetched_at"], } ) return pd.DataFrame(rows)生データを全部残すべきか
残す量は増え続けるので、無条件に全部というわけにはいきません。判断の基準は取り直せるかどうかの一点です。
- 取り直せない — 在庫、順位、板情報、天気の実況。その時点の値は二度と取れないので、必ず残す
- 取り直せる — 確定した過去の統計、公開済みの決算。配信元が消さない限り、後から取れる
- 大きすぎる — 残す期間を決めて、古いものは圧縮するか安い置き場へ移す
判断に迷うのは、たいてい真ん中の「取り直せるが手間はかかる」ものです。配信元が過去分を出していても、遡って取り直すのに何時間もかかるなら、実質的には取り直せないのと変わりません。取り直す手順が実際に動くと確認できているかまで含めて考えてください。
迷ったら残す側に倒してください。保存の費用より、取り直せない損失のほうが高いことがほとんどです。データの置き場所の設計そのものは 壊れないデータ基盤の作り方 で扱っています。次回は、毎回全件取るのをやめる話をします。
この記事の感想を教えてください
あなたの 1 クリックで、本当にこの記事は更新されます。「もっと詳しく」「続編希望」が一定数集まった記事は、 ふくふくが 実際に内容を拡充したり続編記事を公開 します。 送信したリアクションはお使いのブラウザに記録され、再カウントされません。