Парсер прайс-листів поставщиків (Excel/CSV/XML)
Поставщики присилають прайс-листи в різних форматах: Excel з нестандартною структурою, CSV з кирилицею в різних кодуваннях, XML різної степені стандартизованості. Завдання парсера — нормалізувати все це в єдиний формат та синхронізувати з каталогом магазину.
Типічні проблеми вхідних файлів
- Excel: дані починаються з 3-го рядка, заголовки в об'єднаних ячейках
- CSV: кодування windows-1251, розділювач — крапка з комою, ціни з пробілами
- XML: нестандартні теги, простори імен, атрибути замість значень
- Несогласовані формати SKU у різних поставщиків
- Числа як рядки, дати як числа Excel
Парсер Excel (PhpSpreadsheet)
// app/Services/PriceList/ExcelParser.php
class ExcelParser {
public function parse(string $filePath, array $config): array {
$spreadsheet = IOFactory::load($filePath);
$sheet = $spreadsheet->getActiveSheet();
$this->detectColumns($sheet, $config['header_row'] ?? 1, $config['column_aliases']);
$products = [];
for ($row = $config['data_start_row'] ?? 2; $row <= $sheet->getHighestRow(); $row++) {
$sku = $this->getCellValue($sheet, $this->columnMap['sku'], $row);
if (empty($sku)) continue;
$products[] = [
'sku' => trim($sku),
'name' => $this->getCellValue($sheet, $this->columnMap['name'] ?? null, $row),
'price' => $this->parsePrice($this->getCellValue($sheet, $this->columnMap['price'], $row)),
'stock' => $this->parseStock($this->getCellValue($sheet, $this->columnMap['stock'] ?? null, $row)),
];
}
return $products;
}
}
CSV парсер з автовизначенням кодування
// app/Services/PriceList/CsvParser.php
class CsvParser {
public function parse(string $filePath, array $config = []): array {
$content = file_get_contents($filePath);
// Автовизначення кодування
$encoding = mb_detect_encoding($content, ['UTF-8', 'Windows-1251', 'KOI8-R'], true);
if ($encoding && $encoding !== 'UTF-8') {
$content = mb_convert_encoding($content, 'UTF-8', $encoding);
}
// Автовизначення розділювача
$delimiter = $config['delimiter'] ?? $this->detectDelimiter($content);
$lines = str_getcsv($content, "\n");
$headers = str_getcsv(array_shift($lines), $delimiter);
$products = [];
foreach ($lines as $line) {
if (empty(trim($line))) continue;
$row = str_getcsv($line, $delimiter);
$data = array_combine($headers, $row);
$products[] = $this->normalizeRow($data, $config);
}
return $products;
}
}
XML парсер
// app/Services/PriceList/XmlParser.php
class XmlParser {
public function parse(string $filePath, array $config): array {
$xml = simplexml_load_file($filePath, 'SimpleXMLElement', LIBXML_NOCDATA);
$itemXpath = $config['item_xpath'] ?? '//item';
$items = $xml->xpath($itemXpath);
return array_map(fn($item) => $this->extractItem($item, $config), $items);
}
private function extractItem(\SimpleXMLElement $item, array $config): array {
$fields = $config['fields'] ?? [];
return [
'sku' => (string) $item->xpath($fields['sku'] ?? 'article')[0] ?? '',
'name' => (string) $item->xpath($fields['name'] ?? 'name')[0] ?? '',
'price' => (float) ($item->xpath($fields['price'] ?? 'price')[0] ?? 0),
'stock' => ((string) ($item->xpath($fields['stock'] ?? 'available')[0] ?? '1')) !== '0',
];
}
}
Автоматичне отримання файлів
Прайс-листи надходять по email, FTP або HTTP:
// app/Jobs/FetchSupplierPriceList.php
class FetchSupplierPriceList implements ShouldQueue {
public function handle(PriceListFetcher $fetcher, PriceListParser $parser): void {
$supplier = Supplier::findOrFail($this->supplierId);
$filePath = match ($supplier->price_list_source) {
'ftp' => $fetcher->downloadFromFtp($supplier),
'url' => $fetcher->downloadFromUrl($supplier->price_list_url),
'email' => $fetcher->fetchFromEmail($supplier),
};
$config = config("price_list_parsers.{$supplier->config_key}");
$products = $parser->parse($filePath, $config);
foreach (array_chunk($products, 500) as $chunk) {
ImportPriceListChunk::dispatch($supplier->id, $chunk);
}
}
}
Терміни розробки
Парсер для 1 поставщика (1 формат, стандартна структура): 2-4 робочих дні. Універсальний диспетчер + 5 поставщиків з різними форматами: 7-10 робочих днів.







