Merge CSV files with Python without mixing up their columns
Combine matching CSV exports into one file, check headers and row counts, and stop on incompatible input while keeping the original files intact.
In this guide
If you have several exports of the same report, you can put their rows into one CSV with a single header. The important part is checking that every file has the same columns before writing the result.
This guide appends rows: Monday’s orders, followed by Tuesday’s orders. It does not join an orders file to a customer file by an ID, add totals, or remove duplicates. You will get a new file and a row count, while keeping the originals.
Choose your exports
Use a computer with Python 3.12 or newer, a terminal, and a plain-text editor. No packages or server are needed. Check Python with py --version in Windows PowerShell or python3 --version in macOS/Linux Terminal. If it is missing, use the official Python downloads and reopen the terminal before continuing.
Create a folder named csv-merge. Put copies of your small UTF-8, comma-separated exports there. Each file must start with the same non-empty, unique column names in the same order. Completely blank lines are ignored; empty cells are preserved.
For a first run without your own exports, save these two fictional files as UTF-8 plain text. In Notepad, choose All files when saving so the names do not gain .txt.
monday.csv:
order_id,item,quantity
1001,"Notebook, blue",2
1002,Café mug,1
tuesday.csv:
order_id,item,quantity
1003,Pen,4
The result should have three records, not four: the second header is not an order.
Save one merge script
In the same folder, save this complete code as merge_csv.py:
import argparse
import csv
import sys
from pathlib import Path
sys.stdout.reconfigure(errors="backslashreplace")
parser = argparse.ArgumentParser(description="Append matching CSV exports.")
parser.add_argument("output")
parser.add_argument("inputs", nargs="+")
args = parser.parse_args()
try:
paths = [Path(name).resolve() for name in args.inputs]
target = Path(args.output).resolve()
if len(paths) < 2 or len(set(paths)) != len(paths):
raise ValueError("Choose at least two different input files.")
if target in paths or target.exists():
raise ValueError("Choose a new output filename, not an existing file.")
header = None
records = []
counts = []
for path in paths:
with path.open(encoding="utf-8-sig", newline="") as source:
reader = csv.reader(source, strict=True)
names = next(reader, None)
if not names or any(not name.strip() for name in names):
raise ValueError(f"{path.name}: missing column names.")
if len(names) != len(set(names)):
raise ValueError(f"{path.name}: repeated column names.")
if header is None:
header = names
if names != header:
raise ValueError(f"{path.name}: columns or their order differ.")
count = 0
for row in reader:
if not row:
continue
if len(row) != len(header):
raise ValueError(f"{path.name}: wrong cell count near line {reader.line_num}.")
records.append(row)
count += 1
counts.append((path.name, count))
# Create only after all inputs pass; 'x' refuses to overwrite a file.
with target.open("x", encoding="utf-8", newline="") as output:
writer = csv.writer(output)
writer.writerow(header)
writer.writerows(records)
for name, count in counts:
print(f"{name}: {count} records")
print(f"Wrote {len(records)} records to {target.name}")
except (OSError, UnicodeError, csv.Error, ValueError) as error:
sys.exit(f"Merge failed: {error}")
The script checks every input before creating the output. It keeps the rows in memory, so use it for small exports that comfortably fit your computer’s available memory, not multi-gigabyte datasets.
Run the merge and check the result
Open your terminal and enter cd "your folder path", replacing the quoted text with the full path to csv-merge. Then run:
python3 merge_csv.py combined.csv monday.csv tuesday.csv
On Windows, use py instead of python3. The first filename is the new output; the remaining filenames are inputs in the order you want their rows. For your own files, replace the names and add further input filenames at the end. Quote any filename containing spaces.
Expected report for the example:
monday.csv: 2 records
tuesday.csv: 1 records
Wrote 3 records to combined.csv
Open combined.csv in your text editor. It should have one header and orders 1001, 1002, and 1003, in that order. Check that Notebook, blue remains quoted as one cell. Compare the reported total with the sum of the input counts before using the file elsewhere.
A header-only input contributes zero records; a completely empty file is rejected because it has no header. If the total is unexpectedly zero, investigate your exports rather than treating the new file as a completed report.
Fix an input problem without hiding it
If the script reports different columns, compare the first row of each input. Even the same names in a different order are rejected. Export the same report layout again, or deliberately put columns into the same order in a spreadsheet and export a new CSV copy. Do not just rename the header: that leaves the values under the wrong names.
For a wrong cell count, check quotation marks around values containing commas. For an encoding error, export UTF-8. If unsure, check one CSV’s columns first.
If combined.csv already exists, choose a fresh output name such as combined-v2.csv. This avoids accidentally overwriting a previous result or feeding it back into the merge. The script does not choose files automatically, so list only the original exports you intend to combine.
If saving fails or you interrupt the program before its success message, the new output may be incomplete. Leave the originals alone and rerun with a new output name after fixing the cause, such as insufficient disk space.
Merging does not prove that every order is unique. If your date ranges overlap, check duplicates using a clear record key before importing the result.
Sources and useful links
- Python CSV reference — reading and writing CSV while preserving cell boundaries.
- Python file opening modes — explains the exclusive
xmode that refuses an existing output file. - Python paths — reference for identifying input and output paths.