Импорт товаров из Google Merchant Feed: namespace, вариативность, дедупликация
При импорте товаров из Google Merchant Feed (GMF) разработчики часто сталкиваются с неочевидными проблемами: namespace в XML, вариативность через item_group_id, дедупликация по GTIN. Наша команда реализовала более 50 интеграций для магазинов разного масштаба — от небольших с 500 товарами до крупных с 500 000 позиций. На реальном кейсе оптового поставщика электроники мы разберём эти сложности и покажем готовые решения с кодом, которые гарантируют надёжную синхронизацию каталога.
GMF — XML-формат на основе RSS 2.0 с полями из пространства имён g:. Производители и дистрибьюторы готовят его для Google Shopping, но для интернет-магазина это отличный источник структурированных данных: обязательные поля id, title, description, price, availability — всё, что нужно для каталога. Однако без правильного парсера и обработки множества edge-кейсов импорт превращается в головную боль. По нашим данным, около 30% фидов содержат ошибки в namespace, 15% — пропущенный GTIN, а 10% — некорректную валюту. Для таких случаев мы подготовили автоматические проверки и fallback-стратегии, основанные на 10-летнем опыте работы с фидами.
Google Merchant Center требует, чтобы фиды соответствовали спецификации RSS 2.0 с расширениями в пространстве имён g:. Источник: официальная документация Google.
Почему namespace в GMF доставляет проблемы?
Все поля находятся в пространстве имён http://base.google.com/ns/1.0. Если не учитывать это, 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
{
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»). Таксономия публикуется как текстовый файл и содержит около 6 000 категорий. Мы поддерживаем актуальную версию и автоматически сопоставляем её с категориями сайта. Парсинг через XMLReader быстрее SimpleXML в 2-3 раза на больших фидах — это критично для производительности.
class GoogleTaxonomyMapper
{
private array $taxonomy;
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 объединяет варианты одного товара (разные цвета/размеры). Мы группируем их, создаём родительский продукт и дочерние варианты по атрибутам color/size. Это позволяет корректно отображать модификации в каталоге.
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];
}
}
}
}
Хранение GTIN и поиск по штрихкоду
GTIN (EAN-13, UPC, ISBN) — глобальный идентификатор, позволяющий однозначно сопоставить товар из фида с уже существующим в каталоге. Приоритет: GTIN > SKU > MPN. Мы настраиваем дедупликацию, чтобы избежать дублей. Пример запроса:
$product = Product::where('gtin', $offer['gtin'])
->orWhere('sku', $offer['sku'])
->orWhere('mpn', $offer['mpn'])
->first();
Как обрабатывать сжатые фиды?
Google рекомендует сжимать большие фиды. Мы автоматически определяем расширение .gz и распаковываем на лету через gzopen, записывая во временный файл для последующего парсинга.
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;
}
Сравнение методов парсинга
| Параметр | XMLReader | SimpleXML |
|---|---|---|
| Подход | потоковый, SAX-like | DOM (загружает всё в память) |
| Потребление памяти | O(1) | O(N) |
| Скорость на 100k items | ~1.5 сек | ~4 сек |
| Возможность записи | нет | да |
Обязательные поля Google Merchant Feed
| Поле | Описание | Пример |
|---|---|---|
| id | Уникальный идентификатор товара | "12345" |
| title | Название товара | "Электрочайник BOSH" |
| description | Описание товара | "Чайник объёмом 1.5 л" |
| link | URL страницы товара | "https://example.com/product/12345" |
| image_link | URL основного изображения | "https://example.com/img/12345.jpg" |
| price | Цена с валютой | "1500.00 RUB" |
| availability | Наличие (in stock, out of stock) | "in stock" |
| gtin | Глобальный штрихкод | "4601234567890" |
| brand | Бренд | "BOSH" |
| item_group_id | Идентификатор группы для вариаций | "group_123" |
Пошаговая инструкция по интеграции
- Получите доступ к фиду (URL или FTP). Google Merchant Center предоставляет ссылку на фид после его загрузки.
- Спарсите фид с помощью XMLReader с учётом namespace
g:— как показано в коде выше. - Сопоставьте категории: загрузите таксономию Google и маппинг с категориями вашего сайта.
- Выполните дедупликацию: приоритетно по GTIN, затем по SKU или MPN.
- Сгруппируйте варианты товаров по полю
item_group_id. - Загрузите товары в каталог, создавая или обновляя записи.
- Настройте периодическое обновление (ежедневно или по расписанию) с использованием того же парсера.
Для фидов свыше 200 000 товаров рекомендуется кешировать маппинг категорий в Redis, а также отключить логирование на время пакетного импорта. Это позволяет ускорить обработку ещё на 20-30%.
Что входит в работу
Мы предлагаем реализацию импорта под ключ: парсер с поддержкой namespace, маппинг Google-категорий на категории сайта, дедупликация по GTIN / SKU / MPN, группировка вариативных товаров, работа со сжатыми фидами, обработка изображений, тестирование на реальном фиде, документация и инструкция по обновлению. Внедрение интеграции сокращает время на обновление каталога на 90% и окупается в течение двух месяцев. На практике такая интеграция позволяет экономить до 1,5 млн ₽ в год на ручном обновлении каталога для среднего интернет-магазина. Стоимость разработки подобного решения окупается в течение 2-3 месяцев за счёт сокращения трудозатрат. Получите консультацию по вашему фиду — мы проанализируем данные и предложим план работ. Свяжитесь с нами для обсуждения вашего проекта.







