Генерація каталогів товарів для Яндекс.Маркет (YML)
YML (Yandex Market Language) — XML-діалект зі своїми правилами валідації, які суттєво жорсткіші за стандартний RSS. Яндекс регулярно оновлює вимоги до категорій: одяг, електроніка та автозапчастини мають розширені обов'язкові набори атрибутів. Неправильно сформований каталог блокує всю кампанію, а не окремі позиції.
Формати передачі даних у Яндекс.Маркет
Яндекс підтримує два способи:
- YML-каталог — файл за URL, який Яндекс завантажує за розкладом (мінімум раз на 24 год, максимум раз на годину)
- Price API — програмний інтерфейс для оновлення тільки цін та наявності без повної регенерації каталогу
Для більшості магазинів достатньо YML-каталогу. Price API підключається додатково, якщо ціни змінюються кілька разів на день.
Структура YML-документа
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE yml_catalog SYSTEM "shops.dtd">
<yml_catalog date="2026-03-28 14:00">
<shop>
<name>Назва магазину</name>
<company>ТОВ Компанія</company>
<url>https://example.com</url>
<currencies>
<currency id="RUR" rate="1"/>
</currencies>
<categories>
<category id="10">Смартфони</category>
<category id="11" parentId="10">Apple iPhone</category>
</categories>
<delivery-options>
<option cost="299" days="1"/>
</delivery-options>
<offers>
<offer id="12345" available="true">
<url>https://example.com/products/iphone-15</url>
<price>89990</price>
<oldprice>99990</oldprice>
<currencyId>RUR</currencyId>
<categoryId>11</categoryId>
<picture>https://cdn.example.com/iphone-15-1.jpg</picture>
<picture>https://cdn.example.com/iphone-15-2.jpg</picture>
<name>Смартфон Apple iPhone 15 128GB чорний</name>
<vendor>Apple</vendor>
<vendorCode>MTP03LL/A</vendorCode>
<barcode>0194253401353</barcode>
<description>Смартфон Apple iPhone 15 з процесором A16...</description>
<param name="Колір">Чорний</param>
<param name="Вбудована пам'ять" unit="ГБ">128</param>
<param name="Операційна система">iOS</param>
</offer>
</offers>
</shop>
</yml_catalog>
Специфіка типів пропозицій
Яндекс розрізняє кілька типів товарних пропозицій:
| Тип | Коли використовувати | Ключові додаткові поля |
|---|---|---|
vendor.model |
електроніка, побутова техніка | typePrefix, vendor, model |
book |
книги | author, publisher, ISBN, year |
audiobook |
аудіокниги | author, publisher, performed-by |
artist.title |
музика, відео, ігри | artist, title, year, media |
tour |
тури | worldRegion, hotel-stars, room, dataTour |
event-ticket |
квитки | place, hall, date, is-premiere |
| простий | все інше | тільки базові поля |
Генератор на PHP
class YandexMarketFeedGenerator
{
public function handle(): void
{
$path = storage_path('app/public/feeds/yandex.xml');
$writer = new \XMLWriter();
$writer->openUri($path); // пишемо прямо у файл, не в пам'ять
$writer->setIndent(true);
$writer->startDocument('1.0', 'UTF-8');
$writer->writeDtd('yml_catalog', null, 'shops.dtd');
$writer->startElement('yml_catalog');
$writer->writeAttribute('date', now()->format('Y-m-d H:i'));
$this->writeShopHeader($writer);
$this->writeCurrencies($writer);
$this->writeCategories($writer);
$this->writeOffers($writer);
$writer->endElement();
$writer->endDocument();
$writer->flush();
}
private function writeOffers(\XMLWriter $w): void
{
$w->startElement('offers');
Product::with(['category', 'brand', 'images', 'attributes'])
->where('is_active', true)
->chunk(500, function ($products) use ($w) {
foreach ($products as $p) {
$w->startElement('offer');
$w->writeAttribute('id', $p->sku);
$w->writeAttribute('available', $p->stock > 0 ? 'true' : 'false');
$w->writeElement('url', route('products.show', $p->slug));
$w->writeElement('price', (string) $p->price);
if ($p->compare_price > $p->price) {
$w->writeElement('oldprice', (string) $p->compare_price);
}
$w->writeElement('currencyId', 'RUR');
$w->writeElement('categoryId', $p->category_id);
$w->writeElement('name', $p->name);
$w->writeElement('vendor', $p->brand?->name ?? '');
$w->writeElement('barcode', $p->barcode ?? '');
foreach ($p->images as $img) {
$w->writeElement('picture', $img->cdn_url);
}
foreach ($p->attributes as $attr) {
$w->startElement('param');
$w->writeAttribute('name', $attr->name);
if ($attr->unit) {
$w->writeAttribute('unit', $attr->unit);
}
$w->text($attr->value);
$w->endElement();
}
$w->endElement(); // offer
}
});
$w->endElement(); // offers
}
}
Налаштування оновлення
Каталог оновлюється через Laravel Scheduler:
// app/Console/Kernel.php
$schedule->job(GenerateYandexFeedJob::class)->hourly()->withoutOverlapping();
Файл каталогу видається через виділений маршрут або прямо з public/feeds/. Якщо каталог перевищує 500 МБ у XML, Яндекс рекомендує розбивати каталог на кілька файлів та реєструвати кожен окремо в кабінеті.
Діагностика помилок
Яндекс.Маркет повертає детальний звіт по кожній пропозиції. Найпоширеніші проблеми:
- Ціна дорівнює нулю або відсутня — товар автоматично виключається з індексу
- Невідповідність ціни на сайті — Яндекс порівнює ціну в каталозі з ціною на сторінці товару. Різниця понад 1% блокує пропозицію
- Зображення недоступне — перевіряється при первинному завантаженні та повторно при кожному переході
- Занадто довгий опис — допустимо 3000 символів для більшості категорій
Терміни
Базовий генератор для стандартного каталогу — 2–4 робочих дні. Складні категорії (одяг з таблицею розмірів, електроніка з розширеними атрибутами) — 4–6 робочих днів.







