ניתוח אוטומטי של רשימות מחירים: Excel, CSV, XML

ספקים שולחים רשימות מחיר במגוון פורמטים, ועיבוד ידני הופך לשגרה עם שגיאות. אנחנו מפתחים מפענחים אוניברסליים במפתח מלא, שאוספים ומנרמלים נתונים אוטומטית מכל מקור, ומבטיחים פעולה אמינה עם תמיכה מתמשכת.

פיתוח ותחזוקה של כל סוגי האתרים:

אתרי מידע או יישומי אינטרנט
אתרי תדמית, דפי נחיתה, אתרי חברה, קטלוגים מקוונים, חידונים, אתרי קידום, בלוגים, מקורות חדשות, פורטלי מידע, פורומים, אגרגטורים
אתרי מסחר אלקטרוני או יישומי אינטרנט
חנויות מקוונות, פורטלי B2B, שווקים, בורסות מקוונות, אתרי קאשבק, בורסות, פלטפורמות דרופשיפינג, מנתחי מוצרים
יישומי אינטרנט לניהול תהליכים עסקיים
מערכות CRM, מערכות ERP, פורטלים ארגוניים, מערכות ניהול ייצור, מנתחי מידע
אתרי שירות אלקטרוני או יישומי אינטרנט
פלטפורמות מודעות, בתי ספר מקוונים, בתי קולנוע מקוונים, בוני אתרים, פורטלים לשירותים אלקטרוניים, פלטפורמות אירוח וידאו, פורטלים נושאיים

אלה רק חלק מהסוגים הטכניים של אתרים שאנו עובדים איתם, ולכל אחד מהם יכולים להיות מאפיינים ופונקציונליות ספציפיים משלו, וכן ניתן להתאים אותם לצרכים ולמטרות הספציפיים של הלקוח.

השירותים שאנו מציעים
מציג 1 מתוך 1כל 2062 השירותים
ניתוח אוטומטי של רשימות מחירים: Excel, CSV, XML
בינוני
~3-5 ימים

הכישורים שלנו:

שאלות נפוצות

העבודות האחרונות

  • פיתוח אפליקציית ווב עבור FEEDME
    פיתוח אפליקציית ווב עבור FEEDME
    1365
  • פיתוח חנות מקוונת לחברת FURNORO
    פיתוח חנות מקוונת לחברת FURNORO
    1330
  • פיתוח אפליקציית ווב עבור Enviok
    פיתוח אפליקציית ווב עבור Enviok
    1077
  • פיתוח CRM עבור Chasseurs
    פיתוח CRM עבור Chasseurs
    1113
  • פיתוח אתר עבור SBH Partners
    פיתוח אתר עבור SBH Partners
    1201
  • פיתוח אתר עבור Red Pear
    פיתוח אתר עבור Red Pear
    619

ספק שלח קובץ Excel עם תאים ממוזגים, קובץ CSV ב-Windows-1251, ו-XML עם תגיות לא סטנדרטיות. עיבוד ידני פירושו שעות של שגרה ושגיאות סנכרון. לקוח בתחום האלקטרוניקה קיבל מחירונים מ-12 ספקים בפורמטים שונים: חלק שלחו Excel עם טבלאות ציר, אחרים CSV ב-KOI8-R, ואחרים XML עם namespaces. טעינה ידנית ארכה יומיים בשבוע, ושגיאות הובילו לרמות מלאי שגויות. כתבנו מפענח אוניברסלי עם קונפיגורציה לכל ספק — כעת העדכונים אורכים 5 דקות ביום, וחוסכים למעלה מ-40 שעות בחודש וכ-$2,500 בחודש. במשך 5 שנים פיתחנו מפענחים כאלה ל-50+ פרויקטים, והנה איך זה עובד.

אתגרים נפוצים בפענוח מחירונים

  • Excel: הנתונים לא מתחילים מהשורה הראשונה, כותרות בתאים ממוזגים, מספרים מאוחסנים כטקסט, תאריכים בפורמט Excel.
  • CSV: קידוד Windows-1251, תו מפריד נקודה-פסיק, מחירים עם רווחים או נקודות.
  • XML: סכמות שונות — מוצרים יכולים להיות attributes, אלמנטים מקוננים, או namespaces.
  • SKUs לא עקביים: ספק אחד כותב מק"ט כ-'12345', אחר כ-'AB-123/45'. המפענח מנרמל הכל לפורמט אחיד.
  • שאילתת N+1: טעינת מוצרים אחד-אחד הייתה מפילה את מסד הנתונים. אנו משתמשים בקבוצות של 500 רשומות ותורים — זה מהיר פי 10 מייבוא שורה-אחר-שורה.

השוואת פורמטים לפי פרמטרים מרכזיים:

פורמט גמישות קונפיגורציה מהירות פענוח מורכבות הטמעה
Excel גבוהה בינונית בינונית (טיפול בנתונים מרובי שורות)
CSV בינונית גבוהה נמוכה (זיהוי אוטומטי של תו מפריד)
XML גבוהה מאוד נמוכה גבוהה (XPath, namespaces)

טיפול בתאים ממוזגים ב-Excel

אנו משתמשים בספריית PhpSpreadsheet (https://github.com/PHPOffice/PhpSpreadsheet). הטכניקה המרכזית היא קריאת הערך מהתא השמאלי-עליון של הטווח הממוזג. בקונפיגורציה אנו מציינים את שורת הכותרות, שורת תחילת הנתונים, ו-aliases לעמודות. המפענח ממפה אוטומטית כותרות לשדות שלך, תוך התעלמות מתאים ממוזגים ריקים. זה חוסך עד 90% מזמן הכנת הנתונים.

חשיבות הזיהוי האוטומטי לקידוד CSV

ספקים משתמשים לעתים קרובות ב-Windows-1251, וחלק ב-KOI8-R. ללא זיהוי אוטומטי, הנתונים מגיעים כג'יבריש. המפענח שלנו, באמצעות mb_detect_encoding, ממיר את התוכן ל-UTF-8 לפני הפענוח. הוא גם מנחש אוטומטית את התו המפריד (פסיק, נקודה-פסיק, טאב) על ידי ניתוח השורה הראשונה. כתוצאה מכך, שגיאות קידוד מצטמצמות ל-0.1% מהמקרים.

איך אנו מתכננים מפענח אוניברסלי

אנו משתמשים בספריית PhpSpreadsheet עבור Excel, בפונקציות PHP מובנות עבור CSV, וב-SimpleXML עבור XML. תבנית ה-Repository הקלאסית מבודדת את לוגיקת הפענוח ממקור הנתונים. להלן המחלקות המרכזיות.

מפענח Excel (PhpSpreadsheet) – מימוש PHP למפענח Excel

// app/Services/PriceList/ExcelParser.php
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;

class ExcelParser
{
    private array $columnMap = [];

    public function parse(string $filePath, array $config): array
    {
        $spreadsheet = IOFactory::load($filePath);
        $sheet = $spreadsheet->getSheetByName($config['sheet_name'] ?? null) ?? $spreadsheet->getActiveSheet();
        $headerRow = $config['header_row'] ?? 1;
        $dataStartRow = $config['data_start_row'] ?? 2;

        // Определяем маппинг колонок по заголовкам
        $this->detectColumns($sheet, $headerRow, $config['column_aliases']);

        $products = [];
        $highestRow = $sheet->getHighestRow();

        for ($row = $dataStartRow; $row <= $highestRow; $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)),
                'in_stock' => $this->parseStock($this->getCellValue($sheet, $this->columnMap['stock'] ?? null, $row)),
                'category' => $this->getCellValue($sheet, $this->columnMap['category'] ?? null, $row),
            ];
        }

        return $products;
    }

    private function detectColumns(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $sheet, int $headerRow, array $aliases): void
    {
        $highestCol = $sheet->getHighestColumn();
        $highestColIndex = Coordinate::columnIndexFromString($highestCol);

        for ($col = 1; $col <= $highestColIndex; $col++) {
            $header = strtolower(trim(
                $sheet->getCellByColumnAndRow($col, $headerRow)->getValue() ?? ''
            ));

            foreach ($aliases as $fieldName => $possibleHeaders) {
                foreach ($possibleHeaders as $alias) {
                    if (str_contains($header, strtolower($alias))) {
                        $this->columnMap[$fieldName] = $col;
                        break 2;
                    }
                }
            }
        }
    }

    private function parsePrice(mixed $value): float
    {
        if (is_numeric($value)) return (float) $value;
        return (float) preg_replace('/[^\d.]/', '', str_replace(',', '.', (string) $value));
    }

    private function parseStock(mixed $value): bool
    {
        if (is_null($value)) return true;
        $s = strtolower(trim((string) $value));
        return !in_array($s, ['0', 'нет', 'no', 'false', 'отсутствует', '']);
    }
}

קונפיגורציה לספק ספציפי:

// config/price_list_parsers.php
return [
    'supplier_abc' => [
        'format' => 'excel',
        'sheet_name' => 'Прайс',
        'header_row' => 2,
        'data_start_row' => 3,
        'column_aliases' => [
            'sku' => ['Артикул', 'SKU', 'Код товара'],
            'name' => ['Наименование', 'Название', 'Товар'],
            'price' => ['Цена', 'Цена, руб.', 'Стоимость'],
            'stock' => ['Остаток', 'Наличие', 'Кол-во'],
            'category' => ['Категория', 'Группа', 'Раздел'],
        ],
    ],
];

מפענח 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);
        $headers = array_map('trim', $headers);
        $products = [];
        foreach ($lines as $line) {
            if (empty(trim($line))) continue;
            $row = str_getcsv($line, $delimiter);
            if (count($row) !== count($headers)) continue;
            $data = array_combine($headers, $row);
            $products[] = $this->normalizeRow($data, $config);
        }
        return $products;
    }

    private function detectDelimiter(string $content): string {
        $firstLine = strtok($content, "\n");
        $counts = [
            ',' => substr_count($firstLine, ','),
            ';' => substr_count($firstLine, ';'),
            "\t" => substr_count($firstLine, "\t"),
        ];
        return array_key_first(array_filter($counts, fn($c) => $c === max($counts)));
    }

    private function normalizeRow(array $row, array $config): array {
        $map = $config['field_map'] ?? [];
        return [
            'sku' => trim($row[$map['sku'] ?? 'sku'] ?? ''),
            'name' => trim($row[$map['name'] ?? 'name'] ?? ''),
            'price' => $this->parsePrice($row[$map['price'] ?? 'price'] ?? 0),
            'in_stock' => !empty($row[$map['stock'] ?? 'stock']),
        ];
    }
}

מפענח XML

// app/Services/PriceList/XmlParser.php
class XmlParser
{
    public function parse(string $filePath, array $config): array
    {
        $xml = simplexml_load_file($filePath, 'SimpleXMLElement', LIBXML_NOCDATA);
        if ($xml === false) {
            throw new \RuntimeException("Не удалось разобрать XML: " . $filePath);
        }

        // XPath для извлечения товаров
        $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'] ?? [];

        $extract = function(string $path) use ($item): string {
            // Поддержка атрибутов (@attr) и дочерних элементов (tag)
            if (str_starts_with($path, '@')) {
                return (string) ($item->attributes()[substr($path, 1)] ?? '');
            }

            $nodes = $item->xpath($path);
            return $nodes ? trim((string) $nodes[0]) : '';
        };

        return [
            'sku' => $extract($fields['sku'] ?? 'article'),
            'name' => $extract($fields['name'] ?? 'name'),
            'price' => (float) $extract($fields['price'] ?? 'price'),
            'in_stock' => $extract($fields['stock'] ?? 'available') !== '0',
            'category' => $extract($fields['category'] ?? 'category'),
        ];
    }
}

Facade לניתוב פורמטים

// app/Services/PriceList/PriceListParser.php
class PriceListParser {
    public function parse(string $filePath, array $config): array
    {
        $format = $config['format'] ?? $this->detectFormat($filePath);
        return match ($format) {
            'excel', 'xlsx', 'xls' => app(ExcelParser::class)->parse($filePath, $config),
            'csv' => app(CsvParser::class)->parse($filePath, $config),
            'xml' => app(XmlParser::class)->parse($filePath, $config),
            default => throw new \InvalidArgumentException("Неизвестный формат: {$format}"),
        };
    }

    private function detectFormat(string $filePath): string
    {
        return match (strtolower(pathinfo($filePath, PATHINFO_EXTENSION))) {
            'xlsx', 'xls', 'ods' => 'excel',
            'csv', 'txt' => 'csv',
            'xml' => 'xml',
            default => 'csv',
        };
    }
}
מידע נוסף על קונפיגורציית המיפוי

כל ספק מקבל קובץ קונפיגורציה משלו, המגדיר:

  • מיפוי כותרות עמודות לשדות מסד הנתונים
  • פורמט תאריך (d.m.Y, Y-m-d, סידורי Excel)
  • כללי המרת מחירים (הסרת רווחים, החלפת פסיק בנקודה)
  • אינדיקטור מלאי (מספר, 'כן'/'לא')

כשהפורמט משתנה, פשוט עורכים את הקובץ הזה — איננו נוגעים בקוד המפענח. זה מאפשר הוספת ספק חדש תוך 1–2 שעות.

ציר זמן לפרויקט

תרחיש ציר זמן
מפענח לספק אחד (פורמט אחד, מבנה סטנדרטי) 2–4 ימי עבודה
מפענח אוניברסלי + 5 ספקים בפורמטים שונים 7–10 ימי עבודה

עלות הפיתוח מחושבת באופן אישי ותלויה במורכבות פורמטי הספקים. ההשקעה מחזירה את עצמה דרך הפחתת עבודה ידנית ושיפור דיוק הנתונים.

חבילת תוצרים

  • קוד מקור של המפענח עם קונפיגורציות לכל ספק
  • הוראות להוספת ספק חדש (עם דוגמאות)
  • אחזור קבצים אוטומטי מוגדר (FTP, דוא"ל, HTTP)
  • אינטגרציה עם התור שלך (Laravel Horizon / Supervisor)
  • נתוני בדיקה ל-3 ספקים
  • חודש תמיכה לאחר המסירה

המומחיות והערבויות שלנו

יש לנו 5 שנות ניסיון בפיתוח מפענחים למסחר אלקטרוני. הטמענו למעלה מ-50 אינטגרציות עם ספקים מתעשיות שונות. הפתרונות שלנו עובדים בעומסים של עד 100,000 מוצרים לייבוא. אנו מבטיחים יציבות — כל תקלה מטופלת עם התראות אוטומטיות.

צרו קשר — ננתח את הספקים שלכם ונציע פתרון אופטימלי תוך יום אחד. הזמינו פיתוח מפענח וקבלו אוטומציה תוך 2–10 ימים.