vendredi 25 septembre 2026

À propos

Contact

Extensions

Importer 100 000 lignes CSV dans un CPT avec une barre de progression WP-CLI

Un catalogue de pièces détachées automobile à importer d'un coup, avec des lignes malformées à ne pas laisser interrompre l'ensemble du traitement.

Par Clément Hadrot • 16 avril 2023 • 4 min de lecture • Aucun commentaire
Importer 100 000 lignes CSV dans un CPT avec une barre de progression WP-CLI

Un fournisseur de pièces détachées automobile confie à une agence un fichier CSV de cent mille références à importer dans un CPT piece_detachee, avec pour chaque ligne une référence, une désignation, un prix et une compatibilité véhicule. Le fichier pèse plus de 40 Mo. Un script d’import classique, déclenché depuis l’administration via un appel AJAX, se heurte immédiatement à la limite de temps d’exécution du serveur bien avant d’avoir traité l’ensemble des lignes. La solution retenue est une commande WP-CLI dédiée, exécutée directement en ligne de commande, sans les contraintes de temps imposées à une requête HTTP classique.

Ne jamais charger tout le fichier en mémoire

La première erreur à éviter, même en ligne de commande où les limites de temps sont différentes, consiste à charger l’intégralité du fichier avec une fonction comme file() qui retourne un tableau contenant chaque ligne. Sur cent mille lignes, cela reste gérable, mais l’habitude est dangereuse : sur un fichier dix fois plus gros fourni plus tard par le même client, la consommation mémoire deviendrait problématique. La bonne pratique consiste à lire le fichier ligne par ligne avec un pointeur de fichier, quelle que soit sa taille.

La commande complète

L'essentiel à retenir : Découper la lecture du fichier évite de charger 100 000 lignes en mémoire ; WP_CLI::log journalise chaque erreur sans interrompre l'import ; make_progress_bar donne un retour visuel fiable en environnement WP-CLI
class Import_Pieces_Command {

    public function importer( $args, $assoc_args ) {
        $chemin = $args[0];

        if ( ! file_exists( $chemin ) ) {
            WP_CLI::error( "Fichier introuvable : {$chemin}" );
        }

        $handle = fopen( $chemin, 'r' );
        $entetes = fgetcsv( $handle, 0, ';' );

        $total_lignes = $this->compter_lignes( $chemin ) - 1;
        $barre = WP_CLI\Utils\make_progress_bar( 'Import des pièces', $total_lignes );

        $lot = array();
        $erreurs = 0;
        $traites = 0;

        while ( ( $ligne = fgetcsv( $handle, 0, ';' ) ) !== false ) {
            $donnee = array_combine( $entetes, $ligne );

            if ( empty( $donnee['reference'] ) || empty( $donnee['prix'] ) ) {
                WP_CLI::log( "Ligne ignorée, référence ou prix manquant : " . implode( ',', $ligne ) );
                $erreurs++;
                $barre->tick();
                continue;
            }

            $lot[] = $donnee;

            if ( count( $lot ) >= 200 ) {
                $this->traiter_lot( $lot );
                $traites += count( $lot );
                $lot = array();
            }

            $barre->tick();
        }

        if ( ! empty( $lot ) ) {
            $this->traiter_lot( $lot );
            $traites += count( $lot );
        }

        $barre->finish();
        fclose( $handle );

        WP_CLI::success( "{$traites} pièces importées, {$erreurs} lignes ignorées." );
    }

    private function traiter_lot( array $lot ) {
        foreach ( $lot as $donnee ) {
            $post_id = wp_insert_post( array(
                'post_type'   => 'piece_detachee',
                'post_title'  => sanitize_text_field( $donnee['designation'] ),
                'post_status' => 'publish',
            ) );

            if ( is_wp_error( $post_id ) ) {
                WP_CLI::log( "Échec insertion pour {$donnee['reference']} : " . $post_id->get_error_message() );
                continue;
            }

            update_post_meta( $post_id, 'reference', sanitize_text_field( $donnee['reference'] ) );
            update_post_meta( $post_id, 'prix', (float) $donnee['prix'] );
        }
    }

    private function compter_lignes( string $chemin ): int {
        $handle  = fopen( $chemin, 'r' );
        $compte  = 0;
        while ( fgets( $handle ) !== false ) {
            $compte++;
        }
        fclose( $handle );
        return $compte;
    }
}

WP_CLI::add_command( 'agence importer-pieces', array( new Import_Pieces_Command(), 'importer' ) );

Exécutée avec wp agence importer-pieces catalogue.csv, cette commande affiche une barre de progression en temps réel dans le terminal, journalise chaque ligne malformée sans interrompre le traitement des lignes suivantes, et regroupe les insertions par lots de deux cents pour limiter la fréquence des appels à des fonctions coûteuses comme le déclenchement des hooks associés à wp_insert_post().

Pourquoi ce découpage en lots de 200

Ce chiffre n’est pas arbitraire dans l’absolu, mais il constitue un bon point de départ observé en pratique sur ce type d’import : suffisamment grand pour amortir le coût fixe de chaque itération de boucle, suffisamment petit pour rester lisible dans les journaux et pour permettre, en cas d’erreur fatale imprévue en cours de traitement, de ne perdre au maximum que la progression d’un seul lot plutôt que l’ensemble de l’import.

Reprendre un import interrompu

Pour un import qui pourrait être interrompu, par exemple par une coupure réseau sur une connexion SSH distante, il est utile d’ajouter un paramètre optionnel permettant de reprendre à partir d’une ligne donnée :

wp agence importer-pieces catalogue.csv --reprendre-a=45000

Cette option, lue via WP_CLI\Utils\get_flag_value( $assoc_args, 'reprendre-a', 0 ), permet de sauter les lignes déjà traitées lors d’une exécution précédente sans avoir à retraiter l’ensemble du fichier depuis le début.

En résumé

WP-CLI, combiné à une lecture ligne par ligne du fichier source et à un découpage en lots raisonnables, permet d’importer des volumes de données largement hors de portée d’un traitement AJAX classique limité par le temps d’exécution d’une requête HTTP. La barre de progression et la journalisation des erreurs ligne par ligne, loin d’être un simple confort, deviennent indispensables dès que le volume dépasse ce qu’un humain peut surveiller à l’œil sans repère de progression.

Partager :

À propos de l'auteur

Clément Hadrot

Développeur WordPress, passionné par Elementor, le FSE et l’automatisation par IA.

Voir tous ses articles

Dans la même veine

À lire aussi