← 一覧に戻る

MoneyForward → Zaim データ移行 設計

2026年5月7日 08:24 更新

一言でいうと

MFの過去家計簿を、既存の自作CSV取込APIを再利用してZaimに引っ越す設計

なぜこれが必要なのか

家計簿アプリを MoneyForward ME(以下MF。家計簿の老舗アプリ)から Zaim(こちらも国産家計簿アプリ)に乗り換え中。MFには何年分もの取引履歴が溜まっているけど、これを引っ越さないとZaim側では「過去の支出傾向」が一切見えなくなる。家計簿の価値は積み上げの長さだから、ここで歴史を切ったら乗り換える意味が半減する。

放置すると次の問題が起きる:

なので Suica(交通系IC)の自動取込やクレカの自動連携を Zaim 側で本格稼働させる前に、まず歴史データの引っ越しを片付ける。健人 msg 5791「Suica前にMF移行」を踏襲。今回は設計と調査だけ、実装は別ジョブで起こす。

何をするのか

結論を3つに分けると:

1MF側の書き出し

MFの管理画面から月別CSV(カンマ区切りの表データ)ファイルを取得する。プレミアム会員(月額500円)の公式機能を使うのが本命。無料会員ならTampermonkey(ブラウザ拡張で画面の文字をJavaScriptで吸い出す道具)で代替可能。これがないと過去データの引っ越し元が確保できない。

2Zaim側の取込

既存の自作サービス claude-code-zaim(CloudflareのWorkers=小さなサーバ実行環境上で動くZaim連携)の /api/zaim/import-csv(CSVをまとめて受け取り内部で1行ずつZaimに登録するAPI)を流用する。重複防止・カテゴリ自動解決・エラー再開まで実装済みなので、ここを使えば新規開発を最小化できる。

3変換だけ新規実装

MFは10列のCSVを吐き、Zaim取込APIは13列のCSVを期待しているので、間に変換層を1枚挟む。具体的には mf-to-zaim-csv.ts(10列を13列に組み替えるスクリプト、新規)と mfFallbackMap.ts(MFカテゴリ名 → Zaimカテゴリ名の対応表、新規)の2ファイルを claude-code-zaim リポジトリに追加する。

選んだ理由(他案との比較)

MF側の書き出し方法 — 4案比較

仕組み会員要件判定
A. 公式CSVダウンロードWeb版「家計簿 → 入出金 → ダウンロード」から月別/年別CSVを取得プレミアム必須採用(基本)
B. URL直叩きで一括DLmoneyforward.com/cf/csv?year=Y&month=M に月数だけ自動アクセスして連続取得プレミアム必須採用(自動化版)
C. Tampermonkeyスクリプトブラウザ拡張で画面の表示内容をJavaScriptで読み取り、ローカル保存無料会員でも可代替案
D. MF公式APIOAuth(外部サービスへの認可方式)でAPIに直接アクセス却下

MFには個人ユーザー向けの公開APIが事実上存在しない(D却下)。Aが本命だけど、MFは無料会員だと過去1年分しか閲覧できず、CSVダウンロード機能自体もプレミアム限定。1ヶ月だけプレミアム加入(500円)して全期間DL → 解約、が最安ルート。それも嫌なら C(Tampermonkey)で逃げる。Bは何年分もある場合に手で月切替するのが面倒なときの自動化版(健人が「12ヶ月分なら手でいいや」なら不要)。

Zaim側の取込方法 — 3案比較

仕組み判定
α. Zaim公式CSVインポートWeb版「ファイル入出力 → 入力」で13列CSVをアップロード却下(2019/8/8でサポート終了)
β. Zaim API直叩き/v2/home/money/payment(Zaimの支出登録窓口)に1件ずつOAuth署名付きで送信既存実装あり(γの土台)
γ. claude-code-zaim/api/zaim/import-csv 流用13列CSVをまとめて投げると、内部でβを1行ずつ実行+重複弾き+カテゴリ自動解決採用

α は Zaim が2019年8月8日で公式サポートを打ち切ってる(機能は残っているけど絵文字混入で失敗、無保証)。β は1件ずつ手で送るには手数が多すぎる。γ は Suica の月次自動取込ですでに本番運用されているパスを丸ごと再利用できるから、新規開発は最小限で済む。

移行の中身(カラム変換マッピング)

MF側10列CSVのヘッダ:

計算対象, 日付, 内容, 金額(円), 保有金融機関, 大項目, 中項目, メモ, 振替, ID

Zaim側13列CSV(既存 importCsvHandler.ts=取込APIの本体ファイル が期待する形式)のヘッダ:

日付, 方法, カテゴリ, カテゴリの内訳, 支払元, 入金先, 品目, メモ, お店, 通貨, 収入, 支出, 振替

変換ロジックの要点:

Zaim カラム変換ルール
日付MF 日付 をそのまま(YYYY-MM-DD へ正規化)
方法MF 振替=1 なら「振替」、金額 が正なら「収入」、負なら「支出」
カテゴリMF 大項目mfFallbackMap.ts でZaimカテゴリ名に変換
カテゴリの内訳MF 中項目 を同マップでZaimジャンル名(カテゴリの下位区分)に変換
支払元 / 入金先方法に応じてMF 保有金融機関 を片方だけ埋める
品目MF 内容 をそのまま
メモ / お店メモはそのまま、お店はMF側に対応カラムがないので空
通貨固定で JPY
収入 / 支出 / 振替方法に応じて1列だけ abs(金額) を入れる

カテゴリ名は MF と Zaim で表記が微妙に違う(MF「交通費」⇔ Zaim「交通」、MF「水道光熱費」⇔ Zaim「水道・光熱」など)。なので既存の suicaFallbackMap.ts(Suica取込で使われている対応表ファイル)と同じ構造で mfFallbackMap.ts を新規追加し、HTTPヘッダ X-Import-Source: mf|suica でどちらの対応表を使うか切り替える設計にする。

マップに無いカテゴリは Zaim 側「その他」に自動フォールバック(既存ロジック)。警告ログとして残るので、後で見直して対応表に追加できる。

重複防止 — 3層でガードする

家計簿移行で一番怖いのが「同じ取引が二重に登録されること」。3段構えで守る。

レイヤー1: 運用カットオーバー(人間ルール)

カットオーバー(旧→新システムの切替日)を1つ決めて、時間軸で完全分離する:

  1. 移行直前にMFの最終取引日 D をスクショで記録
  2. Zaim側の自動連携(Suica・クレカ)の対象開始日を D + 1日 に固定
  3. MFからの過去データ取込は D 以前のみ
  4. Zaimの自動連携は D + 1日 以降のみ

これで日付が重なる行は構造的にゼロになる。日付が決まらないとレイヤー2/3 も意味が薄まるので、ここが本丸。

レイヤー2: 行ハッシュ突合(コードで保証)

既存 importCsvHandler は、各取引の [日付, 方法, 金額, 品目, メモ, お店] から SHA-256(同じ入力から同じ短い文字列を作るハッシュ関数。指紋のようなもの)で識別キーを生成し、Cloudflareの簡易データベース KV(キー=値の高速ストア)に1年保存する。同じCSVを2度POSTしても、2度目は「すでに登録済み」として自動的にスキップされる。

レイヤー1で運用ミスがあって時間軸が重なっても、ここで救える保険。

レイヤー3: MF「計算対象=0」行の除外

MFには「集計から外す」フラグがあり、計算対象 列が 0 の行は MF 内部でも合計に入っていない。変換時にこれらをCSVから落とす。MFで意図的に外していたものをZaimに入れたら、移行で家計の数字がズレる。

ステップ / 進め方

事前準備(健人手作業・15分)

  1. MF Web版にログインする(カットオーバー日 D を確定するための準備)
  2. 「家計簿 → 入出金」で最後の取引日をスクショ → これがカットオーバー日 D。Zaim自動連携の開始日を確定するため必須
  3. プレミアム会員かどうか確認。未加入なら、500円課金して即解約 / Tampermonkey で逃げる、のどちらにするか判断(ここで詰まると以降の全工程が止まる)
  4. MFの大項目/中項目をざっと眺めて、Zaim側にもありそうかUIで目視確認(カテゴリ対応表の作成精度に直結)

実装ジョブ(別建てで起票・2〜3時間)

  1. scripts/mf-to-zaim-csv.ts(10列を13列に組み替えるNodeスクリプト、新規)を claude-code-zaim に追加。ローカルで実行できる変換層を作る目的
  2. src/lib/mfFallbackMap.ts(MF→Zaim カテゴリ対応表、新規)を追加。表記揺れを吸収する目的
  3. importCsvHandler.ts(取込APIの本体)を拡張: ヘッダ X-Import-Source: mf|suica で使う対応表を切り替える。Suica既存パスを壊さずMF対応を載せるため
  4. 重複検知キーを imported:<source>:<hash> 形式に拡張(Suica と MF で名前空間を分けるか統合するかは健人に確認したい論点、後述)
  5. importCsvHandler.test.ts(テスト本体)にMF由来CSVのテストケースを追加。本番投入前の品質保証
  6. デプロイ後に1ヶ月分だけ試験投入 → 件数とカテゴリ命中率を確認 → OKなら全期間バルク投入。いきなり全期間入れて事故るのを避けるため

実投入(健人手動・チバ伴走・1〜2時間)

  1. MFから月別CSVをまとめてダウンロード(移行元データを揃える)
  2. 変換スクリプト経由でZaim 13列CSVに変換(健人ローカル実行で形式を揃える)
  3. 200行ずつに分割(取込APIの上限が200行/req のため。月別ならほぼ1ファイル1リクエストで収まる)
  4. curl(コマンドラインからHTTP通信を送る道具)で /api/zaim/import-csv に POST
  5. レスポンスの imported(取込成功)/skipped(重複でスキップ)/errors(失敗)を確認
  6. 422エラー(人間介入待ち)が出たら原因を直して再投入
  7. 全期間OKになったら Zaim 自動連携を D+1日 以降で起動

ポイント・確認したいこと

健人に確認したい4点(実装ジョブ起票前にここを潰したい):

1. 健人はMFプレミアム会員?(未加入なら課金 / Tampermonkey 判断)

2. 移行対象期間は何年分?(5年想定だが実データの古さで件数が変わる)

3. 振替(口座間の資金移動)は移行対象に含める? 現状の importCsvHandler は振替を一律スキップする設計。MF時代の振替履歴を Zaim にも残したいなら処理拡張が必要。

4. 重複検知キーの名前空間を Suica と MF で分ける(誤検知ゼロだが二重登録リスクあり)か統合する(同じ取引が両系統で登録されたら自動弾きされるが、たまたま属性が完全一致する別取引も誤って弾く可能性)か。

なぜ既存実装の流用が効くのか

普通なら「データ移行ツールを一から書く」となるところを、Suica取込のために作った importCsvHandler が以下の機能をすでに持っているから流用できる:

つまり 新規実装は変換層1枚 + カテゴリ対応表1枚 で済む。差分の小ささが安全性の裏付けになる。

所要時間まとめ

工程時間担当
事前準備15分健人
変換スクリプト実装+テスト2〜3時間実装ジョブ(チバ)
カテゴリ対応表 初期作成30分〜1時間実装ジョブ + 健人レビュー
MF全期間DL(5年想定)20分健人 or 自動化
変換 + Zaim投入30分〜1時間健人 + チバ
カテゴリ未知の手直し1〜2時間健人 + チバ

完了条件チェック(依頼書 done_when 対応)

#項目状態
1MFデータ最終日時確認健人手作業 — 移行直前に実施。手順は本ページに記載
2MF過去データエクスポート4案比較完了 — 採用A、保険C
3Zaimインポート方式調査完了 — 採用γ
4重複防止戦略完了 — 3層
5移行手順整理完了
6plan-viewer出力本ページ
7verification_summary完了(別途記録)

次のアクション: 健人がこの設計をレビュー → OK判定 → 実装ジョブを別建てで起票(mf-to-zaim-csv.ts + mfFallbackMap.ts + importCsvHandler 拡張 + テスト追加)。

📝 質問モード — テキストを選択してね
✓ 質問を送信しました