Реализация импорта товаров из Google Merchant Feed

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Реализация импорта товаров из Google Merchant Feed
Средняя
~3-5 рабочих дней
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Реализация импорта товаров из Google Merchant Feed

Google Merchant Feed (GMF) — XML-формат на основе Atom/RSS с полями из пространства имён g:, который производители и дистрибьюторы готовят для Google Shopping. Для интернет-магазина импорт из этого формата открывает доступ к хорошо структурированным данным с обязательными полями id, title, description, price и availability — всё, что нужно для каталога.

Структура Google Merchant Feed

<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:g="http://base.google.com/ns/1.0">
  <channel>
    <title>Supplier Product Feed</title>
    <link>https://supplier.com</link>
    <item>
      <g:id>SKU-12345</g:id>
      <g:title>Беспроводные наушники Example Pro</g:title>
      <g:description>Наушники с шумоподавлением, 30 ч работы</g:description>
      <g:link>https://supplier.com/products/sku-12345</g:link>
      <g:image_link>https://cdn.supplier.com/sku-12345-main.jpg</g:image_link>
      <g:additional_image_link>https://cdn.supplier.com/sku-12345-2.jpg</g:additional_image_link>
      <g:availability>in stock</g:availability>
      <g:price>4990 RUB</g:price>
      <g:sale_price>3990 RUB</g:sale_price>
      <g:brand>Example</g:brand>
      <g:gtin>0012345678901</g:gtin>
      <g:mpn>EP-PRO-BLK</g:mpn>
      <g:condition>new</g:condition>
      <g:product_type>Электроника > Аудио > Наушники</g:product_type>
      <g:google_product_category>142</g:google_product_category>
      <g:color>Чёрный</g:color>
      <g:size>One Size</g:size>
      <g:material>Пластик</g:material>
      <g:item_group_id>EP-PRO</g:item_group_id>
      <g:shipping_weight>350 g</g:shipping_weight>
    </item>
  </channel>
</rss>

Парсер с поддержкой namespace

Ключевая особенность GMF — все поля товара в пространстве имён g:. SimpleXML требует явной работы с namespace:

class GoogleMerchantFeedParser
{
    private const G_NS = 'http://base.google.com/ns/1.0';

    public function parse(string $filePath): iterable
    {
        $reader = new \XMLReader();
        $reader->open($filePath);

        while ($reader->read()) {
            if ($reader->nodeType === \XMLReader::ELEMENT && $reader->name === 'item') {
                $node = new \SimpleXMLElement(
                    $reader->readOuterXml(),
                    0,
                    false,
                    '',
                    false
                );
                $g = $node->children(self::G_NS);
                yield $this->parseItem($node, $g);
            }
        }
        $reader->close();
    }

    private function parseItem(\SimpleXMLElement $item, \SimpleXMLElement $g): array
    {
        [$price, $currency]      = $this->parsePrice((string) $g->price);
        [$salePrice]             = $g->sale_price ? $this->parsePrice((string) $g->sale_price) : [null];
        $images                  = [(string) $g->image_link];

        foreach ($g->additional_image_link as $img) {
            $images[] = (string) $img;
        }

        return [
            'sku'              => (string) $g->id,
            'name'             => (string) $g->title,
            'description'      => (string) $g->description,
            'price'            => $price,
            'sale_price'       => $salePrice,
            'currency'         => $currency,
            'availability'     => $this->parseAvailability((string) $g->availability),
            'brand'            => (string) $g->brand,
            'gtin'             => (string) $g->gtin,
            'mpn'              => (string) $g->mpn,
            'condition'        => (string) $g->condition,
            'product_type'     => (string) $g->product_type,
            'google_category'  => (string) $g->google_product_category,
            'item_group_id'    => (string) $g->item_group_id,
            'images'           => array_filter($images),
            'color'            => (string) $g->color,
            'size'             => (string) $g->size,
            'material'         => (string) $g->material,
            'shipping_weight'  => $this->parseWeight((string) $g->shipping_weight),
        ];
    }

    private function parsePrice(string $raw): array
    {
        // "4990 RUB" → [4990.0, 'RUB']
        if (preg_match('/^([\d.,]+)\s+([A-Z]{3})$/', trim($raw), $m)) {
            return [(float) str_replace(',', '.', $m[1]), $m[2]];
        }
        return [(float) $raw, 'RUB'];
    }

    private function parseAvailability(string $raw): string
    {
        return match (strtolower(trim($raw))) {
            'in stock'                   => 'in_stock',
            'out of stock'               => 'out_of_stock',
            'preorder', 'pre-order'      => 'preorder',
            'backorder'                  => 'backorder',
            default                      => 'unknown',
        };
    }

    private function parseWeight(string $raw): ?float
    {
        if (!$raw) return null;
        if (preg_match('/^([\d.,]+)\s*(g|kg|lb|oz)$/i', trim($raw), $m)) {
            $value = (float) str_replace(',', '.', $m[1]);
            return match (strtolower($m[2])) {
                'kg' => $value,
                'g'  => $value / 1000,
                'lb' => $value * 0.453592,
                'oz' => $value * 0.0283495,
            };
        }
        return null;
    }
}

Маппинг Google Product Category

Google использует числовые идентификаторы из таксономии (142 = «Electronics > Audio > Headphones»). Таксономия публикуется Google как текстовый файл и содержит ~6 000 категорий:

class GoogleTaxonomyMapper
{
    private array $taxonomy; // [id => 'path > to > category']

    public function load(): void
    {
        $lines = file('https://www.google.com/basepages/producttype/taxonomy-with-ids.ru-RU.txt');
        foreach (array_slice($lines, 1) as $line) { // пропустить заголовок
            [$id, $path] = explode(' - ', trim($line), 2);
            $this->taxonomy[(int) $id] = $path;
        }
    }

    public function resolve(int $googleId): ?int
    {
        $path = $this->taxonomy[$googleId] ?? null;
        if (!$path) return null;

        // Ищем соответствующую категорию в нашем дереве
        return CategoryMapping::where('google_taxonomy_id', $googleId)->value('site_category_id');
    }
}

Вариативные товары через item_group_id

Поле g:item_group_id объединяет варианты одного товара (разные цвета/размеры):

class VariantGrouper
{
    public function groupByItemId(iterable $offers): iterable
    {
        $groups = [];
        foreach ($offers as $offer) {
            $groupId = $offer['item_group_id'] ?: $offer['sku'];
            $groups[$groupId][] = $offer;
        }

        foreach ($groups as $groupId => $variants) {
            if (count($variants) === 1) {
                yield ['type' => 'simple', 'data' => $variants[0]];
            } else {
                yield ['type' => 'variable', 'group_id' => $groupId, 'variants' => $variants];
            }
        }
    }
}

Вариативный товар создаётся как родительский продукт с дочерними вариантами по атрибутам color/size.

Хранение GTIN и поиск по штрихкоду

GTIN (EAN-13, UPC, ISBN) — глобальный идентификатор, который позволяет однозначно сопоставить товар из фида с уже существующим в каталоге:

$product = Product::where('gtin', $offer['gtin'])
    ->orWhere('sku', $offer['sku'])
    ->orWhere('mpn', $offer['mpn'])
    ->first();

Приоритет: GTIN > SKU > MPN.

Сжатые фиды (.gz)

Google рекомендует сжимать большие фиды. Автодетект:

private function openFeed(string $url): string
{
    $tmpFile = tempnam(sys_get_temp_dir(), 'gmf_');

    if (str_ends_with(parse_url($url, PHP_URL_PATH), '.gz')) {
        $gz = gzopen($url, 'rb');
        $fp = fopen($tmpFile, 'wb');
        while (!gzeof($gz)) fwrite($fp, gzread($gz, 8192));
        gzclose($gz);
        fclose($fp);
    } else {
        copy($url, $tmpFile);
    }

    return $tmpFile;
}

Сроки реализации

  • Парсер с namespace, базовые поля, доступность, цена с валютой — 1–2 дня
  • Вариативные товары через item_group_id, маппинг Google-категорий — +1–2 дня
  • GTIN-дедупликация, сжатые фиды, скачивание изображений — +1 день