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-sig strips 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.