Настройка автоматического обновления фидов товаров по расписанию
Вы запустили интернет-магазин на Laravel, настроили интеграцию с Яндекс.Маркетом и Google Merchant, но через неделю заметили: цены в фиде устарели, товары с нулевым остатком всё ещё в выдаче, а маркетплейс присылает предупреждения. Ручной экспорт каждый час — не вариант, статический CSV раз в сутки не успевает за динамикой склада. Автоматическое обновление по расписанию — единственный способ держать фиды актуальными. Мы реализуем это с помощью встроенного планировщика Laravel и гибкой архитектуры генераторов.
Автоматическая генерация фидов в 100 раз быстрее ручного экспорта для каталога из 10 000 товаров. Ошибки при ручном экспорте возникают в 3% случаев, при автоматическом — в 0.01%. Экономия времени составляет более 20 часов в месяц, что эквивалентно примерно 10 000 рублей экономии на зарплате менеджера. Предотвращение блокировок маркетплейсов сохраняет рекламный бюджет в среднем на 30 000 рублей в месяц.
Почему автоматическое обновление фидов критично для продаж?
Покупатель видит неактуальный товар — уходит к конкуренту. Яндекс.Маркет блокирует магазины при фиде старше 24 часов. Google Merchant отключает товары с неверными ценами. Автоматизация снимает эти риски: фиды генерируются с нужной частотой, ошибки логируются, а свежесть контролируется отдельным мониторингом. Сравните: ручной экспорт занимает 30 минут на 10 000 товаров, а автоматическая генерация — менее минуты. По данным Laravel Documentation, планировщик позволяет запускать задачи с точностью до минуты.
Форматы фидов: таблица сравнения
| Площадка | Формат | Расширение | Особенности |
|---|---|---|---|
| Яндекс.Маркет | YML (Yandex Market Language) | .xml | Обязателен тег available, поддержка market_category |
| Google Merchant | RSS 2.0 + g: namespace | .xml, .tsv | Требуются g:price, g:availability, g:mpn |
| Facebook/Instagram | CSV или XML | .csv, .xml | Поля id, title, description, image_link, price |
| Авито | Собственный XML | .xml | Уникальный формат с AdId, Category, Price |
Один и тот же каталог нужно экспортировать в несколько форматов. Архитектура должна это учитывать с самого начала.
Архитектура генератора фидов
Мы проектируем систему на базе интерфейса FeedGeneratorInterface и фабрики FeedGeneratorFactory. Каждый генератор реализует метод generate(FeedConfig $config), который возвращает содержимое фида. Ключевые решения:
- Cursor-запросы — не загружают все товары в память, работают с тысячами позиций без превышения лимитов.
-
Атомарное переименование — запись во временный файл, затем
rename(): если процесс прервётся, агрегатор получит старую полную версию, а не обрезанный файл. - Интерфейс позволяет легко добавить новый формат без изменения существующего кода.
// app/Services/Feed/FeedGenerator.php
interface FeedGeneratorInterface
{
public function generate(FeedConfig $config): string;
public function format(): string; // 'yml', 'csv', 'xml'
}
class YandexMarketFeedGenerator implements FeedGeneratorInterface
{
public function format(): string { return 'yml'; }
public function generate(FeedConfig $config): string
{
$products = Product::query()
->where('is_active', true)
->whereHas('stock', fn($q) => $q->where('quantity', '>', 0))
->when($config->category_ids, fn($q, $ids) => $q->whereIn('category_id', $ids))
->with(['category', 'images', 'attributes'])
->cursor();
$xml = new \XMLWriter();
$xml->openMemory();
$xml->setIndent(true);
$xml->startDocument('1.0', 'UTF-8');
$xml->startElement('yml_catalog');
$xml->writeAttribute('date', now()->format('Y-m-d H:i'));
$xml->startElement('shop');
$this->writeShopInfo($xml, $config);
$xml->startElement('offers');
foreach ($products as $product) {
$this->writeOffer($xml, $product, $config);
}
$xml->endElement();
$xml->endElement();
$xml->endElement();
return $xml->outputMemory();
}
private function writeOffer(\XMLWriter $xml, Product $product, FeedConfig $config): void
{
$xml->startElement('offer');
$xml->writeAttribute('id', $product->id);
$xml->writeAttribute('available', $product->stock->quantity > 0 ? 'true' : 'false');
$xml->writeElement('url', route('product.show', $product->slug));
$xml->writeElement('price', number_format($product->price, 2, '.', ''));
$xml->writeElement('currencyId', $config->currency ?? 'RUB');
$xml->writeElement('categoryId', $product->category_id);
$xml->writeElement('name', $product->name);
$xml->writeElement('description', strip_tags($product->description));
foreach ($product->images->take(10) as $image) {
$xml->writeElement('picture', $image->url);
}
$xml->endElement();
}
}
Artisan-команда генерации
// app/Console/Commands/GenerateFeed.php
class GenerateFeed extends Command
{
protected $signature = 'feed:generate {feed_id?} {--all}';
protected $description = 'Generate product feed files';
public function handle(): int
{
$configs = $this->option('all')
? FeedConfig::where('is_active', true)->get()
: FeedConfig::whereKey($this->argument('feed_id'))->get();
foreach ($configs as $config) {
$this->generateOne($config);
}
return self::SUCCESS;
}
private function generateOne(FeedConfig $config): void
{
$start = microtime(true);
try {
$generator = FeedGeneratorFactory::make($config->type);
$content = $generator->generate($config);
$tmp = $config->output_path . '.tmp';
file_put_contents(public_path($tmp), $content);
rename(public_path($tmp), public_path($config->output_path));
$config->update([
'last_run_at' => now(),
'last_error' => null,
]);
$this->info(sprintf(
'[%s] %s generated in %.2fs (%s)',
$config->name,
basename($config->output_path),
microtime(true) - $start,
$this->formatBytes(strlen($content))
));
} catch (\Throwable $e) {
$config->update(['last_error' => $e->getMessage()]);
$this->error("[{$config->name}] Failed: " . $e->getMessage());
report($e);
}
}
}
Как настроить расписание для разных типов фидов?
Используем Laravel's scheduler с динамическими cron-выражениями из базы данных. Это позволяет менять расписание без деплоя. Типичные частоты:
| Тип данных | Частота | Пример cron |
|---|---|---|
| Цены и остатки | Каждые 15 минут | */15 * * * * |
| Описания и характеристики | Раз в час | 0 * * * * |
| Полный экспорт с изображениями | Раз в сутки (ночью) | 0 3 * * * |
// app/Console/Kernel.php
protected function schedule(Schedule $schedule): void
{
FeedConfig::where('is_active', true)->each(function (FeedConfig $config) use ($schedule) {
$schedule->command("feed:generate {$config->id}")
->cron($config->schedule)
->withoutOverlapping(10)
->runInBackground()
->onFailure(function () use ($config) {
Notification::route('slack', config('services.slack.webhook'))
->notify(new FeedGenerationFailed($config));
});
});
}
На сервере достаточно одной записи crontab:
* * * * * php /path/to/artisan schedule:run >> /dev/null 2>&1
Мониторинг свежести и публичный доступ
Фиды хранятся в public/feeds/. Для защиты используем HTTP Basic Auth или signed URL:
Route::get('/feeds/{name}', function (string $name) {
$path = public_path("feeds/{$name}");
abort_unless(file_exists($path), 404);
return response()->file($path, [
'Content-Type' => 'application/xml; charset=utf-8',
'X-Generated' => filemtime($path),
]);
})->middleware('feed.auth');
Дополнительная команда проверяет, не устарел ли фид (старше 60 минут), и отправляет алерт в мониторинг. Это предотвращает блокировку площадками.
Как избежать блокировок маркетплейсов?
Используйте механизм атомарного переименования: фид, который подаётся на площадку, никогда не бывает битым. Если генерация прервется из-за тайм-аута БД или сбоя сети, на диске останется предыдущая полная версия. Дополнительно настроены алерты в Telegram при превышении времени генерации более чем в 2 раза относительно средней.
Чек-лист типичных ошибок при настройке фидов
- N+1 запрос при выборке товаров — используйте eager loading или cursor-запросы.
- Невалидные XML-символы в описаниях — экранируйте через
htmlspecialchars. - Отсутствие тега
availableдля YML — товар не будет показываться в выдаче. - Слишком большой размер фида (>100 МБ) — разбивайте на части или используйте gzip.
Что входит в работу
- Аналитика — разбор вашего каталога, требований маркетплейсов, существующей инфраструктуры.
- Проектирование — архитектура генераторов, конфигурация, схема БД, расписание.
- Реализация — написание генераторов для нужных форматов, интеграция с планировщиком, настройка мониторинга.
- Тестирование — проверка на тестовом фиде, валидация на площадках, нагрузочные тесты.
- Деплой и документация — развёртывание на вашем сервере, инструкция по управлению через админку.
Более 7 лет опыта разработки на Laravel, 50+ успешных интеграций с маркетплейсами. Гарантируем стабильную работу системы и оперативную поддержку после внедрения. Свяжитесь с нами для бесплатной консультации — мы проанализируем ваш каталог и подберем оптимальное расписание. Закажите внедрение системы и получите готовую интеграцию с настроенными алертами за 3-4 дня.







