はじめに:現場の「APIないです」に絶望するな
SIerや受託開発の現場で、こんな会話をしたことはありませんか?
クライアント: 「受注業務を自動化(DX)したいんです!」
エンジニア: 「承知しました。基幹システムのAPI仕様書をいただけますか?」
クライアント: 「API……? うちは毎日、取引先からメールでExcelが送られてくるだけですが……」
ここで「APIがないと連携できません」「RPAでやるしかないですね(高額)」と答えるのが、普通のエンジニアです。
しかし、我々「実装屋」の回答は一つです。
「大丈夫です。そのExcel、AI(LLM)を使って無理やりAPI化しましょう」
今回は、セル結合や表記揺れだらけの「謎Excel」を、n8nとOpenAI API (GPT-4o) を使って綺麗なJSONに整形し、データベース(KintoneやSupabase)に格納する 「泥臭い実装術」 を共有します。
敵を知る:今回のラスボス「謎Excel」
今回相手にするのは、人間が見るためだけに作られた、システム連携を拒絶するExcelファイルです。
| 問題 | 具体例 |
|---|---|
| セル結合 | 商品カテゴリが見出しとして結合されている |
| 表記揺れ | 「(株)」「株式会社」「㈱」が混在 |
| 謎の余白 | 印刷レイアウトのためにA列が空いている |
| 備考の自由記述 | 「※大至急!」「午前中着で」などが手入力 |
これをPython(pandas)や従来のプログラムでパースしようとすると、例外処理の嵐で死にます。
そこで、「構造化のプロ」であるLLMの出番です。
アーキテクチャ:n8nが「接着剤」になる
サーバーレスで構築します。n8nをハブにすることで、メール受信からデータ保存までをノーコード(ローコード)で完結させます。
コスト見積もり(月1,000件処理の場合):
- n8n Cloud: ¥2,500/月(セルフホストなら無料)
- OpenAI API: ¥3,000-5,000/月(GPT-4o使用)
- 合計: ¥5,500-7,500/月(RPAツールの1/10以下)
実装ステップ
1. メール監視と添付ファイル取得
n8nの Gmail Trigger ノードを使います。
| 設定 | 値 |
|---|---|
| Filter | Subject Contains 【注文書】
|
| Download Attachments | true |
これで、メールが届いた瞬間にワークフローが起動し、Excelファイルがバイナリデータとしてn8n内に取り込まれます。
2. Excelを「LLMが読める形」にする
ここがポイントです。ExcelバイナリをそのままLLMには投げられません。
n8nの Spreadsheet File ノードを使って、一度「生のデータ(配列)」として読み込みます。
その後、データを軽量化するために、必要なカラムだけを抽出したCSVテキストや、マークダウンの表形式に変換します。
// Code NodeでCSV→Markdownテーブルに整形
const rows = $input.all()[0].json.data;
const headers = Object.keys(rows[0]);
// 最初の20行のみ抽出(トークン節約)
const limitedRows = rows.slice(0, 20);
const mdTable = [
`| ${headers.join(' | ')} |`,
`| ${headers.map(() => '---').join(' | ')} |`,
...limitedRows.map(row => `| ${headers.map(h => row[h] || '').join(' | ')} |`)
].join('\n');
return [{ json: { markdown_table: mdTable, total_rows: rows.length } }];
3. LLMによる「構造化と正規化」
ここが本記事のハイライトです。
OpenAI Chat Model ノードを使い、ぐちゃぐちゃのテキストデータを「理想的なJSON」に変換させます。
プロンプト例:
あなたは優秀なデータ入力オペレーターです。
以下のテキストデータは、フォーマットが崩れた注文書のExcelから抽出したものです。
これを以下のJSONスキーマに従って構造化し、JSONのみを出力してください。
【ルール】
1. セル結合されている「カテゴリ」は、下位の商品行にも適用すること
2. 会社名の「(株)」「㈱」などは「株式会社」に統一すること
3. 数量が「1ケース」「2箱」などの場合、数値のみ(1, 2)を抽出し、unitに単位を入れること
4. 「備考」にある配送希望時間は `delivery_time` フィールドに入れること
5. 「※大至急」などの表現は urgency: "high" として抽出すること
6. 日付はISO 8601形式(YYYY-MM-DD)に統一すること
【出力JSONスキーマ】
```json
[
{
"order_id": "受注番号(なければnull)",
"customer_name": "正規化された会社名",
"order_date": "YYYY-MM-DD",
"items": [
{
"category": "カテゴリ名",
"product_name": "商品名",
"quantity": 数値,
"unit": "単位",
"price": 単価(数値)
}
],
"delivery_time": "配送希望時間",
"urgency": "high/normal/low",
"note": "その他特記事項"
}
]
JSONモードの活用:
OpenAIノードの設定で、**Response Formatを JSON Object** に必ず設定しましょう。
{ "type": "json_object" }
これがないと、AIが気を利かせて「はい、データを整形しました!」という余計な挨拶文を入れてしまい、後続のノードでパースエラーになります。
温度(Temperature)の設定:
事実抽出なので、創造性は不要です。temperature: 0.1 程度に抑え、再現性を高めます。
出力例:
{
"order_date": "2026-02-18",
"customer": {
"name": "株式会社プロドウガ",
"contact": "山本様"
},
"items": [
{
"category": "文具",
"name": "A4コピー用紙",
"qty": 10,
"unit": "箱",
"unit_price": 2500
},
{
"category": "文具",
"name": "ボールペン(黒)",
"qty": 100,
"unit": "本",
"unit_price": 100
}
],
"delivery": {
"date": "2026-02-20",
"time": "AM",
"urgency": "high"
},
"note": "20日午前中までに必着"
}
GPT-4oであれば、人間が「なんとなく」で理解しているセル結合や文脈を、驚くほどの精度で解釈してくれます。
正規表現で3日かかる処理が、プロンプト3行で終わります。
4. データベースへの格納
LLMから返ってきたのは、美しいJSONデータです。
あとは HTTP Request ノード(Kintone REST API)や Kintone ノードを使って、DBにぶち込むだけです。
Kintoneへの登録例:
// HTTP Requestノードの設定
// Method: POST
// URL: https://{{kintone_domain}}[.cybozu.com/k/v1/record.json](https://.cybozu.com/k/v1/record.json)
// Headers:
// X-Cybozu-API-Token: {{token}}
// Content-Type: application/json
// Body (JSON):
{
"app": 123, // アプリID
"record": {
"order_date": { "value": "{{ $json.order_date }}" },
"company_name": { "value": "{{ $json.customer.name }}" },
"total_amount": { "value": {{ $json.items.reduce((a,b)=>a+b.qty*b.unit_price, 0) }} },
"delivery_date": { "value": "{{ $json.delivery.date }}" },
"raw_json": { "value": JSON.stringify({{ $json }}) } // 元データも保存
}
}
これで、「メールで届くExcel」が「API経由のデータ」と同等の品質になりました。
現場で役立つ「泥臭い」テクニック
PII(個人情報)のマスキング
LLMに顧客データを投げる際、セキュリティが懸念される場合があります。
対策: LLMに投げる前に、n8n内の Code ノードで個人情報をハッシュ化・マスキングします。
// マスキング処理
const data = $input.first().json;
// 電話番号、メールアドレスを正規表現で検出して置換
const maskedText = data.markdown_table
.replace(/\d{2,4}-\d{2,4}-\d{4}/g, '[PHONE]') // 電話番号
.replace(/[\w.-]+@[\w.-]+\.\w+/g, '[EMAIL]'); // メール
// 住所も模糊化(例:東京都渋谷区→東京都[MASK])
const maskedText2 = maskedText.replace(/(...市|...区).*/, '$1[MASK]');
return [{
json: {
masked_data: maskedText2,
original_length: data.markdown_table.length
}
}];
ポイント:
- LLMには「構造化」だけを依頼する
- 返ってきたJSONに対して、後からn8nで本物の個人情報をマージし直す
これなら、OpenAI側に個人情報は渡りません。GDPR・個人情報保護法対応が可能です。
エラーハンドリングと「人間の目」
LLMも万能ではありません。複雑なExcelや手書き文字が入ったファイルは失敗する可能性があります。
推奨フロー:
閾値の設定:
// Ifノードの条件
{
"conditions": [
{
"value": "{{ $json.items.length }}",
"operation": "gt", // greater than
"compare": 0
},
{
"value": "{{ $json.customer.name }}",
"operation": "isNotEmpty"
}
]
}
必須フィールドが欠けている場合は、人間の確認待ち(Human-in-the-loop)に回します。
コスト最適化(PL脳)
GPT-4o-miniでも構造化は十分可能です。精度が必要な場合のみGPT-4oを使い分けます。
| 月間件数 | モデル | 概算コスト(月額) |
|---|---|---|
| 1,000件 | GPT-4o-mini | ¥1,500 - 2,000 |
| 1,000件 | GPT-4o | ¥5,000 - 8,000 |
節約テク: Excelの行数が多い場合、1リクエスト100行程度に分割して並列処理すると、速度とコストのバランスが良くなります。
まとめ:DXの入口は「泥」の中にある
「最新のAPIがないから無理」と言うのは簡単です。
しかし、中小企業の現場には、APIなんてありません。あるのはExcelとメールとFAXだけです。
レガシーな入口(Excel/Mail)と、モダンな出口(DB/Dashboard)を、技術(n8n/LLM)で繋ぐこと。
これこそが、我々エンジニアが提供できる本当の「DX」ではないでしょうか。
もし「謎Excel」との戦いに疲れている方がいれば、ぜひこの構成を試してみてください。世界が変わります。
🔗 参考リンク
著者:@YushiYamamoto
株式会社プロドウガ代表 / フルスタックエンジニア。
「技術的負債は返せるなら背負え」がモットー。n8nとTypeScriptを駆使して、泥臭くも止まらない業務自動化システムを構築しています。
