Реализация экспорта товаров из сайта в YML-фид
YML (Yandex Market Language) — XML-формат, который Яндекс.Маркет требует для загрузки каталога. Правильно сформированный фид обеспечивает индексацию без ошибок, корректное отображение офферов и допуск к платным размещениям. Неправильный фид — это отклонённые товары, штрафные баллы магазина и выпадение из поиска.
Структура YML и требования Яндекс.Маркета
Формат описан в официальной документации. Минимальная структура:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE yml_catalog SYSTEM "shops.dtd">
<yml_catalog date="2024-01-15 10: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">Смартфоны</category>
</categories>
<offers>
<offer id="12345" available="true">
<url>https://example.com/product/12345</url>
<price>29990</price>
<currencyId>RUR</currencyId>
<categoryId>11</categoryId>
<picture>https://example.com/img/12345.jpg</picture>
<name>Смартфон Samsung Galaxy S24</name>
<vendor>Samsung</vendor>
<description>...</description>
<param name="Цвет">Чёрный</param>
<param name="Память">256 GB</param>
</offer>
</offers>
</shop>
</yml_catalog>
Критичные поля для допуска к размещению: url, price, currencyId, categoryId, name. Поле available влияет на отображение — товары с false исключаются из поиска, но сохраняются в истории цен.
Архитектура генератора фида
Для небольших каталогов (до 10 000 товаров) фид можно генерировать на лету. Для крупных — только через фоновую задачу с записью на диск или в CDN.
Схема работы:
Cron/Queue → Builder → XML Writer → Storage (disk/S3) → Public URL
Реализация на PHP (Laravel):
class YmlFeedBuilder
{
public function build(string $outputPath): void
{
$writer = new XMLWriter();
$writer->openUri($outputPath);
$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->writeShopInfo($writer);
$this->writeCurrencies($writer);
$this->writeCategories($writer);
$this->writeOffers($writer);
$writer->endElement();
$writer->endDocument();
$writer->flush();
}
private function writeOffers(XMLWriter $writer): void
{
$writer->startElement('offers');
Product::with(['category', 'attributes', 'images'])
->where('active', true)
->where('price', '>', 0)
->chunk(500, function ($products) use ($writer) {
foreach ($products as $product) {
$this->writeOffer($writer, $product);
}
});
$writer->endElement();
}
}
Использование chunk() обязательно — попытка загрузить 50 000+ товаров одним запросом гарантированно даст OOM.
Типы офферов
Яндекс.Маркет различает несколько типов, каждый с обязательными полями:
| Тип | Применение | Обязательные доп. поля |
|---|---|---|
vendor.model |
Электроника, техника | vendor, model |
book |
Книги | author, isbn |
audiobook |
Аудиокниги | author, publisher |
medicine |
Лекарства | vendor, registration-number |
artist.title |
Музыка, кино | artist, title |
tour |
Туры | worldRegion, country |
| По умолчанию | Всё остальное | — |
Для большинства интернет-магазинов работает vendor.model или тип по умолчанию.
Валидация фида
Перед отправкой в Яндекс.Маркет фид нужно проверять локально. Используйте Feed Validator или схему shops.dtd.
Собственная валидация на этапе сборки:
class YmlFeedValidator
{
public function validate(string $filePath): array
{
$errors = [];
$dom = new DOMDocument();
if (!$dom->load($filePath)) {
return ['XML parse error'];
}
$offers = $dom->getElementsByTagName('offer');
foreach ($offers as $offer) {
$id = $offer->getAttribute('id');
if (!$offer->getElementsByTagName('price')->length) {
$errors[] = "Offer {$id}: missing price";
}
if (!$offer->getElementsByTagName('url')->length) {
$errors[] = "Offer {$id}: missing url";
}
// Проверка длины description (Яндекс обрезает после 3000 символов)
$desc = $offer->getElementsByTagName('description')->item(0);
if ($desc && mb_strlen($desc->textContent) > 3000) {
$errors[] = "Offer {$id}: description too long";
}
}
return $errors;
}
}
Расписание обновления
Частота зависит от динамики каталога:
- Цены и остатки — обновляются часто: рекомендуется регенерация каждые 1–4 часа
- Атрибуты и описания — медленно меняются: достаточно 1 раза в сутки
- Новые товары — немедленно или при следующем часовом цикле
Пример расписания в Laravel (app/Console/Kernel.php):
$schedule->job(new GenerateYmlFeedJob)->everyFourHours();
Оптимизация размера фида
Яндекс.Маркет рекомендует не превышать 500 MB на фид. Если каталог больше — разбивайте по категориям и регистрируйте несколько фидов в личном кабинете.
Снижение размера без потери качества:
- Исключать товары без цены (
WHERE price > 0) - Исключать неактивные категории
- Обрезать
descriptionдо 1000–1500 символов - Сжимать фид gzip (
Content-Encoding: gzip) — Яндекс поддерживает
// Сжатие готового фида
$source = storage_path('app/public/feed.xml');
$compressed = storage_path('app/public/feed.xml.gz');
$fp = gzopen($compressed, 'w9');
gzwrite($fp, file_get_contents($source));
gzclose($fp);
Сроки реализации
- Базовый генератор с типом по умолчанию: 1–2 дня
- Добавление типизированных офферов (
vendor.model): +0.5 дня - Валидатор + логирование ошибок: +0.5 дня
- Настройка cron + публичный URL: +0.5 дня
Итого типовой проект: 2–3 рабочих дня.







