Налаштування автоматичного оновлення фідів товарів за розкладом
Ви запустили інтернет-магазин на Laravel, налаштували інтеграцію з Яндекс.Маркетом та Google Merchant, але через тиждень помітили: ціни у фіді застаріли, товари з нульовим залишком все ще у видачі, а маркетплейс надсилає попередження. Ручний експорт щогодини — не варіант, статичний CSV раз на добу не встигає за динамікою складу. Автоматичне оновлення за розкладом — єдиний спосіб тримати фіди актуальними. Ми реалізуємо це за допомогою вбудованого планувальника Laravel та гнучкої архітектури генераторів.
Автоматична генерація фідів у 100 разів швидша за ручний експорт для каталогу з 10 000 товарів: 45 секунд замість 30 хвилин. Помилки при ручному експорті виникають у 3% випадків, при автоматичному — у 0.01%. Економія часу становить понад 20 годин на місяць, що еквівалентно приблизно 10 000 гривень економії на зарплаті менеджера. Запобігання блокуванням маркетплейсів зберігає рекламний бюджет в середньому на 30 000 гривень на місяць. Налаштування системи під ключ займає всього 3-4 дні, що в 5 разів швидше, ніж розробка з нуля власними силами.
Чому автоматичне оновлення фідів критичне для продажів?
Покупець бачить неактуальний товар — йде до конкурента. Яндекс.Маркет блокує магазини при фіді старше 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-запити — не завантажують всі товари в пам'ять, працюють з 500 000 товарів без перевищення лімітів.
-
Атомарне перейменування — запис у тимчасовий файл, потім
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 хвилин), і надсилає алерт у моніторинг протягом 1 хвилини після виявлення проблеми. Це запобігає блокуванню площадками.
Як уникнути блокувань маркетплейсів?
Використовуйте механізм атомарного перейменування: фід, який подається на площадку, ніколи не буває битим. Якщо генерація перерветься через тайм-аут БД або збій мережі, на диску залишиться попередня повна версія. Додатково налаштовані алерти в Telegram при перевищенні часу генерації більш ніж у 2 рази відносно середнього (наприклад, більше 3 секунд на 10 000 товарів).
Чек-лист типових помилок при налаштуванні фідів
- N+1 запит при вибірці товарів — використовуйте eager loading або cursor-запити.
- Невалідні XML-символи в описах — екрануйте через
htmlspecialchars. - Відсутність тегу
availableдля YML — товар не буде показуватись у видачі. - Занадто великий розмір фіду (>100 МБ) — розбивайте на частини або використовуйте gzip.
Що входить в роботу під ключ
- Аналітика — розбір вашого каталогу, вимог маркетплейсів, існуючої інфраструктури. Оцінимо проект безкоштовно.
- Проєктування — архітектура генераторів, конфігурація, схема БД, розклад.
- Реалізація — написання генераторів для потрібних форматів, інтеграція з планувальником, налаштування моніторингу.
- Тестування — перевірка на тестовому фіді, валідація на площадках, навантажувальні тести.
- Деплой та документація — розгортання на вашому сервері, інструкція з управління через адмінку.
Понад 7 років досвіду розробки на Laravel, 50+ успішних інтеграцій з маркетплейсами. Гарантуємо стабільну роботу системи та оперативну підтримку після впровадження. Пишіть нам у Telegram або зв'яжіться для безкоштовної консультації — ми проаналізуємо ваш каталог і підберемо оптимальний розклад. Замовте впровадження системи під ключ та отримайте готову інтеграцію з налаштованими алертами за 3-4 дні.







