You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WordPress中拆分WP_Query输出生成全量产品XML Feed咨询

问题描述

尝试为约30000个产品生成XML Feed,但当前输出仅能生成7000-8000个产品就中断,需要包含全部产品。询问能否拆分生成过程或输出文件,这种方案是否需要大量代码,或者有其他解决思路。当前使用WP_Query从数据库获取数据,通过foreach循环,用XMLWriter输出XML,代码如下:

add_action( 'wp_loaded', 'product_feeds_output_writer');
function product_feeds_output_writer() {

    $args = array(
        'post_type' => 'product', // Total product 30k.
        'post_status' => 'publish',
        'posts_per_page' => -1,
        'meta_query' => array(
            array(
                'key' => '_stock_status',
                'value' => 'instock',
                'compare' => '='
            )
        )
    );

    $query = new WP_Query();

    if (! $posts = $query->query( $args ) ) {
            
        $error = new WP_Error( '003', 'No products found.' );

        wp_send_json_error( $error );
    }

    $bloginfo = array(
        'name' => get_bloginfo('name'),
        'url' => get_bloginfo('url'),
        'description' => get_bloginfo('description')
    );
       
    $xml = new XMLWriter();
    
    $xml->openURI( $_SERVER['DOCUMENT_ROOT'] . '/wp-content/uploads/woo-products-feed-generator/XMLWriter.xml' );
    
    $xml->startDocument('1.0','UTF-8');
    
    $xml->setIndent( 4 );
    
    $xml->startElement('rss');
    
    $xml->writeAttribute('version', '2.0');
    
    $xml->writeAttribute('xmlns:g', 'http://base.google.com/ns/1.0');
    
    $xml->startElement('channel');
    
    $xml->writeElement('title', $bloginfo['name']);
    
    $xml->writeElement('link', $bloginfo['url']);
    
    $xml->writeElement('description', $bloginfo['description']);

    foreach ( $posts as $post ) {

        $product = wc_get_product( $post->ID );
        
        $bir = random_int( 0, 9 );

        $on = random_int( 10, 99 );
        
        $yuz = random_int( 100, 999 );
        
        $bin = random_int( 1000, 9999 );
        
        $mil = random_int( 1000000, 9999999 );
            
        $id = $product->get_id();
            
        $title = $product->get_title();
            
        $link = $product->get_permalink();
            
        $description = $product->get_description();

        $price = $product->get_regular_price() ?: $product->get_price();
            
        $sale_price = $product->get_sale_price() ?: '';

        $availability = $product->get_stock_status() == 'instock' ? 'in_stock' : 'out_of_stock';
            
        $brand = $product->get_attribute( 'pa_marka' );
            
        $image_link = $product->get_meta('_product_image_thumbnail') ?: wp_get_attachment_url( $product->get_image_id() );
        
        $type = implode( '', wp_get_post_terms( $product->id, 'product_cat', array('fields' => 'names') ) );
            
        $xml->startElement('item');
            
            $xml->writeElement( 'g:id', $id );
            
            $xml->writeElement( 'g:title', $title );
            
            $xml->writeElement( 'g:description', $description );
            
            $xml->writeElement( 'g:link', $link );
            
            $xml->writeElement( 'g:price', $price.' '.'TRY' );
            
            if( $sale_price ) {
            $xml->writeElement( 'g:sale_price', $sale_price.' '.'TRY' );
            }
            
            $xml->writeElement( 'g:availability', $availability );
            
            $xml->writeElement( 'g:brand', $brand );
            
            //$xml->writeElement( 'g:google_product_category', $track );
            
            $xml->writeElement( 'g:product_type', $type );
            
            $xml->writeElement( 'g:identifier_exists', 'yes' );
            
            $xml->writeElement( 'g:gtin', $mil.'-'.'K'.$yuz.'DK' );
            
            $xml->writeElement( 'g:mpn', $bin.'BS'.'-'.'L'.$on.$bir.'ZP' );
            
            $xml->writeElement( 'g:condition', 'Yeni' );
            
            //$xml->writeElement( 'g:shipping', $track );
            
            $xml->writeElement( 'g:image_link', $image_link );
            
            if( $attachment_images = $product->get_gallery_image_ids() ) {
                
                $attachment_images = array_slice( $attachment_images, 0, 10 );
                
                foreach( $attachment_images as $attachment_id ) {
                    
                    $image_link = wp_get_attachment_url( $attachment_id );
                
                    $xml->writeElement( 'g:additional_image_link', $image_link );
                }
            }
            
            $xml->writeElement( 'g:material', 'Polipropilen' );
            
            if( $value = $product->get_attribute( 'pa_renk' ) ) {
            
                $xml->startElement('g:product_detail');
                    
                    $xml->writeElement('g:section_name', 'Additional information' );
            
                    $xml->writeElement('g:attribute_name', 'Renk' );
                
                    $xml->writeElement('g:attribute_value', $value );
                
                $xml->endElement();
            }
            
            if( $value = $product->get_attribute( 'pa_beden' ) ) {
            
                $xml->startElement('g:product_detail');
                    
                    $xml->writeElement('g:section_name', 'Additional information' );
            
                    $xml->writeElement('g:attribute_name', 'Beden' );
                
                    $xml->writeElement('g:attribute_value', $value );
                
                $xml->endElement();
            }
            
        $xml->endElement();
    }
    
    $xml->endElement();
    
    $xml->endElement();
    
    $xml->endDocument();
    
    $xml->flush();
    
    unset($xml);
}
问题根源
  • 内存耗尽:一次性拉取3万条产品数据到内存,远超PHP默认内存限制,直接导致脚本中断。
  • 执行超时:处理大量数据的时间超过了PHP的max_execution_time限制,脚本被强制终止。
解决方案

方案1:分批生成单XML文件(推荐,代码改动小)

通过WP_Query的offset和posts_per_page参数分批拉取数据,逐步将产品追加到同一个XML文件中,保证文件结构完整。

修改后代码示例

add_action( 'wp_loaded', 'product_feeds_output_writer');
function product_feeds_output_writer() {
    // 加安全验证,避免公开访问
    if (!isset($_GET['feed_key']) || $_GET['feed_key'] !== 'your_secure_key_here') {
        wp_die('Access denied');
    }

    $per_batch = 1000; // 每批处理1000条,可根据服务器配置调整
    $current_batch = isset($_GET['batch']) ? (int)$_GET['batch'] : 1;
    $offset = ($current_batch - 1) * $per_batch;

    $args = array(
        'post_type' => 'product',
        'post_status' => 'publish',
        'posts_per_page' => $per_batch,
        'offset' => $offset,
        'meta_query' => array(
            array(
                'key' => '_stock_status',
                'value' => 'instock',
                'compare' => '='
            )
        ),
        'no_found_rows' => true, // 关闭总条数查询,优化性能
        'update_post_meta_cache' => false, // 关闭元数据缓存,节省内存
        'update_post_term_cache' => false // 关闭分类缓存,节省内存
    );

    $query = new WP_Query($args);
    $posts = $query->posts;

    $xml_file_path = $_SERVER['DOCUMENT_ROOT'] . '/wp-content/uploads/woo-products-feed-generator/XMLWriter.xml';
    $is_first_batch = $current_batch === 1;

    $xml = new XMLWriter();
    $xml->setIndent(4);

    if ($is_first_batch) {
        // 第一批次写入完整XML头部和基础信息
        $xml->openURI($xml_file_path);
        $xml->startDocument('1.0','UTF-8');
        $xml->startElement('rss');
        $xml->writeAttribute('version', '2.0');
        $xml->writeAttribute('xmlns:g', 'http://base.google.com/ns/1.0');
        $xml->startElement('channel');
        
        $bloginfo = array(
            'name' => get_bloginfo('name'),
            'url' => get_bloginfo('url'),
            'description' => get_bloginfo('description')
        );
        $xml->writeElement('title', $bloginfo['name']);
        $xml->writeElement('link', $bloginfo['url']);
        $xml->writeElement('description', $bloginfo['description']);
    } else {
        // 后续批次生成临时XML,只包含当前批的item
        $xml->openMemory();
        $xml->startDocument('1.0','UTF-8');
        $xml->startElement('channel');
    }

    // 产品数据处理逻辑保留原代码
    foreach ($posts as $post) {
        $product = wc_get_product($post->ID);
        
        $bir = random_int(0, 9);
        $on = random_int(10, 99);
        $yuz = random_int(100, 999);
        $bin = random_int(1000, 9999);
        $mil = random_int(1000000, 9999999);
            
        $id = $product->get_id();
        $title = $product->get_title();
        $link = $product->get_permalink();
        $description = $product->get_description();
        $price = $product->get_regular_price() ?: $product->get_price();
        $sale_price = $product->get_sale_price() ?: '';
        $availability = $product->get_stock_status() == 'instock' ? 'in_stock' : 'out_of_stock';
        $brand = $product->get_attribute('pa_marka');
        $image_link = $product->get_meta('_product_image_thumbnail') ?: wp_get_attachment_url($product->get_image_id());
        $type = implode('', wp_get_post_terms($product->id, 'product_cat', array('fields' => 'names')));
            
        $xml->startElement('item');
            $xml->writeElement('g:id', $id);
            $xml->writeElement('g:title', $title);
            $xml->writeElement('g:description', $description);
            $xml->writeElement('g:link', $link);
            $xml->writeElement('g:price', $price.' TRY');
            
            if($sale_price) {
                $xml->writeElement('g:sale_price', $sale_price.' TRY');
            }
            
            $xml->writeElement('g:availability', $availability);
            $xml->writeElement('g:brand', $brand);
            $xml->writeElement('g:product_type', $type);
            $xml->writeElement('g:identifier_exists', 'yes');
            $xml->writeElement('g:gtin', $mil.'-K'.$yuz.'DK');
            $xml->writeElement('g:mpn', $bin.'BS-L'.$on.$bir.'ZP');
            $xml->writeElement('g:condition', 'Yeni');
            $xml->writeElement('g:image_link', $image_link);
            
            if($attachment_images = $product->get_gallery_image_ids()) {
                $attachment_images = array_slice($attachment_images, 0, 10);
                foreach($attachment_images as $attachment_id) {
                    $image_link = wp_get_attachment_url($attachment_id);
                    $xml->writeElement('g:additional_image_link', $image_link);
                }
            }
            
            $xml->writeElement('g:material', 'Polipropilen');
            
            if($value = $product->get_attribute('pa_renk')) {
                $xml->startElement('g:product_detail');
                    $xml->writeElement('g:section_name', 'Additional information');
                    $xml->writeElement('g:attribute_name', 'Renk');
                    $xml->writeElement('g:attribute_value', $value);
                $xml->endElement();
            }
            
            if($value = $product->get_attribute('pa_beden')) {
                $xml->startElement('g:product_detail');
                    $xml->writeElement('g:section_name', 'Additional information');
                    $xml->writeElement('g:attribute_name', 'Beden');
                    $xml->writeElement('g:attribute_value', $value);
                $xml->endElement();
            }
        $xml->endElement();
    }

    if ($is_first_batch) {
        // 第一批次不关闭根标签,留待后续追加
        $xml->flush();
    } else {
        // 后续批次提取item内容,追加到主文件
        $temp_xml = $xml->outputMemory(true);
        $item_content = substr($temp_xml, strpos($temp_xml, '<item>'), strrpos($temp_xml, '</channel>') - strpos($temp_xml, '<item>'));
        
        $main_xml = file_get_contents($xml_file_path);
        $main_xml = substr($main_xml, 0, strrpos($main_xml, '</channel>')) . PHP_EOL . $item_content . '</channel></rss>';
        file_put_contents($xml_file_path, $main_xml);
    }

    // 计算总批次,自动跳转下一批
    $total_products = count(wc_get_products(array(
        'post_type' => 'product',
        'post_status' => 'publish',
        'meta_query' => array(
            array(
                'key' => '_stock_status',
                'value' => 'instock',
                'compare' => '='
            )
        ),
        'return' => 'ids'
    )));
    $total_batches = ceil($total_products / $per_batch);

    if ($current_batch < $total_batches) {
        wp_redirect(add_query_arg('batch', $current_batch + 1, $_SERVER['REQUEST_URI']));
        exit;
    } else {
        echo 'XML Feed生成完成,文件路径:' . $xml_file_path;
    }

    unset($xml);
    wp_reset_postdata();
}

方案2:拆分生成多个独立XML文件

如果不需要单个大文件,可直接每批生成一个独立的XML文件,逻辑更简单,无需处理文件追加:

// 仅修改文件路径部分
$xml_file_path = $_SERVER['DOCUMENT_ROOT'] . '/wp-content/uploads/woo-products-feed-generator/XMLWriter_batch_'.$current_batch.'.xml';

// 每个批次都写入完整的XML结构,包括头部和尾部
$xml->startDocument('1.0','UTF-8');
$xml->startElement('rss');
// ... 写入channel信息 ...
// ... 写入当前批的item ...
$xml->endElement(); // 关闭channel
$xml->endElement(); // 关闭rss
$xml->endDocument();
$xml->flush();

方案3:临时提升PHP限制(辅助方案)

在代码开头添加以下代码,临时提升内存和超时限制,可缓解小数据量问题,但对3万条数据仅为辅助手段:

ini_set('memory_limit', '256M');
ini_set('max_execution_time', 300); // 设置为5分钟
关键优化点
  • 关闭WP_Query的冗余缓存:通过no_found_rows、update_post_meta_cache、update_post_term_cache参数减少内存占用。
  • 分批处理:避免一次性加载所有数据到内存,降低服务器压力。
  • 安全验证:添加密钥验证,防止Feed生成接口被恶意调用。

内容的提问来源于stack exchange,提问作者ersezar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 03:20:29