Генерация фида товаров в формате 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);
}
}
Авито 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 рабочий день (при унифицированной структуре данных в каталоге).







