Un site associatif avec inscription publique ouverte depuis 2019, sans aucune protection anti-spam, avait accumulé plus de quatre mille comptes visiblement automatisés : adresses courriel à motifs répétitifs, noms d’utilisateur composés de chiffres aléatoires, aucune activité de connexion depuis la création. Ce volume ralentissait les listes d’administration et compliquait la recherche d’un vrai membre parmi le bruit. Un nettoyage s’imposait, mais à la main, hors de question sur un tel volume.
Identifier les comptes suspects avant toute suppression
La première étape, la plus importante, consiste à définir des critères objectifs plutôt que de supprimer au jugé. Sur ce projet, les critères retenus combinaient l’absence de connexion depuis l’inscription et un rôle resté au niveau minimal :
wp user list --role=subscriber --fields=ID,user_login,user_email,user_registered --format=csv > audit-comptes.csv
Le champ de dernière connexion n’étant pas natif dans WordPress, une vérification passe par la meta session_tokens, absente ou vide chez un compte qui ne s’est jamais reconnecté après son inscription :
wp eval '
$utilisateurs = get_users( [ "role" => "subscriber" ] );
foreach ( $utilisateurs as $utilisateur ) {
$jetons = get_user_meta( $utilisateur->ID, "session_tokens", true );
if ( empty( $jetons ) ) {
echo $utilisateur->ID . "," . $utilisateur->user_email . PHP_EOL;
}
}
' > comptes-jamais-connectes.csv

Sauvegarder avant toute suppression
Une suppression en masse d’utilisateurs est irréversible sans sauvegarde. Avant toute exécution, un export ciblé des tables concernées garantit un retour en arrière possible :
wp db export sauvegarde-avant-nettoyage-comptes.sql --tables=wp_users,wp_usermeta
Supprimer avec réattribution des contenus
La commande wp user delete permet de supprimer un compte tout en réattribuant ses éventuels contenus (articles, commentaires) à un autre utilisateur, plutôt que de les perdre ou de les laisser orphelins :
# Suppression unique avec réattribution des contenus à l'administrateur (ID 1)
wp user delete 458 --reassign=1
# Suppression en masse à partir d'une liste d'identifiants
for id in $(cat comptes-jamais-connectes.csv | cut -d',' -f1); do
wp user delete "$id" --reassign=1
done
Sur un volume de plusieurs milliers de comptes, cette boucle shell reste lente : une suppression directe en SQL, plus rapide, est envisageable à condition d’avoir vérifié qu’aucun de ces comptes ne possède de contenu associé, ce qui évite tout besoin de réattribution :
DELETE u, um
FROM wp_users u
JOIN wp_usermeta um ON um.user_id = u.ID
WHERE u.ID IN ( SELECT user_id FROM (
SELECT user_id FROM wp_usermeta
WHERE meta_key = 'session_tokens' AND meta_value = ''
) AS sous_requete )
AND u.ID NOT IN ( SELECT DISTINCT post_author FROM wp_posts );
Prévenir plutôt que nettoyer à répétition
Un nettoyage ponctuel ne règle rien si le formulaire d’inscription reste ouvert sans protection. Sur ce projet, l’ajout d’un champ honeypot invisible et d’une vérification simple du délai de remplissage du formulaire (un bot soumet en une fraction de seconde, un humain met plusieurs secondes) a fait chuter les inscriptions automatisées de plus de 90 % :
add_filter( 'registration_errors', function ( $erreurs ) {
if ( ! empty( $_POST['site_web_confirmation'] ) ) {
$erreurs->add( 'bot_detecte', __( 'Une erreur est survenue.', 'mon-theme' ) );
}
return $erreurs;
} );
Nettoyer quatre mille comptes spam sans protection anti-inscription en amont, c’est vider une baignoire dont le robinet reste grand ouvert.
Vérifier après coup
Un recomptage rapide confirme la baisse effective, et un contrôle sur un échantillon d’anciens identifiants confirme qu’aucun contenu légitime n’a été perdu dans l’opération :
wp user list --role=subscriber --format=count
En résumé
Un nettoyage de comptes spam se prépare toujours dans le même ordre : définir des critères objectifs, exporter une sauvegarde, supprimer avec réattribution des contenus, puis corriger la cause en amont pour ne pas avoir à refaire cette opération dans deux ans.