導入(問題提起)

Excelで長年運用してきた台帳や受発注、顧客リストを業務システムに取り込みたい。しかし、いざ取り込もうとすると「列が毎回変わる」「見出しが2段」「結合セルでレコードが崩れる」「同じ顧客が3通りの表記」など、移行前に“地ならし”が必要になります。ここでつまずくと、Web移行プロジェクトは簡単に遅延・品質劣化します。

本記事は、小規模〜中小規模のWebシステム開発において、Excel資産を失敗なく取り込むための現実的な手順と、Pythonで作る最小の取込・クレンジング基盤を解説します。キーワードは「受入仕様」「一時テーブル」「自動検証」「名寄せ」「再実行可能」です。

課題の詳細説明

Excel由来データの“よくある困りごと”を体系化します。

  • 構造の揺れ
  • - 列名・順序が月ごとに変わる、見出しが2段、メモ列が途中に挟まる - 結合セルや空行、罫線ベースのレイアウトで表が複数存在

  • データ型の不整合
  • - 数値が文字列化、日付がシリアル/文字/和暦の混在、小数点やカンマ問題

  • マスタ未整備
  • - 部署名/商品名/取引先名の表記ゆれ(例:株式会社有→(株)有→有) - 郵便番号・電話・メールなどフォーマット崩れ

  • 参照整合の欠落
  • - 外部キーがなく“名前”で紐づけている、同名異人/異社の混在

  • 運用課題
  • - 取り込みのたびに人手修正が発生、ミスが埋もれる、リトライが面倒

解決方法

次の原則で“壊れにくい”移行基盤を作ります。

  1. 受入仕様書を作る
  2. - 列/型/必須/一意/参照元を定義。Excel提出者にも共有し、将来の揺れを抑制。

  3. 物理前処理:Excel→CSVへ正規化
  4. - ヘッダ1行、データ開始行を固定。結合セルは展開、空行は削除。

  5. クリーニングの標準化
  6. - 欠損補完(既定値/推定)、前後空白トリム、全角半角統一、改行除去 - 正規表現で電話/郵便番号/メール形式を検証し、正規化

  7. 名寄せと参照整合
  8. - 表記ゆれ辞書(“(株)有”→“株式会社有”)を用意し、辞書にないものは“仮マスタ”に退避 - マスタ未登録は一時IDを払い出し、後段の人手確認で確定

  9. 段階ロード
  10. - アップロード→一時テーブル→検証/差分表示→本番反映の順で段階適用

  11. ログ/検証レポート
  12. - 取込件数、スキップ理由、重複ID、型変換件数を記録。毎回ダウンロード可能に

  13. 技術基盤
  14. - Python Webアプリでジョブ化(Pandas/SQLite→将来はPostgreSQL)。再実行可能なスクリプトにする

具体例

以下は実務で“効く”正規化・検証パターンです。

  • 電話番号:[^0-9]除去→10/11桁のみ許可→市外局番/携帯の先頭桁で判定
  • 郵便番号:\d{3}-?\d{4}に正規化→外部APIで住所補完(任意)
  • メール:RFC準拠の簡易検証→ドメインMX確認(任意)
  • 日付:datetime.strptimeで標準化→和暦は西暦に変換→タイムゾーン明示
  • 金額:カンマ除去→数値化→負値/桁あふれチェック
  • 商品名:辞書化(別表)→JOINで標準名付与→未知語は仮マスタ

技術的な解説

最小構成で“壊れにくいETL”をPythonで組みます。UIは既存のWebシステム開発(Bottle/FastAPI)にボタンを追加するだけでOKです。

データモデル(SQLite例)


CREATE TABLE import_job (
  id INTEGER PRIMARY KEY,
  filename TEXT NOT NULL,
  status TEXT NOT NULL,      -- uploaded | validated | applied | failed
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE staging_customer (
  job_id INTEGER,
  row_no INTEGER,
  name TEXT, phone TEXT, email TEXT, zip TEXT, address TEXT,
  valid INTEGER, reason TEXT
);

CREATE TABLE customer (
  id INTEGER PRIMARY KEY,
  name TEXT NOT NULL,
  phone TEXT, email TEXT,
  zip TEXT, address TEXT
);

取込の骨子(Pandas)


import pandas as pd
import re

def normalize_phone(s: str) -> str:
    if not isinstance(s, str):
        return ''
    digits = re.sub(r'[^0-9]', '', s)
    return digits if len(digits) in (10, 11) else ''

def normalize_zip(s: str) -> str:
    if not isinstance(s, str):
        return ''
    m = re.match(r'^(\d{3})-?(\d{4})$', s)
    return f"{m.group(1)}-{m.group(2)}" if m else ''

def validate_row(row):
    reasons = []
    if not row['name']:
        reasons.append('name required')
    if row['email'] and '@' not in row['email']:
        reasons.append('email invalid')
    return (len(reasons) == 0, ';'.join(reasons))

def clean_df(df: pd.DataFrame) -> pd.DataFrame:
    df = df.rename(columns={c: c.strip().lower() for c in df.columns})
    # 想定列を抽出(なければ空列を作る)
    cols = ['name','phone','email','zip','address']
    for c in cols:
        if c not in df:
            df[c] = ''
    df['phone'] = df['phone'].map(normalize_phone)
    df['zip'] = df['zip'].map(normalize_zip)
    df['name'] = df['name'].fillna('')
    df['email'] = df['email'].fillna('')
    # 行検証
    flags, reasons = [], []
    for _, r in df.iterrows():
        ok, why = validate_row(r)
        flags.append(1 if ok else 0)
        reasons.append(why)
    df['valid'] = flags
    df['reason'] = reasons
    return df[cols + ['valid','reason']]

ルーティング(Bottle/FastAPI いずれでも)


from bottle import Bottle, request
import pandas as pd

app = Bottle()

@app.post('/import/customer')
def import_customer():
    upload = request.files.get('file')
    df = pd.read_excel(upload.file)  # 事前にテンプレ提供が望ましい
    cleaned = clean_df(df)
    # stagingへ保存 → 検証レポートをUI表示 → OKなら本番へ適用
    job_id = save_staging('customer', cleaned)
    return {'job_id': job_id, 'valid_rate': float(cleaned['valid'].mean())}

名寄せ(表記ゆれ辞書)


CREATE TABLE name_alias (
  key TEXT PRIMARY KEY,   -- 正規化キー(カナ/ローマ字など)
  label TEXT NOT NULL     -- 正式名称
);

辞書はCSVで管理し、運用中も随時追加できるようにします。未知語は“仮マスタ”に蓄積し、後から人が正式名称を割当て、再実行で反映します。

導入の流れ

  1. 受入仕様テンプレを定義し、提出元に配布(列定義/型/禁止事項/サンプル)
  2. 一時テーブルと検証レポートUIを先に作る(本番反映は後回し)
  3. 代表データでPoC(100〜1000行)を実施、検証観点と名寄せ辞書を整備
  4. 本番適用の“条件”を明文化(有効率90%以上/致命エラー0件など)
  5. 定期取込の自動化(スケジュール/ジョブ/通知)。失敗時は差分を保全
  6. 将来のスケール:SQLite→PostgreSQL、キュー導入、非同期化

まとめ

Excelの“揺れるデータ”を前提に、受入→検証→反映の3段構えにすることで、移行は劇的に安定します。まずはPythonで最小のETLを作り、Excel Web化の延長で再実行可能な仕組みを定着させましょう。小さく始め、大きく間違えない——それが中小企業のWebシステム開発を成功させるコツです。

問い合わせ導線

Webシステム開発のご相談は monou まで → /contact