Excel・CSVを毎回手で結合していませんか?Pythonで1分にする方法(重複削除・列名ゆれ対応)

支店ごとや月ごとに届くCSVやExcelを、毎回コピー&ペーストで1つにまとめていませんか?正直、あの作業って地味に疲れますよね。

  • 列の名前が「日付」「注文日」とファイルごとに違って、貼り付けるたびに直している
  • 同じ注文が複数のファイルに入っていて、重複を目で探している
  • CSVを開いたら文字化けして、保存し直している

💡
この記事で解決すること

フォルダに入れたCSV(UTF-8・Shift_JIS)とExcelを、列名のゆれをそろえて1つにまとめ、重複を除いて保存するPythonスクリプトを、一緒に作っていきます。手作業なら10分以上かかる作業が、実行1回・数秒になります。
所要時間の目安は約20分です。


全体像:読み込み → 列名をそろえる → 結合 → 重複を除く

ファイル結合の流れ

手順 やること 使うもの
1 CSVとExcelを読み込む(文字コードも自動で試す) read_csv / read_excel
2 列名のゆれを正しい名前にそろえる 対応表(辞書)
3 全ファイルを縦に結合する concat
4 重複する行を除く drop_duplicates
5 CSVに保存する to_csv

毎朝のCSVを集計する方法は、こちらの記事で扱っています。今回は「複数ファイルを1つにまとめる」ところに絞って、ゆっくり進めていきます。

Pythonで毎朝のCSV集計を自動化する(pandas+定期実行)


準備:ライブラリと、練習用のフォルダ

1. ライブラリを入れる

ターミナル(Windowsは PowerShell)を開いて、実行してみてください。

  • 1
pip install pandas openpyxl

openpyxl は、Excel(.xlsx)を読み書きするために使います。この記事のコードは、Python 3.13・pandas 3.0.6 で実際に動かして確認しています。

2. 練習用のダミーデータを用意する

作業用のフォルダに data フォルダを作り、次の3つのファイルを入れます。仕事でよくある「列名のゆれ」「文字コードの違い」「重複」を、わざと入れてあります。

orders_tokyo.csv(UTF-8)

  • 1
  • 2
  • 3
  • 4
注文日,氏名,メール,商品,数量
2026/10/01,山田 太郎,taro@example.com,ノートA5,2
2026/10/02,佐藤 花子,hanako@example.com,ペン3本セット,1
2026/10/03,鈴木 一郎,ichiro@example.com,ノートA5,3

orders_osaka.csv(Shift_JIS。Excelで「CSV」として保存したものと同じ形式)

  • 1
  • 2
  • 3
  • 4
日付,お名前,Email,商品名,個数
2026-10-01,山田 太郎, Taro@Example.com ,ノートA5,2
2026-10-04,高橋 次郎,jiro@example.com,付箋セット,5
2026-10-05,伊藤 美咲,misaki@example.com,ペン3本セット,2

orders_web.xlsx(Excel。列は 注文日/名前/メールアドレス/商品/数量。「数量」の後ろに空白が混ざっています)

注文日 名前 メールアドレス 商品 数量
2026-10-02 佐藤 花子 [email protected] ペン3本セット 1
2026-10-06 渡辺 健 [email protected] 付箋セット 1
2026-10-06 渡辺 健 [email protected] 付箋セット 1

dataフォルダの中身(ターミナルで ls -l を実行した画面)

手順1:スクリプトを作る

merge_orders.py という名前で、data フォルダと同じ場所に保存します。コードは長めに見えますが、まずはそのままコピペして動かすところから始めましょう。

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
"""data フォルダ内の CSV / Excel を1つにまとめ、重複を除いて保存するスクリプト"""

import unicodedata
from pathlib import Path

import pandas as pd

# ===== 設定 =====
INPUT_DIR = Path("data")           # まとめたいファイルを入れるフォルダ
OUTPUT_FILE = Path("merged_orders.csv")

# 列名ゆれの対応表:「正しい列名: [ゆれの候補]」
COLUMN_ALIASES = {
    "注文日": ["注文日", "日付", "購入日"],
    "氏名": ["氏名", "お名前", "名前"],
    "メール": ["メール", "メールアドレス", "email", "e-mail"],
    "商品": ["商品", "商品名"],
    "数量": ["数量", "個数", "数"],
}
# 重複とみなす条件(この3列がすべて同じ行は重複)
DUPLICATE_KEYS = ["注文日", "メール", "商品"]
# =================

# 「ゆれの候補 → 正しい列名」の逆引き表を作る
ALIAS_TO_STANDARD = {
    alias.lower(): standard
    for standard, aliases in COLUMN_ALIASES.items()
    for alias in aliases
}


def clean_name(name) -> str:
    """列名の全角・半角や前後の空白をそろえる"""
    return unicodedata.normalize("NFKC", str(name)).strip().lower()


def read_file(path: Path) -> pd.DataFrame:
    """CSV(UTF-8 / Shift_JIS)とExcelを読み込む"""
    if path.suffix.lower() == ".csv":
        for encoding in ("utf-8-sig", "cp932"):
            try:
                return pd.read_csv(path, encoding=encoding)
            except UnicodeDecodeError:
                continue
        raise ValueError(f"{path.name}: 文字コードを判別できませんでした")
    return pd.read_excel(path)  # 先頭のシートを読み込む


def load_one(path: Path) -> pd.DataFrame:
    df = read_file(path)

    # 1) 列名をそろえる
    df.columns = [ALIAS_TO_STANDARD.get(clean_name(c), str(c).strip()) for c in df.columns]

    # 2) 必要な列が足りなければ、ファイル名つきで知らせる
    missing = [c for c in COLUMN_ALIASES if c not in df.columns]
    if missing:
        raise ValueError(f"{path.name}: 列が見つかりません {missing} / 実際の列: {list(df.columns)}")

    # 3) 値をそろえる(日付・メールの表記ゆれ)
    df["注文日"] = pd.to_datetime(df["注文日"], errors="coerce")
    df["メール"] = df["メール"].astype(str).str.strip().str.lower()
    df["氏名"] = df["氏名"].astype(str).str.strip()

    df = df[list(COLUMN_ALIASES)].copy()
    df["元ファイル"] = path.name
    return df


def main() -> None:
    files = sorted(
        p for p in INPUT_DIR.iterdir()
        if p.suffix.lower() in {".csv", ".xlsx"} and not p.name.startswith(("~$", "."))
    )
    if not files:
        print(f"{INPUT_DIR} にCSV/Excelがありません")
        return

    frames = [load_one(p) for p in files]
    for p, f in zip(files, frames):
        print(f"読み込み:{p.name}({len(f)}行)")

    merged = pd.concat(frames, ignore_index=True)
    before = len(merged)

    # 日付が読めなかった行は確認用に知らせる
    bad_dates = merged["注文日"].isna().sum()

    # 重複を除く(最初に出てきた行を残す)
    merged = merged.drop_duplicates(subset=DUPLICATE_KEYS, keep="first")
    merged = merged.sort_values("注文日", kind="stable").reset_index(drop=True)

    merged.to_csv(OUTPUT_FILE, index=False, encoding="utf-8-sig")  # Excelで開いても文字化けしない
    print(f"--- 結合:{before}行 → 重複を除いて {len(merged)}行({before - len(merged)}行削除) ---")
    if bad_dates:
        print(f"※日付を読み取れなかった行が {bad_dates} 行あります")
    print(f"保存しました:{OUTPUT_FILE}")


if __name__ == "__main__":
    main()

コードの読み方(3か所だけ)

全部を理解しなくても大丈夫です。気にしてほしいのは、この3か所です。

1. COLUMN_ALIASES:列名のゆれを登録する

「正しい列名:ゆれの候補」の対応表です。新しいゆれが見つかったら、ここに足すだけでOKです。列名は全角・半角、大文字・小文字、前後の空白をそろえてから比べるので、「Email」「email」「 e-mail 」も同じ扱いになります。

2. read_file:文字コードを順番に試す

CSVはまず UTF-8(utf-8-sig)で読み、失敗したら Shift_JIS(cp932)で読みます。Excelで保存したCSVは Shift_JIS のことが多いため、この順番にしています。

3. drop_duplicates:重複の条件を決める

DUPLICATE_KEYS に指定した列がすべて同じ行を、重複とみなします。今回は「注文日・メール・商品」です。ファイル名順に並べて結合しているので、先に出てきた行が残ります。


手順2:実行して結果を確認する

merge_orders.py があるフォルダで、次のコマンドを実行します(Macは python3)。

  • 1
python merge_orders.py

実際に実行してみた結果は、こんな感じです。

  • 1
  • 2
  • 3
  • 4
  • 5
読み込み:orders_osaka.csv(3行)
読み込み:orders_tokyo.csv(3行)
読み込み:orders_web.xlsx(3行)
--- 結合:9行 → 重複を除いて 6行(3行削除) ---
保存しました:merged_orders.csv

9行が6行になりました!削除された3行は、山田さんの注文(東京と大阪の両方にある)、佐藤さんの注文(東京とExcelの両方にある)、渡辺さんの注文(Excel内の二重入力)です。山田さんは、メールの大文字・空白をそろえたことで、同じ注文として判定されています。

保存された merged_orders.csv の中身はこうなります。

注文日 氏名 メール 商品 数量 元ファイル
2026-10-01 山田 太郎 [email protected] ノートA5 2 orders_osaka.csv
2026-10-02 佐藤 花子 [email protected] ペン3本セット 1 orders_tokyo.csv
2026-10-03 鈴木 一郎 [email protected] ノートA5 3 orders_tokyo.csv
2026-10-04 高橋 次郎 [email protected] 付箋セット 5 orders_osaka.csv
2026-10-05 伊藤 美咲 [email protected] ペン3本セット 2 orders_osaka.csv
2026-10-06 渡辺 健 [email protected] 付箋セット 1 orders_web.xlsx

実行すると、9行が重複を除いて6行になった
できあがった merged_orders.csv を表計算ソフトで開いた画面(Excelでも同じように開けます)

「元ファイル」列があるので、どの行がどのファイルから来たのかを後から追えるので、ちょっと安心です。保存時に utf-8-sig を指定しているので、Excelで開いても文字化けしません。


自分のデータに合わせて変える

変えたいこと 変更する場所
まとめたいフォルダ INPUT_DIR
保存するファイル名 OUTPUT_FILE
列名のゆれを増やす COLUMN_ALIASES に候補を足す
重複とみなす条件 DUPLICATE_KEYS(例:注文番号の列だけ)
列そのものを増やす COLUMN_ALIASES に新しい列を追加する

つまずきやすい点

エラーが出ても慌てなくて大丈夫です。まずはこの表を見てみてください。

症状 原因 対処
列が見つかりません というエラーが出る 列名のゆれが対応表にない エラーに出るファイル名と実際の列名を見て、COLUMN_ALIASES に足す
ModuleNotFoundError: No module named 'openpyxl' Excelを読むライブラリが未導入 pip install openpyxl を実行する
古い形式の .xls が読めない このスクリプトは .xlsx と .csv だけ対象 Excelで .xlsx に保存し直す
日付が空欄になる 日付として読み取れない文字が入っている 実行時に「日付を読み取れなかった行」の件数が表示されるので、元ファイルを確認する
重複が消えない 比べる列の表記ゆれ(空白・大文字小文字など) 比較する列を、読み込み時にそろえる(メールのように)
Excelのファイルを開いたまま実行して失敗する 一時ファイル(~$ で始まる)が混ざる 閉じてから実行する。~$ のファイルはスクリプト側で除外済み

まとめ

  • 列名のゆれは、対応表(辞書)に集めると、あとから足しやすい
  • 文字コードは UTF-8 → Shift_JIS の順に試し、保存は utf-8-sig にしておく
  • 重複の条件(どの列が同じなら重複か)は、自分のデータに合わせて決める

次の一歩は、手元のCSVを2〜3個コピーして data フォルダに入れ、COLUMN_ALIASES に自分の列名を足して実行してみることです。最初は小さく試すのが、僕のおすすめです。


関連する本・記事

ファイルが増えて処理が重くなってきたときの次の一歩として、Pythonを速くする考え方を扱う『爆速Pythonの作り方』もあります。