Налаштування автоматичного оновлення фідів товарів за розкладом
Ви запустили інтернет-магазин на 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 дні.







