「月末に入金を確認しようとネットバンキングを開いたけど、どの振込がどの請求書の分なのか分からない…」

「金額が数百円足りない入金があって、これって払い忘れ?それとも手数料?」

フリーランスになると、請求書を出すところまでは慣れても、「ちゃんと入金されたか」を1件ずつ確かめる作業が意外と面倒ですよね。取引先が増えるほど、見落としも増えていきます。

この作業は経理の言葉で入金消込(にゅうきんけしこみ)と呼ばれます。イメージとしては「請求書の一覧に、届いたお金を1件ずつ“済”のハンコを押していく作業」です。

この記事では、請求一覧のCSVと銀行の入出金明細のCSVをPython(pandas)で突き合わせて、

  • ぴったり入金されたもの
  • 振込手数料を差し引かれて入金されたもの
  • 2件分をまとめて1回で振り込まれたもの
  • 金額が合わない「要確認」の入金
  • 支払期限を過ぎても入っていない「未入金」

を自動で仕分けるスクリプトを作ります。対象はpandasでCSVを読んだことがある初〜中級者です。コードはすべて実際に実行して、出た結果をそのまま載せています😊 一緒に見ていきましょう!

入金消込で「目で見る」とつまずくポイント

invoice payment bank statement
invoice payment bank statement / Photo by Niepoddawajsie.pl Luk via Pexels

最初に、手作業だと何がつらいのかを整理しておきます。実はここが、そのままプログラムで解決すべきポイントになります。

つまずき よくある例 この記事での対処
名義の表記がちがう 請求先は「株式会社ヤマダ商事」、明細は「フリコミ カ)ヤマダシヨウジ」 名義を同じ形にそろえる関数を作る
金額が少しだけ足りない 33,000円の請求に32,560円の入金 差額が一定額以下なら「手数料差引」と判定
まとめて振り込まれる 2件の請求(220,000円+44,000円)が264,000円で1回に 請求の組み合わせの合計と比べる
そもそも入っていない 期限を過ぎたのに明細に見当たらない 残った請求を期限で「超過/期限前」に分ける

銀行の明細では、振込名義が半角カタカナで、「ョ」「ッ」のような小さい文字が使えず大きい文字(ヨ・ツ)で表示されることがよくあります。会社の種類も「カ)」(株式会社)のような略語になります。このままでは文字列として一致しないので、まずは「比べられる形」にそろえるのが最初の仕事です。

ステップ1:請求一覧と銀行明細のサンプルを用意する

練習用に、請求一覧(invoices.csv)と入金明細(bank.csv)を作って読み込んでみます。実際の運用では、自分の請求管理表と、ネットバンキングからダウンロードしたCSVに置き換えてください。

ポイントをまとめるとこんな感じです👇

  • 請求一覧には、取引先名とは別に「振込名義」(カナ)の列を持っておく
  • CSVは utf-8-sig で保存しておくとExcelで開いても文字化けしにくい
  • 日付の列は parse_dates で日付型として読み込む(期限の比較に使います)
import pandas as pd

# 日本語の列がずれないようにする表示設定
pd.set_option("display.unicode.east_asian_width", True)

# 請求書の一覧(自分で管理している表)
invoices = pd.DataFrame({
    "請求番号": ["INV-101", "INV-102", "INV-103", "INV-104", "INV-105", "INV-106"],
    "取引先": ["株式会社ヤマダ商事", "合同会社ショウリン", "佐藤デザイン事務所",
              "株式会社ミライテック", "株式会社ミライテック", "株式会社アオバ"],
    "振込名義": ["ヤマダショウジ", "ショウリン", "サトウデザイン",
              "ミライテック", "ミライテック", "アオバ"],
    "請求額": [110000, 55000, 33000, 220000, 44000, 88000],
    "支払期限": ["2026-09-30", "2026-09-30", "2026-09-30",
              "2026-09-30", "2026-09-30", "2026-10-31"],
})
invoices.to_csv("invoices.csv", index=False, encoding="utf-8-sig")

# 銀行のネットバンキングから落としたイメージの入出金明細(入金だけ抜粋)
bank = pd.DataFrame({
    "日付": ["2026-09-25", "2026-09-29", "2026-09-30", "2026-09-30", "2026-10-02"],
    "摘要": ["フリコミ カ)ヤマダシヨウジ", "フリコミ サトウデザイン", "フリコミ カ)ミライテツク",
             "フリコミ シヨウリン(ド", "フリコミ カ)アオバ"],
    "入金額": [110000, 32560, 264000, 50000, 88000],
})
bank.to_csv("bank.csv", index=False, encoding="utf-8-sig")

inv = pd.read_csv("invoices.csv", parse_dates=["支払期限"])
bk = pd.read_csv("bank.csv", parse_dates=["日付"])
print(inv[["請求番号", "振込名義", "請求額"]])
print()
print(bk)

実行すると、2つの表がこのように表示されます。

  請求番号        振込名義  請求額
0  INV-101  ヤマダショウジ  110000
1  INV-102      ショウリン   55000
2  INV-103  サトウデザイン   33000
3  INV-104    ミライテック  220000
4  INV-105    ミライテック   44000
5  INV-106          アオバ   88000

        日付              摘要  入金額
0 2026-09-25  フリコミ カ)ヤマダシヨウジ  110000
1 2026-09-29    フリコミ サトウデザイン   32560
2 2026-09-30     フリコミ カ)ミライテツク  264000
3 2026-09-30     フリコミ シヨウリン(ド   50000
4 2026-10-02       フリコミ カ)アオバ   88000

明細の摘要を見ると「カ)」「(ド」が付いていたり、「ショウジ」が「シヨウジ」になっていたりしますよね。サンプルには、次の“わな”をわざと入れてあります。

  • 佐藤デザイン事務所:33,000円の請求に32,560円(440円少ない)
  • ミライテック:2件の請求を264,000円で1回にまとめて入金
  • ショウリン:55,000円の請求に50,000円しか入っていない
  • アオバ:期限(10/31)より前に入金済み

ステップ2:振込名義を「比べられる形」にそろえる

ここが重要です👇 名前の比較がずれると、その後の金額チェックがすべて空振りになります。

そろえ方は、ざっくり言うと次の4段階です。

  1. unicodedata.normalize("NFKC", ...) で半角カナを全角カナに(濁点つきの「ダ」も「ダ」1文字になります)
  2. 先頭の「フリコミ」を消す
  3. 法人の略語(カ=株式会社、ユ=有限会社、ド=合同会社、メ=合名会社、シ=合資会社)を括弧ごと消す
  4. 小さいカナを大きいカナに直し、空白や長音などの記号を消す

つまり「銀行の書き方」か「自分の書き方」のどちらかに寄せるのではなく、両方を同じルールで変換して、変換後どうしを比べるのがコツです。

このファイルを name_utils.py として保存し、単体で実行してみます。これ、実行せずに答えられますか? 最初に出る「そのまま比較」は True と False のどちらになるでしょう?

import re
import unicodedata

# 小さい「ャュョッ」などを大きい文字にそろえる表(銀行の名義は大文字だけのことが多い)
SMALL_TO_LARGE = str.maketrans("ァィゥェォャュョッヮ", "アイウエオヤユヨツワ")

# 法人の種類を表す略語(カ=株式会社、ユ=有限会社、ド=合同会社、メ=合名会社、シ=合資会社)
# 「カ)ヤマダ」「ヤマダ(カ」「ヤマダ(カ)シヨウジ」のどの位置でも消せるようにする
CORP_MARK = re.compile(r"[((]?(?:カ|ユ|ド|メ|シ)[))]|[((](?:カ|ユ|ド|メ|シ)")


def normalize_name(text: str) -> str:
    """振込名義を「比べやすい形」にそろえる"""
    s = unicodedata.normalize("NFKC", text)  # 半角カナ→全角カナ、全角英数→半角
    s = s.replace("フリコミ", "")            # 先頭の「フリコミ」を消す
    s = CORP_MARK.sub("", s)                  # カ) や (カ を消す
    s = s.translate(SMALL_TO_LARGE)           # ショウジ → シヨウジ
    s = re.sub(r"[\s・.,\-ー]", "", s)        # 空白・記号・長音を消す
    return s


if __name__ == "__main__":
    tests = ["フリコミ カ)ヤマダシヨウジ", "ヤマダショウジ",
             "フリコミ シヨウリン(ド", "ショウリン",
             "フリコミ カ)ミライテツク", "ミライテック"]
    print("そのまま比較:", tests[0] == tests[1])
    for t in tests:
        print(f"{t}  →  {normalize_name(t)}")
実行結果:振込名義をそろえた結果
実行結果:振込名義をそろえた結果

答えは False でした。そのままでは一致しませんが、変換後は明細側と請求側がどちらも「ヤマダシヨウジ」「シヨウリン」「ミライテツク」になり、一致するようになりました🚀

⚠️ 略語の位置は「カ)ヤマダ」(前)、「ヤマダ(カ」(後ろ)、「ヤマダ(カ)シヨウジ」(途中)のように変わります。正規表現は3パターンとも消せるように書いています。また、名義の表記ルールは金融機関によって異なる場合があるので、自分の明細で「名義キー」が想定どおりになるかを最初に一度確かめておくと安心です。

ステップ3:1件ずつ突き合わせる(ぴったり・手数料差引)

ここからは reconcile.py という1つのファイルに、ステップ3〜5のコードを上から順に書いていきます。

まずは「入金1件」と「同じ名義の請求」を比べる関数です。

import pandas as pd

from name_utils import normalize_name

FEE_LIMIT = 1000  # 振込手数料を引かれていても「入金済」とみなす差額の上限(円)


def match_single(deposit, candidates):
    """1件の入金を、同じ名義の未消込の請求1件と突き合わせる"""
    # ① 金額がぴったり一致
    for idx, row in candidates.iterrows():
        if row["請求額"] == deposit:
            return [idx], "入金済"
    # ② 手数料ぶんだけ少ない(先方が手数料を差し引いて振り込んだ)
    for idx, row in candidates.iterrows():
        diff = row["請求額"] - deposit
        if 0 < diff <= FEE_LIMIT:
            return [idx], f"入金済(手数料{diff}円差引)"
    return None, None

ポイントは次の2つです。

  • 先にぴったり一致を探してから、手数料差引を探す(順番を逆にすると、同じ取引先に金額の近い請求が複数あるときに誤判定しやすくなります)
  • FEE_LIMIT は「ここまでの差額なら手数料とみなす」上限です。振込手数料は銀行や振込方法で変わるので、自分の取引先の実態に合わせて調整してください

なお、振込手数料をどちらが負担するかは契約や請求書の記載しだいです。差し引かれて困る場合は、請求書に「振込手数料はご負担ください」のように書いておくのが一般的ですね。このスクリプトでは差額を状態に残すので、あとから確認できます。

ステップ4:まとめ入金を組み合わせで見つける

同じ取引先から「今月分と先月分をまとめて振り込みました」というケース、けっこうありますよね。1件ずつ比べても見つからないので、請求の組み合わせの合計と入金額を比べます。

ここで使うのが標準ライブラリの itertools.combinations です。イメージとしては「候補の請求から2件選ぶ全パターン、3件選ぶ全パターン…を順番に試す」道具です。

from itertools import combinations


def match_combined(deposit, candidates):
    """複数の請求をまとめて1回で振り込んだケースを探す"""
    for n in range(2, len(candidates) + 1):
        for combo in combinations(candidates.index, n):
            total = candidates.loc[list(combo), "請求額"].sum()
            if total == deposit:
                return list(combo), f"入金済({n}件まとめ)"
            if 0 < total - deposit <= FEE_LIMIT:
                return list(combo), f"入金済({n}件まとめ・手数料{total - deposit}円差引)"
    return None, None
  • 候補は「同じ名義で、まだ消し込んでいない請求」だけに絞ってから渡すので、組み合わせの数は少なく済みます
  • ただし1社に未消込の請求が何十件もたまると、組み合わせが爆発的に増えます。その場合は「期限が近いものから順に足していく」などのルールに切り替えましょう
  • まとめ入金でも手数料が引かれることがあるので、ステップ3と同じ FEE_LIMIT で判定しています

ステップ5:未入金・要確認を仕分けてCSVに保存する

最後に、全体をまとめる reconcile() 関数と、実行部分を書きます。

invoices.csv・bank.csv・name_utils.py と同じフォルダで reconcile.py を実行した結果も、コードの直後に載せています。

def reconcile(invoices, bank, today):
    invoices = invoices.copy()
    invoices["名義キー"] = invoices["振込名義"].map(normalize_name)
    invoices["状態"] = ""
    invoices["入金日"] = pd.NaT
    needs_check = []  # どの請求とも合わなかった入金

    for _, dep in bank.iterrows():
        key = normalize_name(dep["摘要"])
        # 同じ名義で、まだ消し込んでいない請求だけが候補
        candidates = invoices[(invoices["名義キー"] == key) & (invoices["状態"] == "")]

        hit, status = match_single(dep["入金額"], candidates)
        if hit is None:
            hit, status = match_combined(dep["入金額"], candidates)

        if hit is None:
            needs_check.append({"日付": dep["日付"].date(), "摘要": dep["摘要"],
                                "入金額": dep["入金額"], "候補の請求額": candidates["請求額"].tolist()})
            continue
        invoices.loc[hit, "状態"] = status
        invoices.loc[hit, "入金日"] = dep["日付"]

    # 残った請求は未入金。期限を過ぎているかどうかで分ける
    unpaid = invoices["状態"] == ""
    overdue = unpaid & (invoices["支払期限"] < today)
    invoices.loc[overdue, "状態"] = "未入金(期限超過)"
    invoices.loc[unpaid & ~overdue, "状態"] = "未入金(期限前)"
    return invoices, pd.DataFrame(needs_check)


if __name__ == "__main__":
    pd.set_option("display.unicode.east_asian_width", True)
    invoices = pd.read_csv("invoices.csv", parse_dates=["支払期限"])
    bank = pd.read_csv("bank.csv", parse_dates=["日付"])
    today = pd.Timestamp("2026-10-11")  # 実際の運用では pd.Timestamp.today().normalize()

    result, needs_check = reconcile(invoices, bank, today)

    print(result[["請求番号", "取引先", "請求額", "状態"]].to_string(index=False))
    print("\n▼ 確認が必要な入金")
    print(needs_check.to_string(index=False))

    # 督促が必要な請求だけを取り出して合計する
    overdue = result[result["状態"] == "未入金(期限超過)"]
    print(f"\n期限超過の未入金: {len(overdue)}件 / 合計 {overdue['請求額'].sum():,}円")

    # Excelで開いても文字化けしないよう utf-8-sig で保存
    result.drop(columns="名義キー").to_csv("reconcile_result.csv", index=False, encoding="utf-8-sig")
    print("reconcile_result.csv を保存しました")
実行結果:入金消込の判定結果
実行結果:入金消込の判定結果

処理の順番を整理すると、ざっくりとした流れがつかめるはずです。

  1. 明細の入金を1件ずつ取り出し、名義キーが同じ「未消込の請求」を候補にする
  2. ステップ3(1件)→ステップ4(組み合わせ)の順に合うものを探し、見つかったら状態と入金日を書き込む
  3. どれにも合わない入金は「確認が必要な入金」に回す
  4. 最後まで残った請求は、today と支払期限を比べて「期限超過」と「期限前」に分ける

実行結果を見ると、狙いどおりに仕分けられています😊

  • 佐藤デザイン事務所は「手数料440円差引」で入金済み
  • ミライテックの2件は「2件まとめ」で両方とも入金済み
  • アオバは期限前の入金でも、ちゃんと入金済みになっている
  • ショウリンの50,000円は55,000円と5,000円もずれているので、手数料とはみなさず「確認が必要な入金」へ。請求のほうは「未入金(期限超過)」として残っています

ショウリンのように「入金はあるけど金額が合わない」ケースを勝手に消し込まないのが大事なポイントです。値引きの合意があったのか、一部だけの支払いなのか、請求ミスなのかは、プログラムには判断できません。人が取引先に確認する対象として目立たせておく、という役割分担にしています。

保存された reconcile_result.csv には「状態」と「入金日」の列が追加されているので、Excelで開いて「期限超過」で絞り込めば、そのまま督促リストとして使えます。

実務で使うときの注意点

  • 明細CSVの列名は銀行ごとに違います。 読み込んだ直後に rename() でこの記事の列名(日付・摘要・入金額)にそろえると、後ろのコードはそのまま使えます
  • 文字コードにも注意。 銀行のCSVはShift_JIS系のことがあるので、読み込みで文字化けやエラーが出たら encoding="cp932" を試してください
  • 入金だけを対象にする。 出金も含む明細なら、入金額が0や空の行を先に除いておきます
  • 名義が取引先名とまったく違うこともあります。 親会社などの別名義で振り込まれる場合は、請求一覧の「振込名義」をその名義にしておけばOKです
  • 元のファイルは上書きしない。 結果は別ファイルに保存して毎月残しておくと、あとから「いつ入金されたか」を追えます

まとめ

今回は、Pythonで入金消込(入金チェック)を自動化する方法を紹介しました。要点をおさらいします。

  • 入金消込は「請求一覧に、届いたお金を1件ずつ“済”にしていく作業」
  • 銀行の振込名義は半角カナ・小さい文字なし・法人略語つきなので、NFKC正規化+略語の削除+小さいカナの変換で両方をそろえてから比べる
  • 判定は「ぴったり」→「手数料差引」→「まとめ入金」の順。手数料とみなす上限は FEE_LIMIT で調整する
  • 金額が合わない入金は自動で消し込まず「要確認」に回す
  • 残った請求は支払期限と比べて「期限超過」を一覧にし、督促リストとして使う

毎月なんとなく目で追っていた入金確認も、一度スクリプトにしておけば数秒で終わります。まずは先月分の明細で、ぜひ試してみてください😊

📚 関連商品・おすすめ書籍

スッキリわかるPython入門 第2版 (スッキリわかる入門シリーズ)

もしも

スッキリわかるPython入門 第2版 (スッキリわかる入門シリーズ)

初心者に定番のPython入門書

Amazonで見る
実践Claude Code入門―現場で活用するためのAIコーディングの思考法

もしも

実践Claude Code入門―現場で活用するためのAIコーディングの思考法

AIコーディングの現場活用法を学ぶ一冊

Amazonで見る
Python Web開発実践入門 ―― FastAPIによるWebAPI開発と非同期処理

もしも

Python Web開発実践入門 ―― FastAPIによるWebAPI開発と非同期処理

FastAPIでWebAPI開発を実践的に学ぶ

Amazonで見る

※本記事にはアフィリエイトリンクが含まれます。

ABOUT ME
やまちゃん
これまで学生と社会人を合わせて5000人以上にプログラミング学習を指導。 ゼロからイチをわかりやすく解説する専門家として活動しており、本業ではArduinoを用いたIoT開発とロボットプログラミングが専門。 Pythonを用いたアプリ開発、ウェブアプリケーションの開発で業務の効率化をサポートしています。