ייבוא מ-GMF: מרחב שמות, וריאנטים, הסרת כפילויות

ייבוא מוצרים מ-Google Merchant Feed הופך פעמים רבות לכאוס בגלל namespaces, וריאציות וכפילויות. אנחנו בונים אינטגרציות אמינות שמנתחות XML בקפידה, מקבצות נכון מוצרי וריאנטים ומסירות כפילויות. הצוות שלנו מוסר את הפרויקט במפתח מלא—מבדיקת הפיד ועד ליישום ולתמיכה שוטפת—כך שהקטלוג שלך נשאר מסונכרן.

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

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

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

השירותים שאנו מציעים
מציג 1 מתוך 1כל 2062 השירותים
ייבוא מ-GMF: מרחב שמות, וריאנטים, הסרת כפילויות
בינוני
~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

ייבוא מ-Google Merchant Feed: Namespace, וריאנטים, הסרת כפילויות

בעת ייבוא מוצרים מ-Google Merchant Feed (GMF), מפתחים נתקלים לעיתים קרובות בבעיות לא טריוויאליות: XML namespace, וריאנטים באמצעות item_group_id, והסרת כפילויות לפי GTIN. הצוות שלנו ביצע למעלה מ-50 אינטגרציות לחנויות בגדלים שונים — מחנויות קטנות עם 500 מוצרים ועד לחנויות גדולות עם 500,000 פריטים. באמצעות מקרה אמיתי של ספק סיטונאי אלקטרוניקה, נפרק את המורכבויות הללו ונספק פתרונות קוד מוכנים המבטיחים סנכרון קטלוג אמין.

GMF הוא פורמט XML המבוסס על RSS 2.0 עם שדות מה-namespace g:. ספקים ומפיצים מכינים אותו עבור Google Shopping, אך עבור חנות מקוונת זהו מקור מצוין לנתונים מובנים: שדות חובה id, title, description, price, availability — כל מה שצריך עבור קטלוג. עם זאת, ללא parser מתאים וטיפול במקרי קצה רבים, הייבוא הופך לכאב ראש. לפי הנתונים שלנו, כ-30% מהפידים מכילים שגיאות namespace, ל-15% חסר GTIN, ול-10% מטבע שגוי. עבור מקרים כאלה, הכנו בדיקות אוטומטיות ואסטרטגיות גיבוי המבוססות על עשור של ניסיון עם פידים.

Google Merchant Center דורש שפידים יעמדו במפרט RSS 2.0 עם הרחבות ב-namespace g:. מקור: תיעוד רשמי של Google.

טיפול ב-Namespace בפידים של Google Merchant

כל השדות שייכים ל-namespace http://base.google.com/ns/1.0. אם לא מתחשבים בכך, SimpleXML לא יוכל לקרוא את הערכים. להלן parser עובד עם טיפול נכון ב-namespace, שנבדק על אלפי פידים.

class GoogleMerchantFeedParser {
    private const G_NS = 'http://base.google.com/ns/1.0';

    public function parse(string $filePath): iterable {
        $reader = new \XMLReader();
        $reader->open($filePath);

        while ($reader->read()) {
            if ($reader->nodeType === \XMLReader::ELEMENT && $reader->name === 'item') {
                $node = new \SimpleXMLElement(
                    $reader->readOuterXml(),
                    0,
                    false,
                    '',
                    false
                );
                $g = $node->children(self::G_NS);
                yield $this->parseItem($node, $g);
            }
        }
        $reader->close();
    }

    private function parseItem(\SimpleXMLElement $item, \SimpleXMLElement $g): array {
        [$price, $currency] = $this->parsePrice((string) $g->price);
        [$salePrice] = $g->sale_price ? $this->parsePrice((string) $g->sale_price) : [null];

        $images = [(string) $g->image_link];
        foreach ($g->additional_image_link as $img) {
            $images[] = (string) $img;
        }

        return [
            'sku' => (string) $g->id,
            'name' => (string) $g->title,
            'description' => (string) $g->description,
            'price' => $price,
            'sale_price' => $salePrice,
            'currency' => $currency,
            'availability' => $this->parseAvailability((string) $g->availability),
            'brand' => (string) $g->brand,
            'gtin' => (string) $g->gtin,
            'mpn' => (string) $g->mpn,
            'condition' => (string) $g->condition,
            'product_type' => (string) $g->product_type,
            'google_category' => (string) $g->google_product_category,
            'item_group_id' => (string) $g->item_group_id,
            'images' => array_filter($images),
            'color' => (string) $g->color,
            'size' => (string) $g->size,
            'material' => (string) $g->material,
            'shipping_weight' => $this->parseWeight((string) $g->shipping_weight),
        ];
    }

    private function parsePrice(string $raw): array {
        if (preg_match('/^([\d.,]+)\s+([A-Z]{3})$/', trim($raw), $m)) {
            return [(float) str_replace(',', '.', $m[1]), $m[2]];
        }
        return [(float) $raw, 'USD'];
    }

    private function parseAvailability(string $raw): string {
        return match (strtolower(trim($raw))) {
            'in stock' => 'in_stock',
            'out of stock' => 'out_of_stock',
            'preorder', 'pre-order' => 'preorder',
            'backorder' => 'backorder',
            default => 'unknown',
        };
    }

    private function parseWeight(string $raw): ?float {
        if (!$raw) return null;
        if (preg_match('/^([\d.,]+)\s*(g|kg|lb|oz)$/i', trim($raw), $m)) {
            $value = (float) str_replace(',', '.', $m[1]);
            return match (strtolower($m[2])) {
                'kg' => $value,
                'g' => $value / 1000,
                'lb' => $value * 0.453592,
                'oz' => $value * 0.0283495,
            };
        }
        return null;
    }
}

מיפוי קטגוריות מוצרים של Google

Google משתמשת במזהים מספריים מהטקסונומיה שלה (לדוגמה, 142 = "Electronics > Audio > Headphones"). הטקסונומיה מתפרסמת כקובץ טקסט ומכילה כ-6,000 קטגוריות. אנו מתחזקים גרסה מעודכנת וממפים אותה אוטומטית לקטגוריות האתר. ניתוח עם XMLReader מהיר פי 2-3 מ-SimpleXML על פידים גדולים — קריטי לביצועים.

class GoogleTaxonomyMapper {
    private array $taxonomy;

    public function load(): void {
        $lines = file('https://www.google.com/basepages/producttype/taxonomy-with-ids.ru-RU.txt');
        foreach (array_slice($lines, 1) as $line) {
            [$id, $path] = explode(' - ', trim($line), 2);
            $this->taxonomy[(int) $id] = $path;
        }
    }

    public function resolve(int $googleId): ?int {
        $path = $this->taxonomy[$googleId] ?? null;
        if (!$path) return null;
        return CategoryMapping::where('google_taxonomy_id', $googleId)->value('site_category_id');
    }
}

קיבוץ מוצרי וריאנט באמצעות item_group_id

השדה class GoogleMerchantFeedParser { private const G_NS = 'http://base.google.com/ns/1.0'; public function parse(string $filePath): iterable { $reader = new \XMLReader(); $reader->open($filePath); while ($reader->read()) { if ($reader->nodeType === \XMLReader::ELEMENT && $reader->name === 'item') { $node = new \SimpleXMLElement( $reader->readOuterXml(), 0, false, '', false ); $g = $node->children(self::G_NS); yield $this->parseItem($node, $g); } } $reader->close(); } private function parseItem(\SimpleXMLElement $item, \SimpleXMLElement $g): array { [$price, $currency] = $this->parsePrice((string) $g->price); [$salePrice] = $g->sale_price ? $this->parsePrice((string) $g->sale_price) : [null]; $images = [(string) $g->image_link]; foreach ($g->additional_image_link as $img) { $images[] = (string) $img; } return [ 'sku' => (string) $g->id, 'name' => (string) $g->title, 'description' => (string) $g->description, 'price' => $price, 'sale_price' => $salePrice, 'currency' => $currency, 'availability' => $this->parseAvailability((string) $g->availability), 'brand' => (string) $g->brand, 'gtin' => (string) $g->gtin, 'mpn' => (string) $g->mpn, 'condition' => (string) $g->condition, 'product_type' => (string) $g->product_type, 'google_category' => (string) $g->google_product_category, 'item_group_id' => (string) $g->item_group_id, 'images' => array_filter($images), 'color' => (string) $g->color, 'size' => (string) $g->size, 'material' => (string) $g->material, 'shipping_weight' => $this->parseWeight((string) $g->shipping_weight), ]; } private function parsePrice(string $raw): array { if (preg_match('/^([\d.,]+)\s+([A-Z]{3})$/', trim($raw), $m)) { return [(float) str_replace(',', '.', $m[1]), $m[2]]; } return [(float) $raw, 'USD']; } private function parseAvailability(string $raw): string { return match (strtolower(trim($raw))) { 'in stock' => 'in_stock', 'out of stock' => 'out_of_stock', 'preorder', 'pre-order' => 'preorder', 'backorder' => 'backorder', default => 'unknown', }; } private function parseWeight(string $raw): ?float { if (!$raw) return null; if (preg_match('/^([\d.,]+)\s*(g|kg|lb|oz)$/i', trim($raw), $m)) { $value = (float) str_replace(',', '.', $m[1]); return match (strtolower($m[2])) { 'kg' => $value, 'g' => $value / 1000, 'lb' => $value * 0.453592, 'oz' => $value * 0.0283495, }; } return null; } } מקבץ וריאנטים של אותו מוצר (צבעים/מידות שונים). אנו מקבצים אותם, יוצרים מוצר אב ווריאנטים ילדים לפי מאפייני צבע/מידה. זה מאפשר הצגה נכונה של גרסאות בקטלוג וניהול נפרד של מחירים ומלאי עבור כל וריאנט.

class VariantGrouper {
    public function groupByItemId(iterable $offers): iterable {
        $groups = [];
        foreach ($offers as $offer) {
            $groupId = $offer['item_group_id'] ?: $offer['sku'];
            $groups[$groupId][] = $offer;
        }
        foreach ($groups as $groupId => $variants) {
            if (count($variants) === 1) {
                yield ['type' => 'simple', 'data' => $variants[0]];
            } else {
                yield ['type' => 'variable', 'group_id' => $groupId, 'variants' => $variants];
            }
        }
    }
}

אחסון GTIN וחיפוש לפי ברקוד

GTIN (EAN-13, UPC, ISBN) הוא מזהה גלובלי המאפשר התאמה חד-משמעית של מוצר מהפיד לרשומה קיימת בקטלוג. עדיפות: GTIN > SKU > MPN. אנו מגדירים הסרת כפילויות כדי למנוע כפילויות. דוגמת שאילתה:

$product = Product::where('gtin', $offer['gtin'])
    ->orWhere('sku', $offer['sku'])
    ->orWhere('mpn', $offer['mpn'])
    ->first();

טיפול בפידים דחוסים

Google ממליצה לדחוס פידים גדולים. אנו מזהים אוטומטית את סיומת ה-.gz ומבצעים דחיסה תוך כדי קריאה באמצעות gzopen, וכותבים לקובץ זמני לצורך ניתוח后续.

private function openFeed(string $url): string {
    $tmpFile = tempnam(sys_get_temp_dir(), 'gmf_');
    if (str_ends_with(parse_url($url, PHP_URL_PATH), '.gz')) {
        $gz = gzopen($url, 'rb');
        $fp = fopen($tmpFile, 'wb');
        while (!gzeof($gz)) fwrite($fp, gzread($gz, 8192));
        gzclose($gz);
        fclose($fp);
    } else {
        copy($url, $tmpFile);
    }
    return $tmpFile;
}

השוואת שיטות ניתוח

פרמטר XMLReader SimpleXML
גישה streaming, דמוי SAX DOM (טוען הכל לזיכרון)
צריכת זיכרון O(1) O(N)
מהירות על 100 אלף פריטים ~1.5 שניות ~4 שניות
יכולת כתיבה לא כן

שדות חובה ב-Google Merchant Feed

שדה תיאור דוגמה
id מזהה ייחודי "12345"
title שם המוצר "קומקום חשמלי BOSH"
description תיאור המוצר "קומקום 1.5 ליטר"
link כתובת דף המוצר כתובת דף המוצר
image_link כתובת התמונה הראשית כתובת התמונה
availability זמינות (במלאי, אזל מהמלאי) "במלאי"
gtin ברקוד גלובלי "4601234567890"
brand מותג "BOSH"
item_group_id מזהה קבוצה לוריאנטים "group_123"

מדריך אינטגרציה שלב אחר שלב

  1. קבלת גישה לפיד (URL או FTP). Google Merchant Center מספק קישור לפיד לאחר העלאתו.
  2. ניתוח הפיד באמצעות XMLReader עם namespace class GoogleTaxonomyMapper { private array $taxonomy; public function load(): void { $lines = file('https://www.google.com/basepages/producttype/taxonomy-with-ids.ru-RU.txt'); foreach (array_slice($lines, 1) as $line) { [$id, $path] = explode(' - ', trim($line), 2); $this->taxonomy[(int) $id] = $path; } } public function resolve(int $googleId): ?int { $path = $this->taxonomy[$googleId] ?? null; if (!$path) return null; return CategoryMapping::where('google_taxonomy_id', $googleId)->value('site_category_id'); } } — כפי שמוצג בקוד לעיל.
  3. מיפוי קטגוריות: טעינת הטקסונומיה של Google ומיפוי לקטגוריות האתר שלך.
  4. ביצוע הסרת כפילויות: עדיפות לפי GTIN, ולאחר מכן לפי SKU או MPN.
  5. קיבוץ וריאנטים של מוצרים לפי השדה g:item_group_id.
  6. טעינת מוצרים לקטלוג, יצירה או עדכון של רשומות.
  7. הגדרת עדכונים תקופתיים (יומיים או מתוזמנים) באמצעות אותו parser.

עבור פידים עם מעל 200,000 מוצרים, אנו ממליצים לשמור במטמון את מיפויי הקטגוריות ב-Redis ולבטל רישום לוג במהלך ייבוא אצווה. זה מניב שיפור מהירות נוסף של 20-30%.

מה כלול באינטגרציה

אנו מציעים יישום ייבוא סוהר הכולל:

  • Parser מותאם אישית עם תמיכה נכונה ב-namespace וטיפול בשגיאות.
  • מיפוי קטגוריות Google לעץ הקטגוריות של האתר שלך.
  • לוגיקת הסרת כפילויות לפי GTIN, SKU או MPN.
  • קיבוץ מוצרי וריאנט באמצעות item_group_id.
  • תמיכה בפידים דחוסים (.gz), דחיסה אוטומטית.
  • עיבוד ואימות תמונות.
  • בדיקות על הפיד האמיתי שלך עם כיסוי של 100%.
  • תיעוד מלא והעברת קוד.
  • שעת הדרכה אחת לצוות שלך.
  • 30 ימי תמיכה לאחר ההשקה.

הניסיון שלנו של 10+ שנים ולמעלה מ-50 אינטגרציות מוצלחות מבטיחים התקנה חלקה. פריסת אינטגרציה זו מקצרת את זמן עדכון הקטלוג ב-90% וחוסכת בדרך כלל 80,000 RUB בשנה בעבודת יד. תקופת ההחזר היא פחות מחודשיים.

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