← Назад к вопросам

Как строить воронку конверсии и какие метрики на каждом этапе важны?

1.3 Junior🔥 251 комментариев
#Метрики и KPI

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Построение воронки конверсии: архитектура и метрики

Воронка конверсии — основной инструмент для понимания пути пользователя и выявления узких мест. Покажу как я её строю и анализирую.

1. Архитектура воронки

Классический путь e-commerce:

Пользователь посетил сайт
        ↓ (Landing Page View)
    100% (100k)
        ↓
Посмотрел каталог товаров
        ↓ (Category View)
    75% (75k) ← потеря 25%
        ↓
Добавил в корзину
        ↓ (Add to Cart)
    30% (30k) ← потеря 45% (!)
        ↓
Начал оформлять заказ
        ↓ (Checkout Started)
    25% (25k) ← потеря 5%
        ↓
Добавил способ оплаты
        ↓ (Payment Added)
    20% (20k) ← потеря 5% (!
        ↓
Завершил покупку
        ↓ (Order Completed)
    18% (18k) ← потеря 2% ← узкое место!

2. SQL для построения воронки

-- Метод 1: Event-based (рекомендуемый)
-- Считаю количество пользователей на каждом этапе

WITH funnel_events AS (
  SELECT 
    user_id,
    session_id,
    event_type,
    created_at,
    -- Определяю последовательность
    ROW_NUMBER() OVER (
      PARTITION BY user_id, session_id 
      ORDER BY created_at
    ) as event_sequence,
    -- Флаги для каждого события
    CASE WHEN event_type = 'page_view' THEN 1 ELSE 0 END as saw_page,
    CASE WHEN event_type = 'category_view' THEN 1 ELSE 0 END as viewed_category,
    CASE WHEN event_type = 'add_to_cart' THEN 1 ELSE 0 END as added_to_cart,
    CASE WHEN event_type = 'checkout_started' THEN 1 ELSE 0 END as started_checkout,
    CASE WHEN event_type = 'payment_added' THEN 1 ELSE 0 END as added_payment,
    CASE WHEN event_type = 'order_completed' THEN 1 ELSE 0 END as completed_order
  FROM events
  WHERE created_at >= DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY)
),
-- Определяю этап каждого пользователя (максимальный достигнутый)
user_stages AS (
  SELECT 
    user_id,
    session_id,
    CASE
      WHEN MAX(completed_order) = 1 THEN 'Order Completed'
      WHEN MAX(added_payment) = 1 THEN 'Payment Added'
      WHEN MAX(started_checkout) = 1 THEN 'Checkout Started'
      WHEN MAX(added_to_cart) = 1 THEN 'Cart'
      WHEN MAX(viewed_category) = 1 THEN 'Category'
      WHEN MAX(saw_page) = 1 THEN 'Landing Page'
      ELSE 'Unknown'
    END as final_stage,
    MAX(completed_order) = 1 as converted
  FROM funnel_events
  GROUP BY user_id, session_id
)
-- Финальный отчёт
SELECT 
  final_stage,
  COUNT(DISTINCT user_id) as unique_users,
  COUNT(*) as total_sessions,
  ROUND(100.0 * COUNT(*) / SUM(COUNT(*)) OVER (), 2) as percentage_of_total,
  SUM(CASE WHEN converted THEN 1 ELSE 0 END) as conversions,
  ROUND(100.0 * SUM(CASE WHEN converted THEN 1 ELSE 0 END) / COUNT(*), 2) as conversion_rate
FROM user_stages
GROUP BY final_stage
ORDER BY COUNT(*) DESC;

Результат:

final_stage        | unique_users | percentage | conversions | conversion_rate
Landing Page       | 100,000      | 100%       | 18,000      | 18%
Category          | 75,000       | 75%        | 18,000      | 24%
Cart              | 30,000       | 30%        | 18,000      | 60%
Checkout Started  | 25,000       | 25%        | 18,000      | 72%
Payment Added     | 20,000       | 20%        | 18,000      | 90%
Order Completed   | 18,000       | 18%        | 18,000      | 100%

3. Python для анализа воронки

import pandas as pd
import numpy as np
from scipy.stats import chi2_contingency

# Загрузил данные из SQL
df = pd.read_csv('funnel_data.csv')

class FunnelAnalysis:
    """Анализ воронки конверсии"""
    
    def __init__(self, df, stages):
        self.df = df
        self.stages = stages  # ['Landing', 'Category', 'Cart', 'Checkout', 'Payment', 'Order']
        self.stage_counts = {}
    
    def calculate_funnel(self):
        """Считаю количество пользователей на каждом этапе"""
        for stage in self.stages:
            count = len(self.df[self.df['final_stage'] == stage])
            self.stage_counts[stage] = count
        return self.stage_counts
    
    def calculate_dropoff(self):
        """На каком этапе теряю больше всего?"""
        stages_list = list(self.stage_counts.values())
        dropoffs = {}
        
        for i in range(len(stages_list) - 1):
            current_stage = self.stages[i]
            next_stage = self.stages[i + 1]
            
            dropout_rate = 1 - (stages_list[i + 1] / stages_list[i])
            dropoffs[f"{current_stage}{next_stage}"] = {
                'dropout_rate': dropout_rate,
                'lost_users': stages_list[i] - stages_list[i + 1]
            }
        
        # Отсортирую по количеству потерянных пользователей
        return sorted(dropoffs.items(), 
                     key=lambda x: x[1]['lost_users'], 
                     reverse=True)
    
    def compare_segments(self, segment_col):
        """Сравниваю воронку по сегментам (мобила vs десктоп)"""
        segments = self.df[segment_col].unique()
        
        for segment in segments:
            segment_df = self.df[self.df[segment_col] == segment]
            total = len(segment_df)
            converted = len(segment_df[segment_df['converted'] == True])
            
            conv_rate = converted / total if total > 0 else 0
            print(f"{segment}: {conv_rate:.1%} ({converted}/{total})")
    
    def identify_bottleneck(self):
        """Находит самое узкое место"""
        dropoffs = self.calculate_dropoff()
        if dropoffs:
            bottleneck, stats = dropoffs[0]
            print(f"УЗКОЕ МЕСТО: {bottleneck}")
            print(f"Теряю: {stats['lost_users']:,} пользователей")
            print(f"Dropout rate: {stats['dropout_rate']:.1%}")
            return bottleneck
        return None

# Использую
funnel = FunnelAnalysis(df, ['Landing', 'Category', 'Cart', 'Checkout', 'Payment', 'Order'])
funnel.calculate_funnel()
print("\nСамое узкое место:")
funnel.identify_bottleneck()

print("\nДропауты по этапам:")
for stage, data in funnel.calculate_dropoff():
    print(f"{stage}: {data['dropout_rate']:.1%}")

print("\nСравнение по устройствам:")
funnel.compare_segments('device_type')

4. Критические метрики на каждом этапе

Уровень 1: Landing Page → Category

# Метрика: Bounce Rate (отскок)
bounce_users = df[df['final_stage'] == 'Landing Page']
bounce_rate = len(bounce_users) / len(df[df['final_stage'].notna()]) * 100
print(f"Bounce Rate: {bounce_rate:.1f}%")

# Действие: Если > 50%, проблема с:
# - Page speed (проверить lighthouse)
# - Relevance (проверить трафик source)
# - UX (A/B test landing page)

Уровень 2: Category → Cart

# Метрика: Browse Depth (сколько товаров посмотрели)
products_viewed = df.groupby('user_id')['product_view_count'].max()

print(f"Среднее товаров просмотрено: {products_viewed.mean():.1f}")
print(f"Медиана: {products_viewed.median():.1f}")

# Действие: Если среднее < 3, то
# - Плохая фильтрация категории
# - Товары не привлекательны (фото, описание)
# - Нужно A/B тест рекомендаций

Уровень 3: Cart → Checkout (ГЛАВНОЕ УЗКОЕ МЕСТО)

# Метрика 1: Add-to-Cart Rate
added_to_cart = len(df[df['added_to_cart'] == True])
added_to_cart_rate = added_to_cart / len(df) * 100
print(f"Add-to-Cart Rate: {added_to_cart_rate:.1f}%")

# Метрика 2: Cart Abandonment Rate
cart_abandonment = len(df[(df['added_to_cart'] == True) & 
                          (df['started_checkout'] == False)]) / added_to_cart * 100
print(f"Cart Abandonment: {cart_abandonment:.1f}%")

# Если > 70%, главные виновники (по моему опыту):
# 1. Скрытые затраты (доставка, налоги показаны только в checkout)
# 2. Нет интеграции с popular payment методами
# 3. Обязательна регистрация (нужен guest checkout)
# 4. Mobile оптимизация плохая

# Решение: A/B test
# Контроль: текущий процесс
# Вариант A: Явно показать все затраты на странице товара
# Вариант B: Добавить "Guest Checkout"

from scipy.stats import chi2_contingency

control_data = np.array([
    [1000, 700],  # added to cart, completed checkout
])
treatment_data = np.array([
    [1100, 880],  # added to cart, completed checkout (вариант B)
])

total = np.vstack([control_data, treatment_data])
chi2, p_val, dof, expected = chi2_contingency(total)

if p_val < 0.05:
    improvement = (880/1100 - 700/1000) / (700/1000)
    print(f"ВАРИАНТ B ЛУЧШЕ НА {improvement:.1%}")

Уровень 4: Checkout → Payment

# Метрика: Form Abandonment (бросают в процессе заполнения)
started_checkout = len(df[df['started_checkout'] == True])
finished_checkout = len(df[df['payment_method'] != None])

form_abandonment = (started_checkout - finished_checkout) / started_checkout * 100
print(f"Form Abandonment: {form_abandonment:.1f}%")

# Обычно причины:
# - Много полей для заполнения
# - Валидация не понятна
# - Security concerns (не видят SSL badge)
# - Mobile: keyboard скрывает форму

Уровень 5: Payment → Order (последняя линия защиты)

# Метрика: Payment Success Rate
payments_attempted = len(df[df['payment_added'] == True])
payments_successful = len(df[df['order_completed'] == True])

payment_success_rate = payments_successful / payments_attempted * 100
print(f"Payment Success Rate: {payment_success_rate:.1f}%")

# Если < 90%, анализирую отказы по причинам:
error_reasons = df[df['payment_failed'] == True].groupby('error_code').size()
print("\nОтказы по причинам:")
print(error_reasons.sort_values(ascending=False))

# Top причины:
# 1. 3D Secure (банк требует подтверждение) — 30%
# 2. Expired card — 15%
# 3. Insufficient funds — 20%
# 4. Fraud detection — 10%
# 5. Gateway timeout — 5%

# Решение: Retry logic, улучшить UX для 3DS

5. Визуализация воронки

import matplotlib.pyplot as plt
import plotly.graph_objects as go

# Интерактивная воронка (Plotly)
stages = ['Landing Page', 'Category', 'Cart', 'Checkout', 'Payment', 'Order']
values = [100000, 75000, 30000, 25000, 20000, 18000]
colors = ['#1f77b4', '#1f77b4', '#ff7f0e', '#ff7f0e', '#d62728', '#2ca02c']

fig = go.Figure(go.Funnel(
    y=stages,
    x=values,
    textposition='inside',
    textinfo='value+percent previous',
    marker=dict(color=colors, line=dict(width=2))
))

fig.update_layout(
    title="Conversion Funnel (Last 30 Days)",
    height=600
)
fig.show()

6. Когда мониторю воронку

Ежедневно:

  • General conversion rate (тренд)
  • Top dropout stage
  • Revenue per session

Еженедельно:

  • Segment comparison (мобила vs десктоп, новые vs постоянные)
  • Device-specific issues
  • Traffic source quality

Ежемесячно:

  • YoY тренды
  • A/B тест результаты
  • Cohort analysis (когда начали пользователи → когда конвертировались)

7. Вывод: Действие план

Когда вижу drop в воронке:

  1. Количественный анализ

    • Сколько теряю пользователей?
    • В какой сегмент наибольший drop?
    • Статистически значим ли drop?
  2. Качественный анализ

    • Спрашиваю пользователей (опрос, интервью)
    • Session recording (Hotjar, LogRocket)
    • Проверяю логи ошибок
  3. Гипотеза

    • Формулирую одну причину (не несколько!)
    • Пример: "Пользователи не видят кнопку 'Add to Cart' на мобиле"
  4. Решение + A/B тест

    • Не гадаю, тестирую
    • Минимум статистической мощности: 1000 пользователей на вариант
    • Продолжительность: пока не достигну 95% confidence
  5. Мониторинг

    • Не развёртываю побеждённый вариант и не забываю
    • Следю за метриками неделю после релиза
    • Проверяю не сломалось ли что-то другое