עדכון אוטומטי של תיאורי מוצרים ומאפיינים
בעת סנכרון מוצרים מהזנת XML של ספק, תיאורים ומאפיינים נמחקים ומוחלפים, מה שגורם לאובדן עריכות ידניות של מנהלי תוכן. זה מוביל לעבודה כפולה ולשגיאות. יישמנו מערכת עם אחסון נתונים נפרד ומנגנון עקיפה שטוען נתונים אוטומטית אך נותן עדיפות לשינויים ידניים. גישה זו מעבדת עד 100,000 מוצרים ב-15 דקות—פי 30 מהר יותר מעדכונים ידניים. במשך 7 שנים, השלמנו יותר מ-50 פרויקטים של סנכרון קטלוגים. נבחן את הפרויקט שלך ונציע פתרון אופטימלי.
בעיות שאנו פותרים
-
מחיקת עריכות ידניות: אם מנהל תוכן ערך תיאור ידנית, האוטומציה לא צריכה למחוק אותו. פתרון: שדות נפרדים
CREATE TABLE product_content ( product_id int REFERENCES products(id), source varchar(30), -- supplier_id или 'manual' field varchar(50), -- description | spec_weight | spec_color ... value text, is_manual_override boolean DEFAULT false, supplier_value text, -- последнее значение от поставщика updated_at timestamptz, PRIMARY KEY (product_id, field) );ו-class XmlDescriptionSource implements DescriptionSourceInterface { public function fetch(): iterable { $xml = new \XMLReader(); $xml->open($this->url); while ($xml->read()) { if ($xml->nodeType === \XMLReader::ELEMENT && $xml->name === 'product') { $node = new \SimpleXMLElement($xml->readOuterXml()); yield $this->parseProduct($node); } } $xml->close(); } private function parseProduct(\SimpleXMLElement $node): array { $data = [ 'sku' => (string) $node['article'], 'description' => (string) $node->description, 'attributes' => [], ]; foreach ($node->attributes->attribute as $attr) { $data['attributes'][(string) $attr['name']] = (string) $attr; } return $data; } }עם דגלclass UpdateProductDescriptionsJob implements ShouldQueue { public int $tries = 3; public int $backoff = 60; // секунды между повторами public function handle( DescriptionSourceInterface $source, AttributeNormalizer $normalizer, ContentUpdater $updater, ): void { foreach ($source->fetch() as $item) { $productId = Product::where('sku', $item['sku'])->value('id'); if (!$productId) continue; $updater->updateField($productId, 'description', $item['description']); foreach ($item['attributes'] as $name => $value) { $normalized = $normalizer->normalize($name, $value); if ($normalized) { $updater->updateField($productId, $normalized['key'], $normalized['value']); } } } } }. - פורמטים הטרוגניים של נתונים: כל ספק שולח מאפיינים בפורמט משלו. יש צורך בנורמליזטור כדי להביא הכל לסכימה פנימית אחידה.
- נפח נתונים: קטלוג של 100,000 מוצרים דורש עיבוד זורם כדי למנוע בעיות זיכרון.
- חוסר שליטה: מנהלים לא יכולים לראות מה השתנה מהספק ולא יכולים לאשר או לדחות שינויים.
כיצד פועלת מערכת העדכון המנוהל
עיקרון מרכזי: הפרדת מקור הנתונים (הספק) והתוכן הסופי (מה שמוצג באתר), עם דגל "נערך ידנית".
CREATE TABLE product_content (
product_id int REFERENCES products(id),
source varchar(30), -- supplier_id или 'manual'
field varchar(50), -- description | spec_weight | spec_color ...
value text,
is_manual_override boolean DEFAULT false,
supplier_value text, -- последнее значение от поставщика
updated_at timestamptz,
PRIMARY KEY (product_id, field)
);בעדכון אוטומטי: אם class ContentUpdater { public function updateField(int $productId, string $field, mixed $newValue): void { $existing = ProductContent::where([ 'product_id' => $productId, 'field' => $field, ])->first(); if (!$existing) { ProductContent::create([ 'product_id' => $productId, 'field' => $field, 'value' => $newValue, 'supplier_value' => $newValue, ]); return; } $existing->supplier_value = $newValue; if (!$existing->is_manual_override) { $existing->value = $newValue; } $existing->updated_at = now(); $existing->save(); } } , עדכן רק את supplier_value, לא את value. מנהל התוכן רואה את הפער בממשק ומחליט אם לקבל את השינוי של הספק. ארכיטקטורה זו מספקת גמישות שאושרה בפועל.
למה חשוב להפריד בין מקור הנתונים לתוכן הסופי?
ללא הפרדה, כל עדכון אוטומטי מוחק עריכות ידניות. הגישה שלנו עם מנגנון העקיפה שומרת על עריכות העורכים בעוד הספק תמיד רואה נתונים עדכניים. זה קריטי כאשר פריטי מוצר נערכים על ידי מספר אנשים.
מקורות לתיאורים
הזנת XML של ספק
רוב חברות הייצור מספקות XML עם מאפיינים מורחבים. מנתח ה-PHP קורא את הקובץ בזרימה באמצעות XMLReader—זה מאפשר עיבוד קטלוגים בכל גודל ללא עומס זיכרון.
class XmlDescriptionSource implements DescriptionSourceInterface
{
public function fetch(): iterable
{
$xml = new \XMLReader();
$xml->open($this->url);
while ($xml->read()) {
if ($xml->nodeType === \XMLReader::ELEMENT && $xml->name === 'product') {
$node = new \SimpleXMLElement($xml->readOuterXml());
yield $this->parseProduct($node);
}
}
$xml->close();
}
private function parseProduct(\SimpleXMLElement $node): array
{
$data = [
'sku' => (string) $node['article'],
'description' => (string) $node->description,
'attributes' => [],
];
foreach ($node->attributes->attribute as $attr) {
$data['attributes'][(string) $attr['name']] = (string) $attr;
}
return $data;
}
} API עם עדכונים חלקיים
אם הספק מספק נקודת קצה לשינויים, הבקשה מחזירה רק מוצרים שבהם לפחות שדה אחד שצוין השתנה—מה שמפחית משמעותית את נפח העיבוד.
שרשרת עבודה לעדכון תוכן
עדכון תיאורים כבד יותר מעדכון מחירים—התוכן גדול, יש צורך בנורמליזציה של מאפיינים, ויש לבדוק דגלי עקיפה. סכמה אופטימלית: תור נפרד עם מקביליות נמוכה.
class UpdateProductDescriptionsJob implements ShouldQueue {
public int $tries = 3;
public int $backoff = 60; // секунды между повторами
public function handle(
DescriptionSourceInterface $source,
AttributeNormalizer $normalizer,
ContentUpdater $updater,
): void {
foreach ($source->fetch() as $item) {
$productId = Product::where('sku', $item['sku'])->value('id');
if (!$productId) continue;
$updater->updateField($productId, 'description', $item['description']);
foreach ($item['attributes'] as $name => $value) {
$normalized = $normalizer->normalize($name, $value);
if ($normalized) {
$updater->updateField($productId, $normalized['key'], $normalized['value']);
}
}
}
}
} לוגיקת ContentUpdater
class ContentUpdater {
public function updateField(int $productId, string $field, mixed $newValue): void {
$existing = ProductContent::where([
'product_id' => $productId,
'field' => $field,
])->first();
if (!$existing) {
ProductContent::create([
'product_id' => $productId,
'field' => $field,
'value' => $newValue,
'supplier_value' => $newValue,
]);
return;
}
$existing->supplier_value = $newValue;
if (!$existing->is_manual_override) {
$existing->value = $newValue;
}
$existing->updated_at = now();
$existing->save();
}
} לוח זמנים ועדיפויות
| סוג נתונים | תדירות | סיבה |
|---|---|---|
| מאפיינים | פעם ביום | משתנים לעיתים רחוקות |
| תיאורים | פעם ביום | נפח גדול, לא דחוף |
| סטטוסי תעודות | פעם בשבוע | שינויים נדירים עוד יותר |
| מחירים | כל 15-30 דקות | תנודתיות גבוהה |
אילו מקורות נוספים ניתן לחבר?
מלבד XML ו-API, ניתן לשלב CSV, JSON, Excel וגישה ישירה למסד הנתונים של מערכת הספק. הטבלה שלהלן משווה בין השיטות העיקריות.
| מקור | מהירות עיבוד | מורכבות יישום |
|---|---|---|
| הזנת XML | גבוהה (זרימה) | בינונית |
| REST API | בינונית (מגבלות קצב) | בינונית |
| CSV (S3) | גבוהה | נמוכה |
| שכפול SQL | גבוהה מאוד | גבוהה |
ממשק פערים בפאנל הניהול
אם value != supplier_value AND is_manual_override = true, הצג אזהרה בממשק המוצר: "הספק שינה את הערך. נוכחי: X, חדש מהספק: Y. לאשר?" עם כפתורים "אשר" ו-"שמור".
בעיות יישום אופייניות
קשיים נפוצים ופתרונותיהם
- הזנות ספק לא מלאות: לפעמים XML חסר מאפיינים חובה. פתרון: הגדרת ברירת מחדל לערכים או שליחת הודעה.
- שינוי מבנה הזנה: ללא גרסת סכמה, המנתח נשבר. נוהג טוב: לאמת מבנה בגישה הראשונה ולרשום שגיאות.
- קונפליקטים של קידוד: UTF-8 מול Windows-1251. הנורמליזטור צריך לזהות קידוד אוטומטית ולהמיר.
מה כלול בעבודה
- עיצוב סכמת נתונים וטבלת
product_content - יישום מנתח להזנת XML או API של ספק
- נורמליזטור מאפיינים עם מיפוי שמות וסוגים
- הגדרת תור לעדכוני רקע
- ממשק פערים בפאנל הניהול
- בדיקות ותיעוד
- הדרכת מנהלי תוכן
צור קשר כדי שנבחן את הפרויקט שלך—נבחר את ארכיטקטורת הסנכרון האופטימלית. הזמן יישום של עדכון קטלוג אוטומטי, והמנהלים שלך יפסיקו לבזבז זמן על עריכות שגרתיות.







