Генерація каталогів товарів у форматі CSV/XML для маркетплейсів
Кожен маркетплейс диктує власний формат експорту. Ozon, Wildberries, Avito, AliExpress, Amazon, Rozetka — у кожного свої поля, кодування, способи передачі даних та обмеження. Розробляємо універсальний генератор каталогів з адаптерами під конкретні платформи.
Типові формати за маркетплейсами
| Маркетплейс | Формат | Спосіб передачі |
|---|---|---|
| Ozon | Excel / CSV / API | Seller API v3 |
| Wildberries | Excel / API | Supplier API |
| Avito | XML (avito-формат) | URL каталогу |
| AliExpress | CSV | Seller Center |
| Amazon | CSV (Flat File) | Seller Central / MWS |
| Rozetka | YML / XML | URL каталогу |
| Lamoda | CSV | SFTP |
| Leroy Merlin | XML | SFTP / API |
Універсальний генератор з адаптерами
Архітектура будується на паттерні Strategy: ядро генератора одне, адаптери під кожен маркетплейс — окремі класи.
interface MarketplaceFeedAdapter
{
public function getHeaders(): array;
public function transform(Product $product): array;
public function getFormat(): string; // 'csv' | 'xml' | 'xlsx'
public function getDelimiter(): string;
}
class OzonFeedAdapter implements MarketplaceFeedAdapter
{
public function getHeaders(): array
{
return [
'Артикул', 'Назва товару', 'Опис', 'Ціна',
'Стара ціна', 'ПДВ', 'Кількість', 'Вага, г', 'Ширина, мм',
'Висота, мм', 'Глибина, мм', 'Зображення', 'Категорія',
'Бренд', 'Штрихкод',
];
}
public function transform(Product $product): array
{
return [
$product->sku,
$product->name,
strip_tags($product->description),
$product->price,
$product->compare_price ?? '',
'20', // ПДВ 20%
$product->stock,
$product->weight_grams ?? '',
$product->width_mm ?? '',
$product->height_mm ?? '',
$product->depth_mm ?? '',
$product->images->pluck('cdn_url')->implode('; '),
$product->category?->ozon_category ?? '',
$product->brand?->name ?? '',
$product->barcode ?? '',
];
}
public function getFormat(): string { return 'csv'; }
public function getDelimiter(): string { return ';'; }
}
class AvitoCatalogAdapter implements MarketplaceFeedAdapter
{
// Avito вимагає XML зі специфічною структурою
public function getFormat(): string { return 'xml'; }
public function getDelimiter(): string { return ''; }
// ...
}
class UniversalFeedGenerator
{
public function generate(MarketplaceFeedAdapter $adapter, string $outputPath): void
{
if ($adapter->getFormat() === 'csv') {
$this->generateCsv($adapter, $outputPath);
} elseif ($adapter->getFormat() === 'xml') {
$this->generateXml($adapter, $outputPath);
}
}
private function generateCsv(MarketplaceFeedAdapter $adapter, string $path): void
{
$fp = fopen($path, 'w');
// UTF-8 BOM для коректного відкриття в Excel
fwrite($fp, "\xEF\xBB\xBF");
fputcsv($fp, $adapter->getHeaders(), $adapter->getDelimiter());
Product::with(['images', 'brand', 'category'])
->where('is_active', true)
->chunk(500, function ($products) use ($fp, $adapter) {
foreach ($products as $product) {
fputcsv($fp, $adapter->transform($product), $adapter->getDelimiter());
}
});
fclose($fp);
}
}
Avito XML-каталог
Avito вимагає специфічний XML-формат з елементами <Ad>:
<?xml version="1.0" encoding="UTF-8"?>
<Ads formatVersion="3" target="Avito.ru">
<Ad>
<Id>SKU-12345</Id>
<AllowEmail>No</AllowEmail>
<Title>Кросівки Nike Air Max 270</Title>
<Description>Опис товару...</Description>
<Category>Одяг, взуття, аксесуари</Category>
<GoodsType>Кросівки</GoodsType>
<Condition>Нове</Condition>
<Price>4990</Price>
<Images>
<Image url="https://cdn.example.com/product1.jpg"/>
<Image url="https://cdn.example.com/product2.jpg"/>
</Images>
<ContactPhone>+79001234567</ContactPhone>
</Ad>
</Ads>
Валідація перед відправкою
Обов'язкова перевірка перед експортом:
class FeedValidator
{
public function validate(array $row, MarketplaceFeedAdapter $adapter): array
{
$errors = [];
if (empty($row[0])) {
$errors[] = 'Пустий артикул';
}
if (empty($row[1]) || mb_strlen($row[1]) > 255) {
$errors[] = 'Некоректна назва';
}
if (!is_numeric($row[3]) || $row[3] <= 0) {
$errors[] = 'Некоректна ціна';
}
return $errors;
}
}
Розклад та доставка каталогів
- Генерація за розкладом через Laravel Scheduler
- Доставка за URL (прямим посиланням на файл у
storage/public/feeds/) - Доставка за SFTP — використовує
league/flysystem-sftp-v3 - Сповіщення по email при помилках генерації — через Laravel Notifications
Терміни
Генератор з одним адаптером — 1–2 робочих дні. Кожен додатковий маркетплейс — 0.5–1 робочий день (при унікованій структурі даних у каталозі).







