הגדרת ניטור מחירי מתחרים עבור 1C-Bitrix
עבדנו עם קמעונאית מקוונת גדולה למוצרי חשמל ביתיים, שהמתחרים שלה שינו מחירים עד 15 פעמים ביום. ניטור ידני לקח למנהל שעתיים ביום, ועיכובי תגובה עלו עד 5% מרווח על פריטים בודדים. הפתרון שלנו: מעקב אוטומטי אחר מחירי מתחרים בתוך 1C-Bitrix.
מעקב מחירים נבנה באחת משתי דרכים: דרך שירות ניטור מוכן (Competera, Metacommerce, Priceva) או באמצעות מנתח מותאם אישית. שירותים מוכנים פשוטים ואמינים יותר, אך יקרים עבור קטלוגים מעל 10,000 פריטים. מנתחים מותאמים אישית גמישים וזולים לתפעול, אך דורשים תחזוקה שוטפת כאשר אתרי המתחרים משתנים. בשני המקרים, משימת ההתקנה זהה: לאסוף נתונים, לשמור אותם ב-Bitrix, ולהציג אותם למנהל בצורה נוחה.
השוואת גישות: שירות מול מנתח
| קריטריון | שירות מוכן | מנתח מותאם אישית |
|---|---|---|
| מהירות יישום | 1–2 ימים | 3–5 ימים |
| תלות ב-API חיצוני | כן (מפתח, מגבלות) | לא (אך תלוי במבנה האתר) |
| עלות תפעול | גבוהה (מנוי) | נמוכה (שרת בלבד) |
| יציבות | גבוהה | בינונית (סיכון לשבירת מנתח) |
| היקף קטלוג | מוגבל לפי תעריף | ללא הגבלה |
הבחירה תלויה בתקציב ובהיקף שלכם. עבור קטלוגים מעל 50,000 מוצרים, מנתח בדרך כלל משתלם יותר.
למה לא כדאי לאחסן מחירי מתחרים בבלוקי מידע
בלוקי המידע של Bitrix אינם מיועדים לעדכונים המוניים תכופים. עם 10,000 מוצרים ו-5 מתחרים, מקבלים 50,000 רשומות שנכתבות מחדש כל שעה. ה-ORM של בלוקי המידע יוצר אירועים מיותרים (bl_price_competitors), מרוקן מטמון, ומאט את לוח הניהול. הפתרון הוא להשתמש ב-בלוקים ברמה גבוהה (HL-blocks) או בטבלאות מסד נתונים נפרדות. אנחנו מעדיפים טבלאות—הן מהירות יותר, ללא תקורה של אירועים או גרסאות.
ארכיטקטורת אחסון נתונים
ללא קשר למקור הנתונים, מבנה האחסון זהה. אנו יוצרים שתי טבלאות עיקריות: מתחרים CREATE TABLE bl_price_competitors ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, domain VARCHAR(255) UNIQUE, active BOOLEAN DEFAULT true, logo_url VARCHAR(512) ); :
CREATE TABLE bl_price_competitors ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, domain VARCHAR(255) UNIQUE, active BOOLEAN DEFAULT true, logo_url VARCHAR(512) ); מחירי מתחרים bl_competitor_prices:
CREATE TABLE bl_competitor_prices ( id SERIAL PRIMARY KEY, product_id INT NOT NULL, -- b_iblock_element.ID competitor_id INT REFERENCES bl_price_competitors(id), price NUMERIC(12,2) NOT NULL, url VARCHAR(512), -- URL страницы конкурента in_stock BOOLEAN DEFAULT true, checked_at TIMESTAMP NOT NULL DEFAULT NOW(), UNIQUE (product_id, competitor_id) -- одна актуальная цена ); CREATE INDEX idx_comp_prices_product ON bl_competitor_prices(product_id, checked_at DESC); היסטוריה CREATE TABLE bl_competitor_prices ( id SERIAL PRIMARY KEY, product_id INT NOT NULL, -- b_iblock_element.ID competitor_id INT REFERENCES bl_price_competitors(id), price NUMERIC(12,2) NOT NULL, url VARCHAR(512), -- URL страницы конкурента in_stock BOOLEAN DEFAULT true, checked_at TIMESTAMP NOT NULL DEFAULT NOW(), UNIQUE (product_id, competitor_id) -- одна актуальная цена ); CREATE INDEX idx_comp_prices_product ON bl_competitor_prices(product_id, checked_at DESC); —טבלה המחולקת לפי חודשים לאחסון שינויים מבלי להכביד על הטבלה הראשית. כאשר המחיר הנוכחי מתעדכן, הערך הקודם עובר להיסטוריה.
אינטגרציית API דרך שירות ניטור
עם שירות מוכן, סוכן מבקש נתונים וכותב ל-bl_competitor_prices_history:
function SyncCompetitorPrices(): string { $client = new PriceMonitoringClient(MONITORING_API_KEY); $data = $client->getPrices(['date' => date('Y-m-d')]); foreach ($data['products'] as $item) { $productId = ProductMapper::findBySku($item['sku']); if (!$productId) continue; foreach ($item['competitors'] as $comp) { $competitorId = CompetitorTable::getOrCreateByDomain($comp['domain']); // Сохраняем в историю перед обновлением $current = CompetitorPriceTable::getByProductAndCompetitor($productId, $competitorId); if ($current && $current['PRICE'] != $comp['price']) { CompetitorPriceHistoryTable::add([ 'PRODUCT_ID' => $productId, 'COMPETITOR_ID' => $competitorId, 'PRICE' => $current['PRICE'], 'RECORDED_AT' => $current['CHECKED_AT'], ]); } CompetitorPriceTable::addOrUpdate([ 'PRODUCT_ID' => $productId, 'COMPETITOR_ID' => $competitorId, 'PRICE' => $comp['price'], 'URL' => $comp['url'], 'IN_STOCK' => $comp['in_stock'], 'CHECKED_AT' => new \Bitrix\Main\Type\DateTime(), ]); } } return __FUNCTION__ . '();'; } חישוב מיקום מחיר ואגרגטים
בכל עדכון, אנו מחשבים אגרגטים ומיקום ב-bl_competitor_prices:
-- Обновляется триггером или агентом после синхронизации INSERT INTO bl_product_price_position (product_id, our_price, min_comp, avg_comp, max_comp, rank, updated_at) SELECT cp.product_id, bcp.PRICE as our_price, MIN(cp.price) as min_comp, ROUND(AVG(cp.price), 2) as avg_comp, MAX(cp.price) as max_comp, (SELECT COUNT(*) + 1 FROM bl_competitor_prices cp2 WHERE cp2.product_id = cp.product_id AND cp2.price < bcp.PRICE) as rank, NOW() FROM bl_competitor_prices cp JOIN b_catalog_price bcp ON bcp.PRODUCT_ID = cp.product_id AND bcp.CATALOG_GROUP_ID = 1 GROUP BY cp.product_id, bcp.PRICE ON CONFLICT (product_id) DO UPDATE SET our_price = EXCLUDED.our_price, min_comp = EXCLUDED.min_comp, avg_comp = EXCLUDED.avg_comp, rank = EXCLUDED.rank, updated_at = NOW(); איך להגדיר התראות על שינויי מחירי מתחרים?
סוכן משווה מחירים חדשים עם קודמים ומודיע למנהלים על שינויים משמעותיים—לדוגמה, כאשר מתחרה הופך לזול מאיתנו:
foreach ($priceChanges as $change) { if ($change['new_price'] < $change['our_price'] && $change['old_price'] >= $change['our_price']) { $message = sprintf( 'Конкурент %s снизил цену на %s до %s руб. (наша: %s руб.)', $change['competitor_name'], $change['product_name'], number_format($change['new_price'], 2, ',', ' '), number_format($change['our_price'], 2, ',', ' ') ); \Bitrix\Main\Mail\Event::send([ 'EVENT_NAME' => 'COMPETITOR_PRICE_ALERT', 'LID' => SITE_ID, 'C_FIELDS' => ['MESSAGE' => $message], ]); } } טעויות נפוצות בהגדרת ניטור
- שימוש בבלוקי מידע לאחסון—מוביל לירידה בביצועים מעל 20,000 רשומות.
- חוסר באינדקסים על
function SyncCompetitorPrices(): string { $client = new PriceMonitoringClient(MONITORING_API_KEY); $data = $client->getPrices(['date' => date('Y-m-d')]); foreach ($data['products'] as $item) { $productId = ProductMapper::findBySku($item['sku']); if (!$productId) continue; foreach ($item['competitors'] as $comp) { $competitorId = CompetitorTable::getOrCreateByDomain($comp['domain']); // Сохраняем в историю перед обновлением $current = CompetitorPriceTable::getByProductAndCompetitor($productId, $competitorId); if ($current && $current['PRICE'] != $comp['price']) { CompetitorPriceHistoryTable::add([ 'PRODUCT_ID' => $productId, 'COMPETITOR_ID' => $competitorId, 'PRICE' => $current['PRICE'], 'RECORDED_AT' => $current['CHECKED_AT'], ]); } CompetitorPriceTable::addOrUpdate([ 'PRODUCT_ID' => $productId, 'COMPETITOR_ID' => $competitorId, 'PRICE' => $comp['price'], 'URL' => $comp['url'], 'IN_STOCK' => $comp['in_stock'], 'CHECKED_AT' => new \Bitrix\Main\Type\DateTime(), ]); } } return __FUNCTION__ . '();'; }ו-bl_product_price_position—סוכן הסנכרון רץ שעות במקום דקות. - מנתחים ללא טיפול בשגיאות—כאשר אתר מתחרה נופל, הסוכן זורק חריגה ונעצר עד להתערבות ידנית.
- עדכונים נדירים מדי—עם עדכון פעם ביום, מפספסים תנודות תוך-יומיות שיכולות להגיע ל-30%.
מה כלול בהגדרת השירות שלנו?
- ניתוח הקטלוג שלכם ובחירת מקור הנתונים האופטימלי (שירות או מנתח).
- יצירת טבלאות או בלוקים ברמה גבוהה עבור מתחרים, מחירים והיסטוריה.
- פיתוח סוכן סנכרון עם טיפול מפורט בשגיאות (רישום לוג, ניסיונות חוזרים).
- הטמעת מודול חישוב מיקום מחיר עם דירוג.
- הצגת מחירי מתחרים בכרטיס המוצר בלוח הניהול ובחנות המקוונת (אופציונלי).
- הגדרת התראות בדוא"ל או ב-Bitrix24.
- תיעוד ארכיטקטורה והוראות תחזוקה.
לוחות זמנים והערבויות שלנו
| שלב | משך |
|---|---|
| סכמת מסד נתונים ומאגרי קוד | יומיים |
| סוכן סנכרון עם מקור נתונים | יומיים |
| חישוב מיקום ואגרגטים | יום אחד |
| הצגה בכרטיס מוצר (ניהול) | יומיים |
| התראות על שינויים | יום אחד |
| בדיקות | יום אחד |
| סה"כ | 9–10 ימים |
התמחור נקבע באופן אישי בהתאם לגודל הקטלוג ומורכבות האינטגרציה. אנו מבטיחים פעילות יציבה של הסוכן למשך 6 חודשים לאחר המסירה. צרו קשר להערכת פרויקט חינמית, ונציע ארכיטקטורה המתאימה לתקציב שלכם. קבלו ייעוץ על ארכיטקטורת ניטור מחירים, ותראו שהאוטומציה מחזירה את עצמה בתוך החודשיים הראשונים.
עם ניסיון של למעלה מ-10 שנים בפיתוח Bitrix ו-Bitrix24, ויותר מ-50 אינטגרציות מוצלחות עם מערכות ניטור מחירים—המומחיות שלנו מתמודדת עם כל מורכבות.







