1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

フォーマットがバラバラな複数CSVをn8nで自動統合する【実装編】— ノード設定とCodeノードを全公開

1
Posted at

はじめに

前回の設計編では、バラバラなCSVをn8nで統合するアーキテクチャを解説した。

今回はその続き。実際にn8nの画面上でどのようにノードを設定し、どんなJavaScriptを書けば動くのか、コードをそのままコピペして動かせるレベルで公開する。

動作確認環境:n8n v1.85.4(self-hosted)、Node.js 22.x


前回のおさらい:今回実装するパイプライン

使用するサンプルデータは前回と同じ3種類のCSVだ。

検査機 キー列名 判定表記
検査機A(外径) ロット番号 OK / NG
検査機B(硬度) lot_id PASS / FAIL
検査機C(内径) ロットID 合格 / 不合格

実装:5ステップのノード設定

Step 1. Folder Trigger — ファイル検知

n8nのノード追加画面で「Local File Trigger」を選択する。

設定項目:

項目 設定値
Watch Folder /data/csv_input
Events File Added
File Extension Filter csv

self-hostedの場合、n8nが起動しているサーバー上のパスを指定する。Docker環境ではボリュームマウントを忘れずに。

このノードが発火すると、後続ノードに {{ $json.path }} としてファイルパスが渡される。


Step 2. CSVパースと列名の正規化 — Codeノード

ここが最も重要なステップだ。Codeノード(JavaScript) を1つ用意し、以下の処理を一括で行う。

  • CSVファイルを読み込む
  • どの検査機のデータかをファイル名で判定する
  • 列名を共通スキーマに正規化する
  • 判定表記を OK / NG に統一する
// Step2: CSVパース + 列名正規化
const fs = require('fs');
const path = require('path');

// 前ノードから受け取ったファイルパス
const filePath = $input.first().json.path;
const fileName = path.basename(filePath);
const raw = fs.readFileSync(filePath, 'utf-8');

// CSVをオブジェクト配列にパース
function parseCsv(text) {
  const lines = text.trim().split('\n');
  const headers = lines[0].split(',').map(h => h.trim());
  return lines.slice(1).map(line => {
    const values = line.split(',').map(v => v.trim());
    return Object.fromEntries(headers.map((h, i) => [h, values[i]]));
  });
}

// 検査機ごとの列マッピング定義
const SCHEMA_MAP = {
  'machine_a': {
    lotKey: 'ロット番号',
    judgeKey: '判定',
    judgeMap: { 'OK': 'OK', 'NG': 'NG' },
    valueKey: '外径(mm)',
    outputKey: '外径判定',
  },
  'machine_b': {
    lotKey: 'lot_id',
    judgeKey: 'result',
    judgeMap: { 'PASS': 'OK', 'FAIL': 'NG' },
    valueKey: 'hardness',
    outputKey: '硬度判定',
  },
  'machine_c': {
    lotKey: 'ロットID',
    judgeKey: '合否',
    judgeMap: { '合格': 'OK', '不合格': 'NG' },
    valueKey: '内径(mm)',
    outputKey: '内径判定',
  },
};

// ファイル名から検査機を特定
function detectMachine(name) {
  if (name.includes('machine_a')) return 'machine_a';
  if (name.includes('machine_b')) return 'machine_b';
  if (name.includes('machine_c')) return 'machine_c';
  throw new Error(`未知のファイル: ${name}`);
}

const machineKey = detectMachine(fileName);
const schema = SCHEMA_MAP[machineKey];
const rows = parseCsv(raw);

// 共通スキーマへ正規化
const normalized = rows.map(row => ({
  ロット番号: row[schema.lotKey],
  [schema.outputKey]: schema.judgeMap[row[schema.judgeKey]] ?? 'NG',
  生データ: JSON.stringify(row), // 原本保持(障害調査用)
  検査機: machineKey,
}));

return normalized.map(item => ({ json: item }));

設計のポイント:

列マッピングを SCHEMA_MAP オブジェクトに外部化している。新しい検査機が増えても、このオブジェクトにエントリを追加するだけで対応できる。「コードではなくデータを変える」設計だ。

また 生データ として元のCSV行をJSON文字列で保持している。後から「なぜこのロットはNGになったのか」を追跡できるようにするためだ。


Step 3. Mergeノード — ロット番号で結合

3つの検査機からのデータをn8nの Mergeノード で結合する。

設定:

項目 設定値
Mode Combine
Combination Mode Multiplex
Join Field(左) ロット番号
Join Field(右) ロット番号

n8n v1.x以降、Mergeノードの設定UIが変更されている。「Combine」→「By Key」を選ぶと、指定したキーで行を突合できる。

このノードの出力は、1ロットあたり1行に集約されたJSONになる。

{
  "ロット番号": "LOT-2026-001",
  "外径判定": "OK",
  "硬度判定": "OK",
  "内径判定": "NG"
}

Step 4. 総合判定ロジック — Codeノード

// Step4: 総合判定
const items = $input.all();

return items.map(item => {
  const d = item.json;

  const allOk =
    d['外径判定'] === 'OK' &&
    d['硬度判定'] === 'OK' &&
    d['内径判定'] === 'OK';

  // どの検査でNGが出たかを記録(後工程でのトレーサビリティ確保)
  const ngItems = ['外径判定', '硬度判定', '内径判定']
    .filter(key => d[key] === 'NG');

  return {
    json: {
      ...d,
      総合判定: allOk ? 'OK' : 'NG',
      NG項目: ngItems.join(', ') || '-',
      処理日時: new Date().toISOString(),
    },
  };
});

なぜNG項目を記録するのか:

「総合判定NG」だけでは、現場担当者が「どの検査機のどの項目で落ちたか」を再度確認しに行かなければならない。NG項目 カラムを持たせることで、Google Sheetsを見るだけで原因が特定できる。


Step 5. Google Sheetsノード — 書き込み

設定:

項目 設定値
Operation Append Row
Spreadsheet ID (対象のシートID)
Sheet Name 統合品質DB
Columns ロット番号, 外径判定, 硬度判定, 内径判定, 総合判定, NG項目, 処理日時

Google Sheets APIの書き込みは1秒あたり約60リクエストの上限がある。大量バッチ処理時はSplit In Batchesノードで分割するか、Spreadsheets Append(配列一括書き込み)を使うこと。


完成後の動作確認

以下の3ファイルを取込フォルダに投入してテストする。

machine_a_20260320.csv

ロット番号,外径(mm),判定
LOT-001,12.05,OK
LOT-002,11.87,NG
LOT-003,12.01,OK

machine_b_20260320.csv

lot_id,hardness,result
LOT-001,58.2,PASS
LOT-002,57.9,PASS
LOT-003,61.0,PASS

machine_c_20260320.csv

ロットID,内径(mm),合否
LOT-001,8.01,合格
LOT-002,8.03,合格
LOT-003,7.94,不合格

期待される出力(Google Sheets):

ロット番号 外径判定 硬度判定 内径判定 総合判定 NG項目
LOT-001 OK OK OK OK -
LOT-002 NG OK OK NG 外径判定
LOT-003 OK OK NG NG 内径判定

LOT-002は外径でNG、LOT-003は内径でNG。それぞれの原因が1カラムで特定できる。


本番運用で追加すべき3つの処理

上記の実装はハッピーパスのみだ。本番で安定稼働させるには以下を追加する。

1. ファイル二重取り込みの防止

同じファイルを2回投入した場合の処理を定義する。処理済みファイルをアーカイブフォルダに移動するか、ロット番号の重複チェックを入れる。

2. パースエラー時の通知

CSVの列が増減した、文字コードがShift-JISだったなど、予期しないフォーマット変更は現場では起きる。エラー時にSlack通知を飛ばすError Triggerノードを繋いでおく。

3. 処理ログの保持

どのファイルをいつ処理したかを別シートに記録する。障害調査と監査対応の両方に使える。


まとめ

今回実装したポイントを振り返る。

ステップ 実装のキモ
列名正規化 SCHEMA_MAPで検査機ごとのマッピングを外部化
結合 Mergeノードをロット番号キーで突合
総合判定 NG項目を明示してトレーサビリティを確保
書き込み Google Sheetsにリアルタイム追記

設計編で「1年以内に回収可能」と書いたが、実際の回収期間は現場の手作業時間によって変わる。本記事のコードをそのまま動かして、まず自社データで検証してほしい。

次回は 「n8n × LangChainで、統合されたデータから異常検知アラートを自動生成する」 実装を予定している。


この記事を書いた人 ✏️ @YushiYamamoto
株式会社プロドウガ CEO / AIアーキテクト
製造業・EC・SaaSの業務自動化設計を専門としています。
n8n・Supabase・Claude Codeを活用した自律型アーキテクチャの構築事例を発信中。

技術的な相談やシステム設計の壁打ちは、記事下部のコメント欄か、プロフィールのリンクからどうぞ。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?