perkun.eu Services Portfolio Blog About Contact PL
← Blog

11/22/2022

XML import of 2,700 WooCommerce products in < 5 minutes

TL;DR: Importing 2,700 products from XML via standard plugins = 2 hours. A custom PHP script with diff + batch insert + disabled WooCommerce hooks = < 5 minutes.

Running a dropshipping store with a supplier that provides an XML feed? Standard import plugins (WP All Import, etc.) work via admin AJAX — one product at a time, with a full WooCommerce hook cycle. For 2,700 products that’s a recipe for timeouts and grey hairs.

Diagnosing the problem

What makes the standard import slow?

  1. WooCommerce hooks on every productwoocommerce_update_product, search index rebuild, cache invalidation. For 2,700 products = 2,700 × a dozen hooks each.
  2. No diffing — you re-import everything even if 2,690 products haven’t changed.
  3. Individual SQL queries — one query per product instead of batch inserts.

Solution: a custom importer

Key elements:

// 1. Disable unnecessary hooks during import
remove_all_actions('woocommerce_update_product');
wp_suspend_cache_addition(true);
wp_defer_term_counting(true);

// 2. Parse XML into an associative array
$xml = simplexml_load_file($feed_url);
$products = [];
foreach ($xml->product as $p) {
    $products[(string)$p->ean] = [
        'title'    => (string)$p->name,
        'price'    => (float)$p->price * MARGIN_MULTIPLIER,
        'stock'    => (int)$p->stock,
        'ean'      => (string)$p->ean,
    ];
}

// 3. Fetch current state from DB (one query)
global $wpdb;
$existing = $wpdb->get_results(
    "SELECT meta_value as ean, post_id FROM {$wpdb->postmeta} WHERE meta_key = '_ean'",
    OBJECT_K
);

// 4. Diff — only update what changed
foreach ($products as $ean => $data) {
    if (isset($existing[$ean])) {
        // update price and stock if they changed
        $post_id = $existing[$ean]->post_id;
        update_post_meta($post_id, '_price', $data['price']);
        update_post_meta($post_id, '_stock', $data['stock']);
    } else {
        // new product — insert
        wc_create_product($data);
    }
}

// 5. Restore hooks and recount terms
wp_suspend_cache_addition(false);
wp_defer_term_counting(false);

Results

  • Full import (first run): 4 minutes 23 seconds
  • Delta import (changes only): 47 seconds
  • Previously via WP All Import: > 2 hours (timeout at 1,000 products)

Schedule

Cron every 6 hours via WP-Cron or system cron:

0 */6 * * * php /var/www/html/wp-content/plugins/my-importer/run.php >> /var/log/import.log 2>&1

After import — force nginx cache flush (if you have FastCGI cache):

find /tmp/nginx-cache -type f -delete