Реализация персонализации контента по сегментам аудитории на сайте
Персонализация — показ разного контента разным сегментам пользователей для повышения релевантности и конверсии. В отличие от A/B теста (случайное распределение), персонализация детерминирована: пользователь с определёнными атрибутами всегда видит соответствующий контент.
Типы сегментов
- Поведенческие: просмотренные товары, категории, предыдущие покупки
- Демографические: гео, язык, устройство
- Источник трафика: Google Ads, SEO, email-рассылка, прямой
- Стадия воронки: новый посетитель, вернувшийся, зарегистрированный, платящий
- Бизнес-атрибуты: тарифный план, компания, роль
Архитектура персонализации
User Request
↓
Segment Resolver (кто этот пользователь?)
↓
Rule Engine (какой контент показать?)
↓
Content Renderer (отрендерить персонализированный вариант)
↓
Analytics (зафиксировать показ + конверсию)
Сегментация на сервере
# segment_resolver.py
class SegmentResolver:
def resolve(self, user: User, request: Request) -> list[str]:
segments = []
# Геолокация
country = get_geoip(request.remote_addr)
segments.append(f"country:{country}")
# Устройство
device = parse_device(request.user_agent)
segments.append(f"device:{device}")
# Источник трафика
referrer = request.referrer or ''
if 'google' in referrer:
segments.append("source:google")
elif 'email' in request.args.get('utm_medium', ''):
segments.append("source:email")
else:
segments.append("source:direct")
# Стадия жизненного цикла
if not user:
segments.append("lifecycle:anonymous")
elif not user.has_purchases:
segments.append("lifecycle:prospect")
if user.session_count > 3:
segments.append("lifecycle:warm_lead")
else:
segments.append("lifecycle:customer")
segments.append(f"plan:{user.plan}")
# Поведенческие (из Redis)
viewed_cats = redis.smembers(f"viewed_cats:{user.id}")
for cat in viewed_cats:
segments.append(f"interest:{cat}")
return segments
Rule Engine для маппинга сегментов на контент
# personalization_rules.py
RULES = [
{
'id': 'email_promo_banner',
'segments': ['source:email'],
'content': {
'hero_banner': 'Ваш эксклюзивный промокод: EMAIL20',
'cta_text': 'Применить скидку 20%'
},
'priority': 100
},
{
'id': 'warm_lead_urgency',
'segments': ['lifecycle:warm_lead'],
'content': {
'hero_banner': 'Вы смотрели {last_viewed_product} — осталось 3 штуки',
'floating_badge': 'Ваша корзина ждёт'
},
'priority': 90
},
{
'id': 'customer_cross_sell',
'segments': ['lifecycle:customer'],
'content': {
'sidebar': 'recommended_for_customers',
'hero_banner': 'Добро пожаловать обратно! Новинки для вас:'
},
'priority': 80
},
{
'id': 'mobile_simplified',
'segments': ['device:mobile'],
'content': {
'layout': 'mobile_first',
'show_phone_cta': True
},
'priority': 50
}
]
def get_personalized_content(segments: list[str]) -> dict:
matched = []
for rule in sorted(RULES, key=lambda r: r['priority'], reverse=True):
if all(s in segments for s in rule['segments']):
matched.append(rule)
break # Применить только первое подходящее правило
if not matched:
return get_default_content()
return matched[0]['content']
Frontend реализация с персонализированными слотами
// PersonalizationSlot.jsx
function PersonalizationSlot({ slotId, fallback }) {
const [content, setContent] = useState(null)
const { segments } = useUserSegments()
useEffect(() => {
fetch('/api/personalization', {
method: 'POST',
body: JSON.stringify({ slot: slotId, segments })
})
.then(r => r.json())
.then(setContent)
}, [slotId, segments])
if (!content) return fallback || null
return <div dangerouslySetInnerHTML={{ __html: content.html }} />
}
// Использование
function HeroSection() {
return (
<section>
<PersonalizationSlot
slotId="hero_banner"
fallback={<DefaultHeroBanner />}
/>
</section>
)
}
Edge персонализация (без задержки)
// Cloudflare Worker: персонализировать HTML на Edge
addEventListener('fetch', event => {
event.respondWith(personalizeResponse(event.request))
})
async function personalizeResponse(request) {
const response = await fetch(request)
if (!response.headers.get('Content-Type')?.includes('text/html')) {
return response
}
const segments = getSegmentsFromCookies(request)
const content = getPersonalizedContent(segments)
const html = await response.text()
const personalized = html
.replace('{{hero_headline}}', content.hero_headline)
.replace('{{cta_text}}', content.cta_text)
return new Response(personalized, {
headers: response.headers,
status: response.status
})
}
Рекомендательный движок
# Collaborative filtering для товарных рекомендаций
from sklearn.metrics.pairwise import cosine_similarity
import numpy as np
def get_recommendations(user_id, all_purchases, n=5):
# Матрица пользователь × товар
users = list(all_purchases.keys())
items = list(set(item for purchases in all_purchases.values() for item in purchases))
matrix = np.zeros((len(users), len(items)))
for i, user in enumerate(users):
for item in all_purchases.get(user, []):
j = items.index(item)
matrix[i][j] = 1
# Схожесть пользователей
user_idx = users.index(user_id)
similarities = cosine_similarity(matrix[user_idx:user_idx+1], matrix)[0]
# Взвешенные рекомендации
scores = similarities @ matrix
scores[0][list(all_purchases.get(user_id, {}))] = 0 # убрать уже купленные
top_indices = scores[0].argsort()[-n:][::-1]
return [items[i] for i in top_indices]
Измерение эффекта персонализации
def measure_personalization_impact(analytics_db):
# Сравнить конверсию: персонализированный контент vs дефолтный
results = analytics_db.query("""
SELECT
is_personalized,
COUNT(DISTINCT session_id) AS sessions,
SUM(converted) AS conversions,
ROUND(AVG(CAST(converted AS FLOAT)) * 100, 2) AS cvr,
ROUND(AVG(order_value), 0) AS avg_order_value
FROM sessions
WHERE date >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY is_personalized
""")
return results
Срок выполнения
Реализация системы персонализации с сегментацией, Rule Engine и измерением эффекта — 5–10 рабочих дней.







