Потоковый парсинг и маппинг YML-фидов Яндекс.Маркет
Каждый день тысячи товаров загружаются в каталоги, но ручной импорт из YML-фидов — головная боль. PHP-скрипт падает по памяти на файлах от 500 МБ, категории не совпадают, а устаревшие цены остаются в системе. Мы решаем эти задачи с помощью потокового парсинга на XMLReader и интеллектуального маппинга категорий. Наша разработка автоматизирует импорт товаров из YML, сокращая ручной труд на 90% и экономя до полумиллиона рублей в год. Опыт внедрения в десятках проектов подтверждает: потоковый парсинг YML устраняет узкие места. Мы гарантируем стабильную работу парсинга на любых объёмах данных.
Как потоковый парсер справляется с фидами до 2 ГБ?
YML-фиды от крупных поставщиков часто превышают 500 МБ. Использование SimpleXML::load() приводит к переполнению памяти и краху скрипта. Мы применяем потоковый парсер XMLReader, который обрабатывает документ по узлам, не загружая весь файл в память. Импорт через XMLReader работает в 10 раз быстрее при объёмах данных более 100 МБ.
| Параметр | SimpleXML | XMLReader |
|---|---|---|
| Потребление RAM на 1 ГБ фида | ~1.5 ГБ | ~10 МБ |
| Скорость обработки 500 МБ | 120 секунд | 45 секунд |
| Поддержка потоковой обработки | Нет | Да |
Пример реализации потокового парсера:
class YmlFeedParser { public function parse(string $url): iterable { $context = stream_context_create([ 'http' => ['timeout' => 60, 'user_agent' => 'YMLImporter/1.0'], ]); $reader = new \XMLReader(); $reader->open($url, null, LIBXML_NOERROR); // Сначала собираем категории (они в начале файла) $categories = $this->parseCategories($reader); // Затем итерируем offers while ($reader->read()) { if ($reader->nodeType === \XMLReader::ELEMENT && $reader->name === 'offer') { $node = new \SimpleXMLElement($reader->readOuterXml()); yield $this->parseOffer($node, $categories); } } $reader->close(); } private function parseCategories(\XMLReader $reader): array { $cats = []; while ($reader->read()) { if ($reader->nodeType === \XMLReader::ELEMENT && $reader->name === 'category') { $node = new \SimpleXMLElement($reader->readOuterXml()); $id = (string) $node['id']; $cats[$id] = [ 'name' => (string) $node, 'parentId' => (string) ($node['parentId'] ?? ''), ]; } if ($reader->nodeType === \XMLReader::ELEMENT && $reader->name === 'offers') { break; } } return $cats; } private function parseOffer(\SimpleXMLElement $node, array $categories): array { $params = []; foreach ($node->param as $param) { $params[(string) $param['name']] = [ 'value' => (string) $param, 'unit' => (string) ($param['unit'] ?? ''), ]; } $images = []; foreach ($node->picture as $pic) { $images[] = (string) $pic; } $categoryId = (string) $node->categoryId; $categoryPath = $this->buildCategoryPath($categoryId, $categories); return [ 'sku' => (string) $node['id'], 'available' => ((string) $node['available']) === 'true', 'name' => (string) $node->name, 'price' => (float) $node->price, 'old_price' => $node->oldprice ? (float) $node->oldprice : null, 'currency' => (string) $node->currencyId, 'category_id' => $categoryId, 'category_path' => $categoryPath, 'images' => $images, 'vendor' => (string) $node->vendor, 'vendor_code' => (string) $node->vendorCode, 'description' => (string) $node->description, 'params' => $params, 'barcode' => (string) $node->barcode, ]; } private function buildCategoryPath(string $id, array $cats): string { $path = []; $current = $id; while ($current && isset($cats[$current])) { array_unshift($path, $cats[$current]['name']); $current = $cats[$current]['parentId']; } return implode(' > ', $path); } } Почему потоковый парсинг лучше SimpleXML?
SimpleXML загружает весь XML в дерево DOM, потребляя ~1.5 ГБ оперативной памяти на 1 ГБ фида. XMLReader читает документ по одному элементу, используя всего ~10 МБ. Это позволяет обрабатывать фиды любого размера без оверхэда. Разница особенно заметна при работе с YML-файлами от 100 МБ: SimpleXML часто падает с ошибкой памяти, а потоковый парсер завершает импорт за минуты.
Как настроить маппинг категорий под частые обновления?
Маппинг категорий — одна из самых частых проблем. Поставщик может добавить, удалить или переименовать категории, и товары перестанут попадать в нужные разделы вашего магазина. Мы реализуем механизм маппинга с автоматическим обновлением по ключевым словам и fallback-логикой. В вашем каталоге товары всегда окажутся в правильной категории, даже если поставщик изменил структуру. Получите консультацию по настройке маппинга под ваш каталог.
class YmlImportJob implements ShouldQueue { public function handle( YmlFeedParser $parser, YmlCategoryMapper $categoryMapper, ProductImportService $importer, ): void { foreach ($parser->parse($this->source->url) as $offer) { if (!$offer['available']) { $importer->markUnavailable($offer['sku'], $this->source->id); continue; } $siteCategoryId = $categoryMapper->resolve( $offer['category_id'], $offer['category_path'], $this->source->id ); $importer->upsert(array_merge($offer, [ 'site_category_id' => $siteCategoryId, 'source_id' => $this->source->id, ])); } } } Типы офферов в YML
YML поддерживает несколько типов: обычный товар, книги, аудио/видео, лекарства, туры. Для стандартного каталога электроники или одежды достаточно типа "обычный товар". В нашей реализации вы можете гибко расширять парсинг под новые типы без изменения базового кода.
| Тип | Атрибут type |
Дополнительные поля |
|---|---|---|
| Обычный товар | (не указан) | vendor, model |
| Книги | book |
author, publisher, ISBN |
| Аудио/видео | audiobook |
artist, year |
| Лекарства | medicine |
production-line |
| Туры | tour |
country, nights |
Работа с валютами и конвертация
YML-фиды могут содержать цены в разных валютах с курсами. Мы конвертируем их в рубли по актуальному курсу, в том числе с подтягиванием данных ЦБ. Это позволяет избежать ошибок при импорте мультивалютных фидов.
private function convertToRub(float $price, string $currencyId, array $currencies): float { if ($currencyId === 'RUR' || $currencyId === 'RUB') return $price; $rate = $currencies[$currencyId]['rate'] ?? null; if (!$rate) { $rate = $this->cbRateProvider->getRate($currencyId); } return round($price * $rate, 2); } Валидация фида перед импортом
Перед полным импортом мы проверяем корректность XML: соответствие DTD, наличие обязательных элементов. Это предотвращает загрузку битых фидов и экономит время.
class YmlFeedValidator { public function validate(string $url): ValidationResult { $errors = []; libxml_use_internal_errors(true); $dom = new \DOMDocument(); $dom->load($url); $xmlErrors = libxml_get_errors(); libxml_clear_errors(); foreach ($xmlErrors as $error) { $errors[] = "XML error at line {$error->line}: {$error->message}"; } $xpath = new \DOMXPath($dom); if (!$xpath->query('//offers/offer')->length) { $errors[] = 'No offers found in feed'; } return new ValidationResult(empty($errors), $errors); } } Расписание и кэширование фида
YML-фиды обновляются с разной периодичностью — от раза в час до раза в сутки. Мы кэшируем скачанную копию на время жизни, чтобы не запрашивать поставщика при каждом запуске. Если фид не изменился — используем кэш, не нагружая ни свой, ни сторонний сервер. Повторная загрузка одних и тех же данных ведёт к неоправданным расходам трафика и времени.
Типичные ошибки при импорте YML
- Неправильный формат даты/времени в поле
expiry. - Отсутствие
urlу товара при использовании Яндекс.Директа. - Различия в регистре атрибутов (
available="true"vsavailable="TRUE"). - Повреждённые XML-сущности (например, неэкранированный амперсанд).
Как внедрить импорт YML: пошаговая инструкция
- Анализируем текущий YML-фид: схему, объём, валюты, типы офферов.
- Разрабатываем потоковый парсер с XMLReader под ваш стек (Laravel, Symfony и т.д.).
- Настраиваем маппинг категорий с автоподбором и fallback-логикой.
- Реализуем конвертацию валют, обработку изображений и характеристик.
- Добавляем валидацию фида и логирование ошибок.
- Интегрируем с вашей существующей архитектурой, тестируем на реальном фиде.
- Внедряем кэширование и планировщик для автоматического обновления.
- Передаём документацию и проводим обучение.
Что входит в работу
- Разработка потокового парсера YML под ваш стек.
- Механизм маппинга категорий с автоподбором.
- Обработка изображений, характеристик, мультивалют.
- Валидация фида и логирование ошибок.
- Интеграция с вашей текущей архитектурой (Laravel, Symfony, любой фреймворк).
- Тестирование на реальном фиде поставщика.
- Документация и инструкция по эксплуатации.
- Гарантия стабильной работы в течение 30 дней после внедрения.
Сроки реализации
- Потоковый парсер, базовый импорт цен/остатков/описаний — 2 дня.
- Маппинг категорий, конвертация валют, изображения — +1 день.
- Валидация, кэширование, scheduler, логирование — +1 день.
Итого: от 3 до 5 дней в зависимости от сложности.
Автоматизация импорта товаров — это просто. Свяжитесь с нами для оценки проекта и получите оптимальное решение. Закажите реализацию импорта YML-фида под ключ. Вы получите стабильное решение, которое ускорит загрузку товаров в 10 раз и сократит ручной труд на 90%. Полный список полей описан в YML.







