Comment supprimer les emails invalides et les doublons d'un fichier CSV
Pour supprimer les emails invalides et les doublons d'un CSV, mettez la colonne email en minuscules et retirez les espaces superflus, dédoublonnez sur cette valeur nettoyée, écartez les adresses qui échouent au contrôle de syntaxe, puis écartez les domaines sans serveur de messagerie. Excel et Google Sheets savent faire les trois premières étapes ; un court script Python fait les quatre et note pourquoi chaque ligne a été retirée.
Les contrôles de syntaxe et MX repèrent les erreurs de format et les domaines morts. Ils ne peuvent pas vous dire si une boîte mail existe : cela exige une conversation SMTP avec le serveur du destinataire. Considérez cette étape comme le premier passage gratuit, avant une vraie vérification.
Ce que permet chaque méthode
| Tâche | Excel | Google Sheets | Script Python ci-dessous |
|---|---|---|---|
| Minuscules et suppression des espaces | Oui | Oui | Oui |
| Espaces insécables | Nécessite SUBSTITUTE(…, CHAR(160), " ") |
Nécessite aussi SUBSTITUTE |
Gérés |
| Suppression des doublons | Oui | Oui | Oui, garde la première |
| Contrôle de syntaxe | Regex uniquement sur Microsoft 365 | Oui, REGEXMATCH |
Oui |
| Motif de suppression de chaque ligne | Manuel | Manuel | Automatique |
| Contrôle MX | Non | Non | Oui, avec dnspython |
| Existence de la boîte mail | Non | Non | Non |
Pour des fichiers de quelques milliers de lignes nettoyés une seule fois, un tableur suffit. Pour des nettoyages répétés, des fichiers très volumineux ou tout ce qui exige une piste d'audit, utilisez le script.
Dans Excel ou Google Sheets
Normalisez dans une colonne auxiliaire, contrôlez la syntaxe de cette colonne, puis dédoublonnez sur celle-ci. Nous avons des tutoriels complets et testés pour Excel et Google Sheets ; voici la version courte.
- Colonne auxiliaire :
=LOWER(TRIM(SUBSTITUTE(A2, CHAR(160), " "))). LeSUBSTITUTEest là parce que la fonction TRIM d'Excel comme l'outil Supprimer les espaces de Sheets laissent les espaces insécables en place. Dans une version française d'Excel, la même formule s'écrit=MINUSCULE(SUPPRESPACE(SUBSTITUE(A2;CAR(160);" "))); dans Google Sheets avec les paramètres régionaux français, remplacez les virgules par des points-virgules. - Syntaxe :
REGEXMATCHdans Sheets,REGEXTESTdans Excel pour Microsoft 365, avec le motif utilisé dans le script ci-dessous. Les anciennes versions d'Excel nécessitent une formule de repli plus longue qui, lors de notre test, a laissé passer quatre adresses mal formées. - Dédoublonnez sur la colonne auxiliaire avec Supprimer les doublons (Microsoft indique qu'Excel conserve la première occurrence), ou avec
UNIQUEdans une nouvelle feuille. - Filtrez les lignes en échec, puis enregistrez au format CSV.
En Python : un script testé de 20 lignes
Ce script nettoie, contrôle et dédoublonne en un seul passage, et écrit les lignes rejetées dans un fichier séparé, avec un motif. Il n'utilise que la bibliothèque standard de Python.
import csv, re, sys
PATTERN = re.compile(r"^[a-z0-9_%+'-]+(\.[a-z0-9_%+'-]+)*@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*\.[a-z]{2,}$")
src, column = sys.argv[1], sys.argv[2] if len(sys.argv) > 2 else "email"
seen = set()
with open(src, newline="", encoding="utf-8-sig") as f, \
open("clean.csv", "w", newline="", encoding="utf-8") as ok, \
open("rejected.csv", "w", newline="", encoding="utf-8") as bad:
reader = csv.DictReader(f)
good = csv.DictWriter(ok, fieldnames=reader.fieldnames)
rejected = csv.DictWriter(bad, fieldnames=reader.fieldnames + ["reason"])
good.writeheader(); rejected.writeheader()
for row in reader:
email = (row.get(column) or "").replace(" ", " ").strip().lower()
reason = "empty" if not email else "bad syntax" if not PATTERN.match(email) else "duplicate" if email in seen else ""
if reason:
rejected.writerow({**row, "reason": reason})
continue
seen.add(email)
good.writerow({**row, column: email})
print(f"kept {len(seen)} unique addresses")
Lancez-le avec le nom du fichier, et le nom de la colonne si elle ne s'appelle pas email :
python3 clean_emails.py leads.csv
python3 clean_emails.py leads.csv "Email Address"
Il écrit clean.csv (toutes les colonnes d'origine, avec l'email normalisé) et rejected.csv (les lignes d'origine plus une colonne reason : empty pour vide, bad syntax pour syntaxe incorrecte ou duplicate pour doublon).
Ce qui s'est passé quand nous l'avons lancé
Nous avons lancé le script le 1er octobre 2026 (Python 3.14, macOS) sur un fichier de test de 19 lignes, construit à partir des erreurs que l'on retrouve dans de vrais exports. Il a affiché kept 4 unique addresses.
| Entrée | Résultat |
|---|---|
ana.silva@example.com |
Conservée (première occurrence) |
Ana.Silva@Example.com |
Rejetée : duplicate |
ana.silva@example.com + espace final |
Rejetée : duplicate |
ana.silva@example.com + espace insécable |
Rejetée : duplicate |
o'brien@example.co.uk |
Conservée |
jo+news@example.com |
Conservée |
kai@sub.example.io |
Conservée |
ben@example |
Rejetée : bad syntax |
cara@@example.com |
Rejetée : bad syntax |
dev@example..com |
Rejetée : bad syntax |
eve@example,com |
Rejetée : bad syntax |
gus.@example.com |
Rejetée : bad syntax |
max@-example.com |
Rejetée : bad syntax |
ned@example.c |
Rejetée : bad syntax |
mailto:finn@example.com |
Rejetée : bad syntax |
Pia <pia@example.com> |
Rejetée : bad syntax |
ola@example.com;ola2@example.com |
Rejetée : bad syntax |
hana o@example.com |
Rejetée : bad syntax |
| (vide) | Rejetée : empty |
Les quatre dernières lignes rejetées contiennent de vraies adresses entourées de parasites. C'est pour cela que le script écrit les rejets dans un fichier au lieu de les jeter : ouvrez rejected.csv, corrigez à la main ce qui est manifestement corrigeable, et réintégrez ces lignes. Ne « corrigez » pas les cas ambigus. hana o@example.com pourrait être hanao@ ou hana.o@, et deviner crée une adresse que personne ne vous a donnée.
Deux pièges du CSV que le script évite
Découper sur les virgules. La ligne eve@example,com est stockée dans le fichier sous la forme Eve,"eve@example,com", entre guillemets parce qu'elle contient une virgule. Nous avons vérifié : un line.split(",") naïf la transforme en trois champs, ['Eve', '"eve@example', 'com"'], alors que le module csv de Python en renvoie correctement deux. Utilisez toujours un vrai analyseur CSV.
La marque d'ordre des octets. Certains programmes écrivent trois octets invisibles, une marque d'ordre des octets (BOM), au début des fichiers UTF-8. Ouvert en simple utf-8, Python lit le premier en-tête comme 'email' : une recherche sur email ne trouve rien et chaque ligne semble vide. Nous l'avons reproduit avec un fichier de test de deux lignes. encoding="utf-8-sig" supprime la marque si elle est présente et ne fait rien sinon.
Un mot sur l'espace insécable : la fonction strip() de Python le traite déjà comme un espace, contrairement aux fonctions de suppression d'espaces des tableurs. Le replace explicite attrape aussi celui qui se trouve au milieu d'une adresse, laquelle échoue alors, à juste titre, au contrôle de syntaxe.
Ajouter un contrôle MX
Un contrôle de domaine retire les adresses dont le domaine ne peut pas recevoir de courrier, et il coûte peu, car chaque domaine n'est interrogé qu'une fois. Il ne demande qu'un paquet : pip install dnspython.
import csv, sys
import dns.resolver # pip install dnspython
cache = {}
def mail_status(domain):
if domain in cache:
return cache[domain]
try:
answers = dns.resolver.resolve(domain, "MX", lifetime=5)
hosts = [str(r.exchange) for r in answers]
status = "null MX (accepts no mail)" if hosts == ["."] else "has MX"
except dns.resolver.NXDOMAIN:
status = "domain does not exist"
except dns.resolver.NoAnswer:
try:
dns.resolver.resolve(domain, "A", lifetime=5)
status = "no MX, A record fallback"
except Exception:
status = "no mail server"
except Exception:
status = "lookup failed, retry"
cache[domain] = status
return status
with open(sys.argv[1], newline="", encoding="utf-8") as f:
for row in csv.DictReader(f):
email = row["email"]
print(f"{email:45} {mail_status(email.rsplit('@', 1)[1])}")
Lancé sur un fichier de test de cinq lignes, avec dnspython 2.8.0, le 1er octobre 2026, il a renvoyé :
someone@gmail.com has MX
someone@example.com null MX (accepts no mail)
someone@nonexistent-domain-zz91x.com domain does not exist
someone@neverssl.com no MX, A record fallback
other@gmail.com has MX
Nous avons recoupé chaque résultat avec dig. Deux de ces lignes expliquent pourquoi le script va plus loin que « y a-t-il un enregistrement MX ? » :
- Null MX.
example.compublie un enregistrement MX, mais c'est0 ., que la RFC 7505 définit comme le signal qu'un domaine n'accepte aucun courrier. Un contrôle qui se contente de demander « y a-t-il un enregistrement MX ? » le laisse passer. - Repli sur l'enregistrement A.
neverssl.comn'a pas d'enregistrement MX, mais possède un enregistrement A. Le résumé de la RFC 7505 décrit la remise du courrier comme se faisant « first by looking for an MX record and then by looking for an A/AAAA record as a fallback » (d'abord en cherchant un enregistrement MX, puis un enregistrement A/AAAA en repli) : l'absence de MX ne prouve donc pas à elle seule qu'un domaine est injoignable. Signalez ces adresses pour vérification au lieu de les supprimer.
Supprimez domain does not exist (le domaine n'existe pas), null MX et no mail server (pas de serveur de messagerie). Relancez plus tard lookup failed, retry (échec de la requête, à réessayer), car un délai DNS dépassé ne dit rien du domaine. L'outil de recherche MX traite un domaine à la fois dans le navigateur si vous voulez faire un contrôle ponctuel.
Ce que cela ne fait pas
Ni le script ni un tableur ne peuvent vous dire si une boîte mail existe. nobody-here-12345@gmail.com passe le contrôle de syntaxe et le contrôle MX, puis rebondit si cette boîte mail n'existe pas. Confirmer une boîte mail exige une conversation avec le serveur de messagerie du destinataire sur le port 25, que la plupart des réseaux domestiques et cloud bloquent ; notre article pourquoi les fournisseurs cloud bloquent le port 25 présente nos mesures. Cela ne permet pas non plus de repérer les domaines catch-all, qui acceptent toutes les adresses.
Pour cette étape, passez le fichier nettoyé dans un vérificateur. Comme vous avez déjà retiré les doublons et les domaines morts, vous payez pour contrôler moins de lignes.
À retenir
Normalisez avant de dédoublonner, utilisez un vrai analyseur CSV, ouvrez les fichiers en utf-8-sig et conservez les lignes rejetées avec leur motif au lieu de les supprimer. Ajoutez un contrôle MX qui comprend le null MX et le repli sur l'enregistrement A. Cela élimine gratuitement les erreurs de format et les domaines morts ; seuls restent les contrôles au niveau de la boîte mail, et le calculateur de coût de vérification indique ce que cela coûte sur ce qui reste.
Questions fréquentes
Comment supprimer les emails en double d'un CSV ?
Mettez d'abord la colonne email en minuscules et supprimez les espaces superflus, puis dédoublonnez sur cette valeur nettoyée en gardant la première occurrence. Dédoublonner la colonne brute laisse passer les copies qui ne diffèrent que par des majuscules ou des espaces invisibles.
Comment supprimer les emails invalides d'un CSV ?
Passez chaque adresse dans un contrôle de syntaxe pour écarter celles qui sont mal formées, puis vérifiez les enregistrements MX de chaque domaine pour écarter les domaines qui ne peuvent pas recevoir de courrier. Supprimer les adresses dont la boîte mail n'existe plus exige un service de vérification au niveau SMTP : aucun script local ni tableur ne peut le faire de façon fiable.
Pourquoi mon script indique-t-il qu'aucune ligne ne contient d'email ?
Souvent parce que le fichier commence par une marque d'ordre des octets (BOM), que certains programmes écrivent au début des fichiers UTF-8. Python lit alors le premier en-tête comme '\ufeffemail' au lieu de 'email'. Ouvrir le fichier avec encoding='utf-8-sig' la supprime.
Peut-on mettre les adresses email en minuscules sans risque ?
En pratique, oui. La RFC 5321 autorise techniquement la partie située avant le @ à être sensible à la casse, mais elle précise que s'en servir nuit à l'interopérabilité et le déconseille, et les grands fournisseurs ne créent pas de boîtes mail qui ne diffèrent que par la casse.
Vérifiez un nombre illimité d’adresses pour 29,99 USD/mois
Vérification SMTP réelle de la boîte aux lettres. Sans crédits ni frais par e-mail.
Commencer