Настройка персонализированных рекомендаций на основе поведения 1С-Битрикс
Персонализированные рекомендации на основе поведения — это средний уровень между «похожими по категории» и полноценным ML. Не нужна отдельная Python-инфраструктура: всё строится на данных из базы Битрикса и нескольких SQL-запросах. Работает хуже нейросетей, но в разы лучше простых «похожих товаров» и требует только PHP + PostgreSQL.
Поведенческие сигналы и их веса
Разные действия пользователя имеют разную ценность как сигнал интереса. Типичная шкала:
| Событие | Вес |
|---|---|
| Покупка товара | 10 |
| Добавление в корзину | 5 |
| Добавление в избранное | 4 |
| Просмотр карточки (>30 сек) | 2 |
| Просмотр карточки (<30 сек) | 1 |
| Поисковый запрос с переходом | 3 |
Веса хранятся в конфигурации, события — в таблице b_user_behavior (структура описана в статье о персонализации контента). Ключевое поле: EVENT_TYPE с значениями purchase, cart_add, wishlist, view.
Item-based коллаборативная фильтрация без ML
Суть: «пользователи, которые смотрели товар A, также смотрели товары B, C, D». Это считается напрямую из базы без ML-модели:
SELECT
b2.ENTITY_ID AS recommended_id,
COUNT(DISTINCT b2.USER_ID) AS co_view_count
FROM b_user_behavior b1
JOIN b_user_behavior b2
ON b1.USER_ID = b2.USER_ID
AND b1.ENTITY_ID != b2.ENTITY_ID
AND b2.EVENT_TYPE IN ('view', 'cart_add', 'purchase')
AND b2.DATE_CREATE > NOW() - INTERVAL '60 days'
WHERE
b1.ENTITY_ID = :current_item_id
AND b1.EVENT_TYPE IN ('view', 'cart_add', 'purchase')
GROUP BY b2.ENTITY_ID
ORDER BY co_view_count DESC
LIMIT 20;
Этот запрос выполняется офлайн (раз в час через агент) и результат кешируется в отдельной таблице:
CREATE TABLE b_item_recommendations (
ITEM_ID INT NOT NULL,
RECOMMENDED_ID INT NOT NULL,
SCORE FLOAT NOT NULL,
UPDATED_AT TIMESTAMP DEFAULT NOW(),
PRIMARY KEY (ITEM_ID, RECOMMENDED_ID)
);
CREATE INDEX idx_item_recs_item ON b_item_recommendations(ITEM_ID, SCORE DESC);
Персональный рейтинг для конкретного пользователя
Из 20 кандидатов-рекомендаций нужно выбрать 6–8 наиболее релевантных для текущего пользователя. Используем его поведенческий профиль:
function getPersonalizedRecs(int $itemId, int $userId, int $limit = 8): array {
// 1. Получить кандидатов из item-based таблицы
$candidates = getCandidates($itemId, 20);
if (empty($candidates) || !$userId) {
return array_slice($candidates, 0, $limit);
}
// 2. Получить категории из истории пользователя
$userCategoryIds = getUserTopCategories($userId, 10);
// 3. Boosting: поднять товары из предпочитаемых категорий
foreach ($candidates as &$candidate) {
$sectionId = getElementSectionId($candidate['id']);
if (in_array($sectionId, $userCategoryIds)) {
$candidate['score'] *= 1.5;
}
}
// 4. Убрать уже купленные товары
$purchased = getUserPurchasedIds($userId);
$candidates = array_filter($candidates,
fn($c) => !in_array($c['id'], $purchased)
);
usort($candidates, fn($a, $b) => $b['score'] <=> $a['score']);
return array_column(array_slice($candidates, 0, $limit), 'id');
}
Кеширование и отображение
Кастомный компонент local:catalog.recommendations принимает ELEMENT_ID и выводит рекомендованные товары. Кеш строится на уровне item_id — не персональный, а item-based (одинаковые кандидаты для всех). Персональный буст применяется через отдельный AJAX-вызов после загрузки основного блока.
Такой подход позволяет кешировать основной блок рекомендаций на уровне Битрикса и при этом показывать каждому пользователю персонализированный порядок без лишних запросов к базе при рендеринге страницы.
Перенос истории при авторизации
Битрикс не переносит поведенческую историю анонима на авторизованного пользователя автоматически. Обработчик OnAfterUserLogin:
AddEventHandler('main', 'OnAfterUserLogin', function($fields) {
$fuserId = \CSaleUser::GetAnonymousUserID();
if (!$fuserId) return;
$DB->Query("
UPDATE b_user_behavior SET USER_ID = " . (int)$fields['USER_ID'] . "
WHERE SESSION_ID = '" . $DB->ForSql(session_id()) . "'
AND USER_ID IS NULL
");
});







