GASで住所データをAIで
都道府県・市区町村に自動分割する方法
1列にまとまった住所を、Claude API(Anthropic社の生成AIをプログラムから呼び出す仕組み)に渡し、都道府県・市区町村・それ以降にきれいに分けてスプレッドシートへ書き戻す方法を、動くコードで解説します。
Table of Contents
なぜ住所の分割はAIが向いているのか
フォームやCSVで集まる住所は、1列にまとめて入っていることがほとんどです。ラベル印刷や顧客分析では、これを都道府県・市区町村・それ以降に分けたい場面が多くあります。
都道府県までは正規表現でも分割できます。しかし市区町村より後ろは、次のような例外が多く、ルールベースでは破綻しがちです。AIは文脈ごと判断するため、こうした揺れに強いのが利点です。
政令指定都市の区
「横浜市西区」のように市の後にさらに区が続く
郡を含む住所
「〇〇郡△△町」のように郡と町がセットで入る
番地と建物名の境目
「1-2-3 ○○ビル301」のように区切りが曖昧
表記ゆれ
全角・半角、ハイフンの種類、余分な空白が混在する
この記事では、GASからClaude APIを呼び出し、住所をまとめて分割してスプレッドシートに書き戻す実装を組み立てます。
APIキーを安全に保管する
APIキー(AIサービスを利用するための秘密の鍵)はコードに直接書かず、スクリプトプロパティ(GASにキーと値を安全に保存する領域)に入れます。共有時にキーが漏れるのを防げます。
// スクリプトプロパティにAPIキーを保存(初回に一度だけ実行)
function setApiKey() {
PropertiesService.getScriptProperties()
.setProperty("CLAUDE_API_KEY", "sk-ant-xxxxxxxx");
}
function getApiKey() {
const key = PropertiesService.getScriptProperties()
.getProperty("CLAUDE_API_KEY");
if (!key) throw new Error("CLAUDE_API_KEY が未設定です");
return key;
}住所の表記ゆれを正規化する
AIに渡す前に、全角英数字を半角へ、各種ハイフンや長音を半角の-にそろえておくと、分割精度が安定し、後段のシステムへの取り込みも楽になります。
// 住所の表記ゆれをAIに渡す前にそろえる
function normalizeAddress(value) {
return String(value)
.trim()
// 全角英数字を半角へ
.replace(/[A-Za-z0-9]/g, (s) =>
String.fromCharCode(s.charCodeAt(0) - 0xfee0)
)
// 各種ハイフン・長音を半角ハイフンに統一
.replace(/[−ー‐―-]/g, "-")
// 連続する空白を1つに
.replace(/[\s\u3000]+/g, " ");
}住所をまとめてAIに渡すコード
住所を1件ずつ送るとAPI呼び出しが増えて遅くなります。複数件を番号付きで1つのプロンプトにまとめ、入力順を保ったJSON配列で返すよう指示します。出力形式を例で示すのが安定のコツです。
// 住所の配列をまとめてClaude APIに渡し、分割結果を受け取る
function splitAddresses(addresses) {
const prompt =
"次の日本の住所を、都道府県・市区町村・それ以降(番地や建物名)に分割してください。\n" +
"政令指定都市の区は市区町村に含めます。郡がある場合は市区町村に含めます。\n" +
"入力順を保ったまま、次の形式のJSON配列だけを返してください。前後に説明文は不要です。\n" +
'[{"pref":"都道府県","city":"市区町村","rest":"それ以降"}]\n\n' +
"住所リスト:\n" +
addresses.map((a, i) => (i + 1) + ". " + a).join("\n");
const payload = {
model: "claude-sonnet-4-20250514",
max_tokens: 1500,
messages: [{ role: "user", content: prompt }],
};
const res = UrlFetchApp.fetch("https://api.anthropic.com/v1/messages", {
method: "post",
contentType: "application/json",
headers: {
"x-api-key": getApiKey(),
"anthropic-version": "2023-06-01",
},
payload: JSON.stringify(payload),
muteHttpExceptions: true,
});
if (res.getResponseCode() !== 200) {
throw new Error("API エラー: " + res.getContentText());
}
const text = JSON.parse(res.getContentText()).content[0].text;
return extractJsonArray(text);
}※ モデル名(model)は利用可能な最新のものを公式ドキュメントで確認して指定してください。
壊れたJSONに備えて配列を取り出す
AIが前後に説明文を付けてくることがあります。レスポンスから最初の[と最後の]の範囲だけを切り出してからJSON.parseすると、余計なテキストが混ざっても壊れにくくなります。
// レスポンスから配列部分だけを安全に取り出す
function extractJsonArray(text) {
const start = text.indexOf("[");
const end = text.lastIndexOf("]");
if (start === -1 || end === -1) {
throw new Error("JSON配列が見つかりません: " + text);
}
return JSON.parse(text.substring(start, end + 1));
}分割結果の妥当性をチェックする
自動処理では「間違いを見つけられる仕組み」が重要です。都道府県が47都道府県のいずれかに一致し、市区町村が空でないかを簡易チェックし、怪しい行には「要確認」フラグを立てます。
// 47都道府県リストで妥当性を簡易チェック
const PREFS = [
"北海道","青森県","岩手県","宮城県","秋田県","山形県","福島県",
"茨城県","栃木県","群馬県","埼玉県","千葉県","東京都","神奈川県",
"新潟県","富山県","石川県","福井県","山梨県","長野県","岐阜県",
"静岡県","愛知県","三重県","滋賀県","京都府","大阪府","兵庫県",
"奈良県","和歌山県","鳥取県","島根県","岡山県","広島県","山口県",
"徳島県","香川県","愛媛県","高知県","福岡県","佐賀県","長崎県",
"熊本県","大分県","宮崎県","鹿児島県","沖縄県",
];
function checkResult(r) {
return r && PREFS.indexOf(r.pref) !== -1 && r.city;
}バッチ処理でシート全体を分割する
A列の住所を10件ずつまとめて処理し、B〜D列に分割結果、E列に状態(完了/要確認)を書き込みます。E列が埋まっている行はスキップするので、途中で止まっても再実行で続きから処理できます。
// A列: 住所 / B列: 都道府県 / C列: 市区町村 / D列: それ以降 / E列: 状態
function runAddressSplit() {
const sheet = SpreadsheetApp.getActive().getSheetByName("住所");
const last = sheet.getLastRow();
if (last < 2) return;
const values = sheet.getRange(2, 1, last - 1, 5).getValues();
const BATCH = 10;
for (let i = 0; i < values.length; i += BATCH) {
// 未処理(E列が空)で住所がある行だけを集める
const chunk = [];
for (let j = i; j < Math.min(i + BATCH, values.length); j++) {
if (values[j][0] && !values[j][4]) {
chunk.push({ row: j, address: normalizeAddress(values[j][0]) });
}
}
if (chunk.length === 0) continue;
try {
const results = splitAddresses(chunk.map((c) => c.address));
chunk.forEach((c, idx) => {
const r = results[idx] || {};
const rowNum = c.row + 2;
sheet.getRange(rowNum, 2).setValue(r.pref || "");
sheet.getRange(rowNum, 3).setValue(r.city || "");
sheet.getRange(rowNum, 4).setValue(r.rest || "");
sheet.getRange(rowNum, 5).setValue(checkResult(r) ? "完了" : "要確認");
});
} catch (e) {
Logger.log("バッチ失敗(" + i + "): " + e.message);
}
Utilities.sleep(1000); // レート制限対策
}
}トリガーで定期実行を自動化する
フォームやインポートで住所が継続的に増えるシートには、時間主導トリガー(決めた時刻に関数を自動実行する仕組み)を設定します。未処理行だけを対象にするため、毎回全件を再処理する無駄がありません。
// 毎日深夜に未処理の住所だけを自動で分割する
function createDailyTrigger() {
ScriptApp.newTrigger("runAddressSplit")
.timeBased()
.everyDays(1)
.atHour(2)
.create();
}まとめ
住所の分割は、都道府県までは正規表現、それ以降はAIという役割分担ではなく、AIにまとめて任せると例外に強くなります。ポイントは、送る前の正規化・壊れたJSONへの備え・結果の妥当性チェックの3つです。
バッチ処理と処理済みスキップ、定期トリガーを組み合わせれば、増え続ける住所データを人手をかけずにきれいな状態で保てます。重要な住所は「要確認」を目視してから反映する運用にすると安全です。
よくある質問
都道府県までは正規表現でも分割できますが、市区町村より後ろは「郡」「政令指定都市の区」「番地とビル名の境目」など例外が多く、正規表現だけで正確に分けるのは困難です。AIに文脈ごと判断させると、こうした例外を含む多様な書式にも柔軟に対応できます。
プロンプトで出力形式を明示し、キー名と例を示すと安定します。それでも前後に説明文が混ざる場合があるため、レスポンスから最初の [ と最後の ] の範囲を切り出してからJSON.parseする防御的な処理を入れておくと安全です。パースに失敗した行はスキップしてログに残します。
AIに渡す前に全角英数字・全角ハイフンを半角へ寄せる正規化を通しておくと、分割精度が安定し、後段のシステムへの取り込みも楽になります。番地の「−(全角マイナス)」「ー(長音)」「‐」などをすべて半角の - に統一する処理を関数化しておくのがおすすめです。
1回のAPI呼び出しで10件前後をまとめて渡すバッチ処理にすると、リクエスト回数を抑えつつGASの6分実行時間制限に収まりやすくなります。数千件規模の場合は、処理済み列で未処理行だけを対象にし、時間主導トリガーで小分けに実行する構成が安定します。
都道府県が47都道府県のいずれにも一致しない、市区町村が空になっているなど、明らかにおかしい結果には「要確認」フラグを立てて別列に出力しておくと、後から人がまとめて目視チェックできます。重要な住所は自動反映せず確認前提の運用にするのが安全です。
AI×GASでデータ整形を
自動化する。
住所の分割・名寄せ・表記統一など、手作業になりがちなデータ整形をClaude APIとGASで自動化するご相談を承っています。