Реализация экспорта товаров из сайта в CSV/Excel/XML/JSON

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Реализация экспорта товаров из сайта в CSV/Excel/XML/JSON
Средняя
~3-5 рабочих дней
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Реализация экспорта товаров из сайта в CSV/Excel/XML/JSON

Экспорт каталога нужен для разных задач: загрузка на маркетплейсы, отправка партнёрам, бэкап, интеграция с ERP/1С, аналитика. Каждый получатель хочет свой формат и своё подмножество полей. Задача — построить гибкий экспорт, который обслуживает все эти сценарии без дублирования кода.

Архитектура: Builder + Writer

ExportBuilder
  └─> определяет набор полей, фильтры, сортировку
  └─> итерирует данные из БД чанками
  └─> передаёт строки в ExportWriter

ExportWriter (один из):
  ├─> CsvWriter
  ├─> ExcelWriter
  ├─> XmlWriter
  └─> JsonWriter
interface ExportWriterInterface
{
    public function open(string $filePath): void;
    public function writeHeader(array $columns): void;
    public function writeRow(array $row): void;
    public function close(): string; // возвращает путь к файлу
}

CSV-писатель

class CsvWriter implements ExportWriterInterface
{
    private $handle;

    public function open(string $filePath): void
    {
        $this->handle = fopen($filePath, 'w');
        // BOM для корректного открытия в Excel
        fwrite($this->handle, "\xEF\xBB\xBF");
    }

    public function writeHeader(array $columns): void
    {
        fputcsv($this->handle, $columns, ';', '"');
    }

    public function writeRow(array $row): void
    {
        fputcsv($this->handle, $row, ';', '"');
    }

    public function close(): string
    {
        fclose($this->handle);
        return $this->filePath;
    }
}

Excel-писатель (потоковый, без лишней памяти)

Для больших каталогов PhpSpreadsheet потребляет огромно памяти. Используем openspout/openspout — работает потоково:

class ExcelWriter implements ExportWriterInterface
{
    private \OpenSpout\Writer\XLSX\Writer $writer;

    public function open(string $filePath): void
    {
        $this->writer = new \OpenSpout\Writer\XLSX\Writer();
        $this->writer->openToFile($filePath);
    }

    public function writeHeader(array $columns): void
    {
        $cells = array_map(fn($c) => \OpenSpout\Common\Entity\Cell::fromValue($c), $columns);
        $this->writer->addRow(new \OpenSpout\Common\Entity\Row($cells, null));
    }

    public function writeRow(array $row): void
    {
        $cells = array_map(fn($v) => \OpenSpout\Common\Entity\Cell::fromValue($v), $row);
        $this->writer->addRow(new \OpenSpout\Common\Entity\Row($cells, null));
    }

    public function close(): string
    {
        $this->writer->close();
        return $this->filePath;
    }
}

openspout пишет строки напрямую в ZIP-поток .xlsx файла — потребление памяти ~10 МБ независимо от размера файла.

XML-писатель

class XmlWriter implements ExportWriterInterface
{
    private \XMLWriter $xml;

    public function open(string $filePath): void
    {
        $this->xml = new \XMLWriter();
        $this->xml->openUri($filePath);
        $this->xml->startDocument('1.0', 'UTF-8');
        $this->xml->startElement('products');
    }

    public function writeHeader(array $columns): void
    {
        $this->columns = $columns; // сохраняем для writeRow
    }

    public function writeRow(array $row): void
    {
        $this->xml->startElement('product');
        foreach (array_combine($this->columns, $row) as $key => $value) {
            $this->xml->startElement($key);
            $this->xml->text((string) $value);
            $this->xml->endElement();
        }
        $this->xml->endElement();
    }

    public function close(): string
    {
        $this->xml->endElement(); // products
        $this->xml->endDocument();
        $this->xml->flush();
        return $this->filePath;
    }
}

JSON-писатель (NDJSON для потоковой обработки)

Для больших экспортов вместо одного большого JSON-массива — NDJSON (одна строка = один объект):

class NdjsonWriter implements ExportWriterInterface
{
    private $handle;
    private array $columns;

    public function writeRow(array $row): void
    {
        $obj = array_combine($this->columns, $row);
        fwrite($this->handle, json_encode($obj, JSON_UNESCAPED_UNICODE) . "\n");
    }
}

Стандартный JSON-массив тоже поддерживается через JsonWriter, но для файлов >50 МБ NDJSON предпочтительнее.

Builder: формирование запроса и итерация

class ProductExportBuilder
{
    private array $fields = ['sku', 'name', 'price', 'qty'];
    private array $filters = [];
    private int   $chunkSize = 1000;

    public function withFields(array $fields): self
    {
        $this->fields = $fields;
        return $this;
    }

    public function withFilter(string $field, mixed $value): self
    {
        $this->filters[$field] = $value;
        return $this;
    }

    public function export(ExportWriterInterface $writer, string $filePath): ExportResult
    {
        $writer->open($filePath);
        $writer->writeHeader($this->fields);

        $total = 0;
        $query = $this->buildQuery();

        $query->chunk($this->chunkSize, function ($products) use ($writer, &$total) {
            foreach ($products as $product) {
                $row = array_map(fn($f) => $this->resolveField($product, $f), $this->fields);
                $writer->writeRow($row);
                $total++;
            }
        });

        $filePath = $writer->close();
        return new ExportResult($total, $filePath);
    }

    private function buildQuery(): \Illuminate\Database\Eloquent\Builder
    {
        $query = Product::query()->orderBy('id');
        foreach ($this->filters as $field => $value) {
            $query->where($field, $value);
        }
        return $query;
    }

    private function resolveField(Product $product, string $field): mixed
    {
        return match ($field) {
            'category'    => $product->category?->name,
            'images'      => implode(',', $product->images->pluck('url')->all()),
            'attributes'  => $this->formatAttributes($product),
            default       => $product->{$field},
        };
    }
}

Асинхронный экспорт больших файлов

Для каталогов >10 000 позиций экспорт выполняется в фоновом режиме:

class ExportProductsJob implements ShouldQueue
{
    public int $timeout = 600;

    public function handle(ProductExportBuilder $builder): void
    {
        $filePath = storage_path("exports/products_{$this->exportId}.{$this->format}");
        $writer   = ExportWriterFactory::make($this->format);

        $result = $builder
            ->withFields($this->config['fields'])
            ->withFilter('source_id', $this->config['source_id'] ?? null)
            ->export($writer, $filePath);

        ExportFile::find($this->exportId)->update([
            'status'       => 'ready',
            'file_path'    => $filePath,
            'total_rows'   => $result->total,
            'completed_at' => now(),
        ]);

        // Уведомить пользователя о готовности
        $this->user->notify(new ExportReadyNotification($this->exportId));
    }
}

Конфигурируемые шаблоны экспорта

Разные получатели хотят разные наборы полей. Шаблоны хранятся в БД:

{
  "name": "Для Яндекс.Маркета",
  "format": "xml",
  "fields": ["sku", "name", "price", "qty", "description", "category", "images", "brand"],
  "filters": {"in_stock": true},
  "transform": {
    "price": "round:2",
    "images": "first_only"
  }
}

Раздача готового файла

public function download(int $exportId): BinaryFileResponse
{
    $export = ExportFile::findOrFail($exportId);
    $this->authorize('download', $export);

    if ($export->status !== 'ready') {
        abort(202, 'Export is not ready yet');
    }

    return response()->download(
        storage_path($export->file_path),
        "products_{$export->created_at->format('Y-m-d_H-i')}.{$export->format}",
        ['Content-Type' => $this->mimeType($export->format)]
    );
}

Сроки реализации

  • CSV + Excel (openspout) + Builder с чанкированием — 2 дня
  • XML + JSON/NDJSON + асинхронный Job + уведомление — +1 день
  • Шаблоны экспорта в БД, трансформации полей, скачивание — +1 день