顧客リストの名寄せをAIにやらせた|“同名別会社”で事故る実測と回避策

2026.06.30 約 17 分で読めます

「株式会社A」「(株)A」「A株式会社」が、別の会社として3行に分かれている。顧客リストでよく見る光景です。手で名寄せすると数百行で軽く2時間は溶けます。これを Claude のAPIに投げたら、206行の名寄せが一瞬で終わりました。ただ、そのまま信じると事故ります。会社名だけで判定させると、同じ名前の別会社を勝手にくっつけてしまう。実際にわざと汚したデータで精度を測り、どこで間違えるかまで確かめました。コピペで動くスクリプトとコストも、そのまま置いておきます。

結論:会社名だけだと事故る。住所も渡して「下ごしらえ9割+人の最終確認」

先に答えを置きます。AIに任せると速くて安いです。でも「全自動で完成」までは届きません。鍵は2つでした。

  • 会社名だけ渡すと、同名別会社を誤って統合する。東京の山田製作所と愛知の山田製作所が1社にまとまってしまいます。
  • 会社名+住所を渡すと、誤統合は消える。ただし今度は、本社が移転した会社を「別の会社」と取りこぼします。

つまり、AIに任せていいのは下ごしらえの9割くらい。残りの「同名別会社」と「移転」だけを人が確認する。この線引きが現実的でした。数字で言うと、会社名だけのときは精度(F1)が0.94、住所も渡すと0.99まで上がります。なぜそうなるのかを、実データで順に見ていきましょう。

題材:わざと汚した顧客リスト206行を作った

実在の顧客データは、当然ですが公開できません。そこで、現場で本当に起きる「汚れ方」を仕込んだダミーの顧客CSVを206行ぶん、スクリプトで生成しました。正解(どの行が同じ会社か)を持たせたうえで、名前と住所だけを名寄せ側に渡しています。仕込んだ汚れはこんな具合です。

  • 法人格の表記ゆれ:株式会社A / (株)A / ㈱A / A株式会社
  • 全角と半角の混在、スペースの有無:「株式会社 サクラ商事」「サクラ商事(株)」
  • 住所の揺れ:都道府県を省く / 「1丁目2-3」と「1-2-3」/ 漢数字の丁目 / ビル名の有無 / 全角数字

ここまでは正規化で片づく、いわば素直な汚れです。意地悪なのは、ここから先に3つの罠を混ぜたこと。これが名寄せの本当の難所でした。

  • 罠1:同名別会社。山田製作所が「愛知県名古屋市」と「東京都大田区」に1社ずつ。鈴木商店も「東京都台東区」と「大阪府堺市」に1社ずつ。名前は同じでも別の会社です。
  • 罠2:似て非なる別社。「ニッポンソフト」と「ニッポンソフトウェア」。1文字違いで別会社。
  • 罠3:移転した会社。ムサシ物産は「千代田区の旧本社」と「横浜の新本社」、タマガワ印刷は「大田区」と「世田谷区」。住所は違っても同じ会社です。

全部で62社。これを206行にバラして、表記をランダムに散らしました。この62社をどれだけ正しく言い当てられるかが、今回の勝負です。

やってみた①:会社名だけで名寄せ → 同名別会社を35ペア誤統合

まずは素直に、会社名だけを見て名寄せさせました。法人格・全角半角・スペースを正規化して、同じ名前をまとめる。結果はこうです。

条件A:会社名のみ数値
precision(統合したものの正しさ)0.893
recall(同じ会社を取りこぼさない度)1.000
F1(総合スコア)0.9435
検出した会社数60社(正解は62社)

recall は1.0。表記ゆれは完璧にまとめました。「㈱サクラ商事」も「サクラ商事株式会社」も、ちゃんと1社に寄せています。問題は precision の0.89のほう。別会社を35ペアも「同じ会社」と判定していました。中身を見ると犯人は明白です。

  • 『(株)山田製作所』(名古屋市中区)=『株式会社山田製作所』(東京都大田区) … 別会社なのに統合
  • 鈴木商店(東京都台東区)= 鈴木商店(大阪府堺市) … 別会社なのに統合

そうですよね。名前しか渡していないのだから、東京の山田製作所と愛知の山田製作所を区別する材料がない。AIが悪いというより、入力が足りていません。検出した会社数が60社(正解62社)なのも、2組の同名別会社が1社ずつに潰れたからです。ここで「住所も渡せばいいのでは」と思いますよね。やってみました。

やってみた②:会社名+住所で名寄せ → 誤統合は消えたが、移転を6ペア取りこぼし

次は、会社名に加えて住所(とくに市区)も判定材料に入れました。名前が同じでも市区が違えば別会社として分ける、という方針です。結果が気持ちいいくらい変わりました。

条件B:会社名+住所数値
precision1.000
recall0.9795
F10.9896
検出した会社数64社(正解は62社)

precision が1.0。さっき誤統合していた山田製作所も鈴木商店も、住所でスパッと分かれました。同名別会社の事故はゼロです。これは効きました。ただ、今度は recall がわずかに落ちて0.98。取りこぼしが6ペア出ています。中身を見て、なるほどと思いました。

  • 『㈱ムサシ物産』(横浜市港北区=新本社)≠『株式会社ムサシ物産』(千代田区=旧本社) … 同じ会社なのに別物扱い
  • 『タマガワ印刷株式会社』(大田区)≠『タマガワ印刷㈱』(世田谷区=移転先) … 同じ会社なのに別物扱い

住所で切ったぶん、移転した会社が別会社に割れてしまった。これは住所をコンテキストに入れた副作用で、ある意味で正しい判断ミスです。人間でも、住所だけ見せられたら同じように迷うはず。検出が64社(正解62社)と2社ぶん増えているのも、ムサシ物産とタマガワ印刷がそれぞれ2つに割れたからです。

ここまでで、AIの名寄せの「効きどころ」と「穴」がはっきりしました。表記ゆれは丸投げでいい。同名別会社は住所で解決する。でも移転だけは、住所を見ても判断がつかない。だから最後に人がいる、という話です。

コピペで動く実装(Anthropic API・Haiku 4.5)

本番で使う形のスクリプトを置いておきます。モデルは安い Claude Haiku 4.5。ポイントは、会社名だけでなく住所も一緒に渡すこと、そして判断に迷う行に needs_review フラグを立てさせることです。出力は構造化(JSON Schema)で受け取るので、パースで崩れません。

# -*- coding: utf-8 -*-
"""
顧客リストの名寄せを Claude API にやらせる。
入力: customers_dirty.csv(会社名・住所)/ 出力: clusters.csv
ポイント: 会社名だけでなく住所も渡す。迷う行は needs_review を立てる。
実行: ANTHROPIC_API_KEY を設定して  python dedupe_with_claude.py
"""
import csv, json
import anthropic

MODEL = "claude-haiku-4-5"   # 安価。精度を上げたいなら claude-sonnet-4-6

SYSTEM = """あなたは顧客マスタの名寄せ(重複統合)を行う専門家です。
渡された行を「同じ会社か」で正規化・グルーピングしてください。

ルール:
- 「株式会社」「(株)」「㈱」などの法人格表記、全角半角、空白の違いは無視して同一とみなす。
- ただし会社名が同じでも、住所(特に都道府県・市区)が明らかに別なら【別会社】として分ける。
- 似ているが別名の会社(例: ニッポンソフト と ニッポンソフトウェア)は別会社として分ける。
- 各行に entity_key(同一会社で共通の英数字キー)と canonical_name(正規化後の正式名称)を割り当てる。
- 判断に迷う(移転の可能性・住所欠落など)行は needs_review=true を立てる。"""

rows = []
with open("customers_dirty.csv", encoding="utf-8-sig") as f:
    for r in csv.DictReader(f):
        rows.append({"row_id": int(r["row_id"]), "会社名": r["会社名"], "住所": r["住所"]})

SCHEMA = {
    "type": "object",
    "properties": {
        "results": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "row_id": {"type": "integer"},
                    "entity_key": {"type": "string"},
                    "canonical_name": {"type": "string"},
                    "needs_review": {"type": "boolean"},
                },
                "required": ["row_id", "entity_key", "canonical_name", "needs_review"],
                "additionalProperties": False,
            },
        }
    },
    "required": ["results"],
    "additionalProperties": False,
}

client = anthropic.Anthropic()  # ANTHROPIC_API_KEY を環境変数から読む

resp = client.messages.create(
    model=MODEL,
    max_tokens=16000,
    system=[{
        "type": "text",
        "text": SYSTEM,
        "cache_control": {"type": "ephemeral"},  # 指示は固定なのでプロンプトキャッシュに乗せる
    }],
    output_config={"format": {"type": "json_schema", "schema": SCHEMA}},
    messages=[{
        "role": "user",
        "content": "次の顧客行を名寄せして:\n" + json.dumps(rows, ensure_ascii=False),
    }],
)

text = next(b.text for b in resp.content if b.type == "text")
data = json.loads(text)

with open("clusters.csv", "w", encoding="utf-8-sig", newline="") as f:
    w = csv.writer(f)
    w.writerow(["row_id", "entity_key", "canonical_name", "needs_review"])
    for r in sorted(data["results"], key=lambda x: x["row_id"]):
        w.writerow([r["row_id"], r["entity_key"], r["canonical_name"], r["needs_review"]])

u = resp.usage
cost = u.input_tokens / 1e6 * 1.00 + u.output_tokens / 1e6 * 5.00
print(f"トークン: 入力{u.input_tokens} / 出力{u.output_tokens} / 概算 ${cost:.4f}")

システムプロンプトに「住所が違えば別会社」「似た別名は分ける」「迷う行は needs_review」と書き下しているのが要点です。ここを曖昧にすると、条件Aのときのような同名別会社の誤統合が戻ってきます。指示は固定なのでプロンプトキャッシュに乗せておくと、再実行が安くなります。

コストは1回約3円、バッチなら半額

気になるお金の話。今回の206行ぶんを1回のリクエストで投げると、入力はおよそ5,900トークンでした。Haiku 4.5 の料金は入力が100万トークンあたり1ドル、出力が5ドルです。(公式の料金ページ)

  • 入力 5,900トークン × $1 / 100万 = 約 $0.006
  • 出力 約3,000トークン × $5 / 100万 = 約 $0.015
  • 合計 約 $0.02(およそ3円)

206行でワンコインどころか、3円です。さらに急がない処理なら Batch API を使うと全トークンが50%オフ。指示文をプロンプトキャッシュに乗せれば、繰り返し回すぶんはもっと下がります。数千件の台帳でも、手作業の人件費を思えば誤差みたいなコストでした。

向かないケース・気をつけたいこと

良いことばかり書くと嘘になるので、つまずいた点と限界も正直に。

  • 「会社名だけでいける」は誤り。今回いちばんの学びです。住所を渡さないと同名別会社で precision が0.89まで落ちます。名前だけの台帳しか手元にないなら、まず住所を補完するところからです。
  • 住所も万能ではない。本社移転や支店住所のズレで、同じ会社を別物に割ってしまう。住所を入れても recall は0.98でした。完璧の一歩手前で必ず人が要ります。
  • 合成データでの数字。今回は罠を仕込んだダミーでの実測です。実データは手書き由来の誤字やOCRの揺れがもっと多いので、本番の精度はこれより下がる前提で見たほうが安全です。

だから運用は「全自動」を狙わない設計にします。スクリプトで needs_review を立てさせ、その行だけ人が目視する。今回でいえば、同名別会社の候補と移転疑いの計数件を確認すれば済みます。数百行を端から見比べる作業と比べたら、負担は桁違いに軽いです。

まとめ

  • 顧客リストの表記ゆれ名寄せは、AIに丸投げでほぼ片づく(recall 1.0)。
  • ただし会社名だけ渡すと同名別会社を誤統合する(F1 0.94 / 35ペア誤り)。
  • 住所も一緒に渡すと誤統合は消える(precision 1.0 / F1 0.99)。代わりに移転を取りこぼす(6ペア)。
  • コストは206行で約3円、Batch APIで半額。Haiku 4.5 で十分。
  • 完全自動は狙わず、needs_review で「同名別会社」「移転」だけ人に回すのが現実解。

同じやり方は、画像やPDFを一括処理する作業にも応用できます。以前 AI生成画像を一括でWebP最適化した記事 でも、手元で実測してからスクリプトを公開しました。ほかの効率化ネタは 業務効率化実践のカテゴリ にまとめています。

自社の台帳でやってみたい方へ

「うちの顧客リストでも名寄せしたいけど、住所の補完やスクリプトの組み込みが不安」という方へ。業務効率化の導入支援もやっています。データの下ごしらえから運用フローの設計まで、30分の無料相談から始められます。 → サービス詳細

こま

こま

元SE × AI開発 × 業務自動化の実践者

SE 4年 + プログラミング歴 8年。業務自動化と AI 組み込み開発を仕事にしながら、現場で使えるものだけを書いています。 最新のAIの現場を発信中!!