Duplicate keys are one of the most common reasons a product CSV import fails or silently overwrites data. Think of a Shopify export where two rows share the same Handle, or a contact list with the same email written as Anna@x.de and anna@x.de.
Here is a small script that reports which values of a chosen column appear more than once, and on which line numbers. It uses only the standard library.
import csv, sys from collections import defaultdict def find_duplicates(path, key): seen = defaultdict(list) with open(path, newline="", encoding="utf-8-sig") as f: for line_no, row in enumerate(csv.DictReader(f), start=2): value = (row.get(key) or "").strip().lower() if value: seen[value].append(line_no) return {k: v for k, v in seen.items() if len(v) > 1} if __name__ == "__main__": path, key = sys.argv[1], sys.argv[2] dupes = find_duplicates(path, key) for value, lines in sorted(dupes.items()): print(f"{value}: lines {', '.join(map(str, lines))}") print(f"{len(dupes)} duplicate value(s) found") sys.exit(1 if dupes else 0) Usage
python dupes.py products.csv Handle I tested it on this small file:
Handle,Title mug-red,Red Mug Mug-Red ,Red Mug 2 tee-blue,Blue Tee TEE-BLUE,Tee hat,Hat Output:
mug-red: lines 2, 3 tee-blue: lines 4, 5 2 duplicate value(s) found Why it works this way
-
utf-8-sigstrips the byte order mark that Excel adds, so the first column name matches. -
strip().lower()catches duplicates that differ only by case or stray spaces. Remove.lower()if your keys are case-sensitive. - Line numbers start at 2 because line 1 is the header, so they match what you see in a spreadsheet.
- The exit code is 1 when duplicates exist, so you can use the script as a check before an import in a shell script or CI job.
It only reports; it never changes your file. Decide yourself which row to keep.
I build small CSV and Python tools like this on request, for example for Shopify product data.



