Настройка отслеживания цен конкурентов для 1С-Битрикс
Отслеживание цен конкурентов строится одним из двух способов: через готовый сервис мониторинга (Competera, Metacommerce, Priceva) или через собственный парсер. Готовый сервис — проще, надёжнее, но дорого при большом каталоге. Собственный парсер — гибко, дёшево в эксплуатации, но требует регулярного обслуживания при изменениях на сайтах конкурентов. Задача настройки в обоих случаях одинакова: собрать данные, сохранить в Битрикс, показать менеджеру в удобном виде.
Архитектура хранения данных
Независимо от источника данных (сервис или парсер), структура хранения одинакова:
Конкуренты bl_price_competitors:
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);
История bl_competitor_prices_history — партиционированная по месяцам таблица для хранения изменений без раздувания основной.
Интеграция через API сервиса мониторинга
При использовании готового сервиса агент запрашивает данные и записывает в bl_competitor_prices:
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:
-- Обновляется триггером или агентом после синхронизации
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],
]);
}
}
Сроки
| Этап | Срок |
|---|---|
| Схема БД и репозитории | 2 дня |
| Агент синхронизации с источником данных | 2 дня |
| Расчёт позиции и агрегатов | 1 день |
| Отображение в карточке товара (админка) | 2 дня |
| Оповещения при изменениях | 1 день |
| Тестирование | 1 день |
| Итого | 9–10 дней |







