Промо-готовность — подготовка к распродажам WB
Как заранее подготовить контент клиента к крупным акциям Wildberries и получить в 2-5 раз больше продаж, пока конкуренты надеются только на скидки.
Проблема
Wildberries проводит крупные распродажи 4-6 раз в год: Чёрная пятница, 11.11, Новый год, 8 марта, День России, Школьный базар. В эти периоды трафик на площадке взлетает в 3-10 раз — миллионы покупателей одновременно ищут товары. Это золотая жила для тех, кто готов.
Проблема: подавляющее большинство селлеров не готовят контент к промо. Их стратегия на распродажу — одна кнопка «снизить цену»:
- Фото те же — зимние варежки сфотографированы на белом фоне, без праздничного контекста, без подарочной упаковки. Покупатель ищет «подарок на Новый год», а видит безликую карточку
- Инфографика не адаптирована — нет акцента на скидку, нет сравнения «было/стало», нет urgency-элементов («только 3 дня!»)
- Заголовки без промо-ключевиков — селлер не добавил в title слова «подарок», «набор», «скидка», «акция», по которым покупатели активно ищут именно в дни распродаж
- Видео отсутствует — в период пиковой конкуренции карточка без видео проигрывает тем, у кого оно есть (видео повышает время просмотра, а это сигнал алгоритму WB)
- Запасы не рассчитаны — товар заканчивается на второй день промо, карточка вылетает из поиска, а восстановление позиций занимает 2-4 недели
Результат: селлер ставит скидку 30%, теряет маржу, но не получает пропорционального роста продаж. А соседняя карточка с подготовленным контентом и скидкой 15% продаёт в 3 раза больше — потому что покупатель выбирает не самую дешёвую, а самую убедительную карточку.
Во время распродажи все снижают цены. Скидка перестаёт быть конкурентным преимуществом — это входной билет. Выигрывает тот, кто совмещает разумную скидку с контентом, заточенным под промо: праздничные фото, акционная инфографика, промо-ключевики в заголовке. Алгоритм WB тоже реагирует на подготовку: карточки с обновлённым контентом получают буст в ранжировании, потому что свежий контент = сигнал актуальности.
Пайплайн данных
Таймлайн подготовки к промо
gantt
title Подготовка к распродаже — 6 недель
dateFormat YYYY-MM-DD
axisFormat %d.%m
section Анализ (T-6 недель)
Тренды прошлых промо :analysis1, 2026-10-12, 7d
Аудит контента конкурентов :analysis2, 2026-10-12, 7d
section Контент (T-4 недели)
Промо-фотосъёмка :content1, 2026-10-19, 10d
Промо-инфографика :content2, 2026-10-26, 7d
SEO под промо-запросы :content3, 2026-10-26, 5d
Видео для промо :content4, 2026-10-26, 10d
section Загрузка (T-2 недели)
Загрузка контента на WB :upload1, 2026-11-09, 3d
Переиндексация WB :upload2, 2026-11-12, 10d
section Промо
Чёрная пятница :crit, promo, 2026-11-22, 5d
section Замер
Сбор метрик после промо :measure, 2026-11-27, 7d
Источники данных MPStats
| Эндпоинт | Метод | Ключевые поля | Зачем |
|---|---|---|---|
/wb/get/category/trends | GET | 72 точки помесячной выручки за 6 лет | Выявить пики прошлых промо: ноябрь, декабрь, март — исторические данные показывают точный масштаб подъёма |
/wb/get/category | POST | picscount, hasvideo, revenue, sales, final_price, basic_sale, start_price | Текущее состояние контента конкурентов — кто уже готовится к промо? |
Ценовые поля из category | POST | final_price, start_price, basic_sale | Динамика цен: как конкуренты выстраивают ценовую стратегию перед распродажей |
Wildberries проводит крупные промо с предсказуемой регулярностью. Основные даты для планирования контента:
| Промо-событие | Даты | Пиковый трафик | Подготовка контента |
|---|---|---|---|
| День влюблённых | 10-14 февраля | x2-3 | До 1 января |
| 23 февраля | 18-23 февраля | x2-3 | До 10 января |
| 8 марта | 1-8 марта | x3-5 | До 20 января |
| День России | 10-12 июня | x1.5-2 | До 1 мая |
| Школьный базар | 15 авг - 5 сен | x2-4 | До 1 июля |
| 11.11 | 9-13 ноября | x3-5 | До 1 октября |
| Чёрная пятница | 22-28 ноября | x5-10 | До 15 октября |
| Новый год | 1-25 декабря | x5-10 | До 1 ноября |
Ключевое правило: контент должен быть загружен минимум за 2 недели до начала промо, чтобы WB успел переиндексировать карточку.
Общая схема пайплайна
flowchart LR
A["GET /wb/get/category/trends\n72 месяца данных"] --> B["Идентификация промо-пиков\nnoябрь, декабрь, март"]
B --> C["Расчёт promo_uplift\nпик / среднее"]
C --> D["POST /wb/get/category\nТОП-100 в нише"]
D --> E["Аудит: сколько конкурентов\nуже обновили контент?"]
E --> F["Promo Readiness Score\n0-100 баллов"]
F --> G["Персональный план\nподготовки клиента"]
Анализ
Шаг 1: Выявление промо-пиков из исторических данных
import httpx
import statistics
MPSTATS_TOKEN = "YOUR_TOKEN"
BASE_URL = "https://mpstats.io/api"
HEADERS = {
"X-Mpstats-TOKEN": MPSTATS_TOKEN,
"Content-Type": "application/json",
}
# Промо-месяцы WB (индексы 0-11, где 0 = январь)
WB_PROMO_MONTHS = {
1: "23 февраля", # февраль
2: "8 марта", # март
10: "11.11 / Чёрная пятница", # ноябрь
11: "Новый год", # декабрь
}
def fetch_category_trends(category_path: str) -> list[float]:
"""Загрузить 72-месячный тренд категории."""
resp = httpx.get(
f"{BASE_URL}/wb/get/category/trends",
headers=HEADERS,
params={"path": category_path},
timeout=30,
)
resp.raise_for_status()
return resp.json()
def identify_promo_peaks(trends_72: list[float]) -> list[dict]:
"""
Находит промо-пики в историческом тренде.
Алгоритм:
1. Разбить 72 точки на 6 лет по 12 месяцев
2. Для каждого месяца рассчитать медиану по годам
3. Рассчитать promo_uplift = median_month / avg_all_months
4. Отметить месяцы с uplift > 1.3 как промо-пики
"""
# Разбиваем на годы
years_data = []
for year_idx in range(6):
year = trends_72[year_idx * 12 : (year_idx + 1) * 12]
if sum(year) > 0:
years_data.append(year)
if not years_data:
return []
# Медиана для каждого месяца
monthly_medians = []
for month_idx in range(12):
values = [y[month_idx] for y in years_data if y[month_idx] > 0]
median_val = statistics.median(values) if values else 0
monthly_medians.append(median_val)
# Среднемесячная выручка
avg_monthly = statistics.mean(monthly_medians) if monthly_medians else 1
# Рассчитываем uplift
peaks = []
for month_idx, median_val in enumerate(monthly_medians):
uplift = round(median_val / avg_monthly, 2) if avg_monthly > 0 else 1.0
promo_name = WB_PROMO_MONTHS.get(month_idx)
is_promo_month = uplift > 1.3 or promo_name is not None
peaks.append({
"month_idx": month_idx,
"month_name": [
"Январь", "Февраль", "Март", "Апре ль", "Май", "Июнь",
"Июль", "Август", "Сентябрь", "Октябрь", "Ноябрь", "Декабрь",
][month_idx],
"median_revenue": round(median_val),
"uplift": uplift,
"is_promo": is_promo_month,
"promo_event": promo_name,
})
return peaks
# --- Пример ---
trends = fetch_category_trends("Одежда/Женская одежда/Платья")
peaks = identify_promo_peaks(trends)
print("=== ПРОМО-ПИКИ КАТЕГОРИИ ===")
for p in peaks:
marker = " ** ПРОМО **" if p["is_promo"] else ""
bar = "█" * int(p["uplift"] * 10)
event = f" ({p['promo_event']})" if p["promo_event"] else ""
print(f"{p['month_name']:>10} | x{p['uplift']:.2f} | {bar}{event}{marker}")
Шаг 2: Аудит промо-готовности конкурентов
from datetime import datetime, timedelta
def fetch_category_top(category_path: str, d1: str, d2: str) -> list[dict]:
"""Загрузить ТОП-100 товаров в категории."""
resp = httpx.post(
f"{BASE_URL}/wb/get/category",
headers=HEADERS,
json={
"path": category_path,
"d1": d1,
"d2": d2,
"startRow": 0,
"endRow": 100,
},
timeout=30,
)
resp.raise_for_status()
return resp.json().get("data", [])
def audit_competitor_readiness(products: list[dict]) -> dict:
"""
Анализирует, сколько конкурентов из ТОП-100 уже готовятся к промо.
Признаки подготовки:
- Обновлённые фото (picscount > среднего)
- Наличие видео (hasvideo = 1)
- Изменение цен (basic_sale > 0 = заготовлена скидка)
- Высокий рейтинг + свежие отзывы (готовность к трафику)
"""
total = len(products)
if total == 0:
return {"total": 0, "prepared": 0, "prepared_pct": 0}
avg_pics = sum(p.get("picscount", 0) for p in products) / total
prepared = 0
readiness_signals = []
for product in products:
signals = 0
# Много фото (выше среднего + 3) — обновлённый контент
if product.get("picscount", 0) > avg_pics + 3:
signals += 1
# Есть видео
if product.get("hasvideo", 0) == 1:
signals += 1
# Заготовлена скидка (basic_sale > 0)
if product.get("basic_sale", 0) > 0:
signals += 1
# Высокий рейтинг (>= 4.5)
if product.get("rating", 0) >= 4.5:
signals += 1
# 3+ сигналов = конкурент готовится
if signals >= 3:
prepared += 1
readiness_signals.append({
"sku": product.get("id"),
"name": product.get("name", "")[:60],
"revenue": product.get("revenue", 0),
"signals": signals,
})
return {
"total": total,
"prepared": prepared,
"prepared_pct": round(prepared / total * 100),
"avg_pics_in_top": round(avg_pics, 1),
"top_prepared": sorted(
readiness_signals,
key=lambda x: x["revenue"],
reverse=True,
)[:5],
}
Шаг 3: Promo Readiness Score клиента
def calculate_promo_readiness(
client_product: dict,
competitor_audit: dict,
promo_uplift: float,
weeks_before_promo: int,
) -> dict:
"""
Рассчитывает Promo Readiness Score (0-100) для карточки клиента.
Компоненты:
- Свежесть контента (30%) — когда последний раз обновлялись фото
- Сезонная релевантность (25%) — контент адаптирован под промо?
- SEO для промо-ключевиков (20%) — есть ли "подарок", "скидка", "набор"
- Готовность стока (15%) — хватит ли товара на пик
- Ценовая конкурентоспособность (10%) — адекватная ли цена/скидка
"""
scores = {}
# 1. Свежесть контента (30%)
pics_count = client_product.get("picscount", 0)
has_video = client_product.get("hasvideo", 0)
avg_pics = competitor_audit.get("avg_pics_in_top", 8)
content_score = min(100, (pics_count / max(avg_pics, 1)) * 60)
if has_video:
content_score += 40
content_score = min(100, content_score)
scores["content_freshness"] = round(content_score)
# 2. Сезонная релевантность (25%)
# Оценка: есть ли промо-элементы в контенте
# В реальности: анализ через CV или ручной чек-лист
# Здесь: упрощённая оценка по наличию видео + кол-ву фото
seasonal_score = 0
if pics_count >= 10:
seasonal_score += 40 # Много фото = вероятно, есть сезонные
if has_video:
seasonal_score += 30
if client_product.get("has3d", 0):
seasonal_score += 30
scores["seasonal_relevance"] = min(100, seasonal_score)
# 3. SEO для промо-ключевиков (20%)
# Проверяем наличие промо-слов в названии
name = (client_product.get("name", "") or "").lower()
promo_keywords = ["подарок", "скидка", "набор", "акция", "sale", "промо", "праздн"]
keyword_hits = sum(1 for kw in promo_keywords if kw in name)
seo_score = min(100, keyword_hits * 35)
scores["promo_seo"] = seo_score
# 4. Готовность стока (15%)
# Оценка по остаткам: хватит ли на 5-7 дней пикового спроса
daily_sales = client_product.get("sales", 0) / 30 # средние продажи/день
peak_daily = daily_sales * promo_uplift # ожидаемые продажи в промо
# Для упрощения: если прогноз > 0, ставим 60 (нужна проверка стоков)
stock_score = 60 if peak_daily > 0 else 20
scores["stock_readiness"] = stock_score
# 5. Ценовая конкурентоспособность (10%)
basic_sale = client_product.get("basic_sale", 0)
if basic_sale >= 30:
price_score = 100
elif basic_sale >= 20:
price_score = 75
elif basic_sale >= 10:
price_score = 50
else:
price_score = 25
scores["price_competitiveness"] = price_score
# Итоговый Promo Readiness Score
weights = {
"content_freshness": 0.30,
"seasonal_relevance": 0.25,
"promo_seo": 0.20,
"stock_readiness": 0.15,
"price_competitiveness": 0.10,
}
total_score = sum(scores[k] * weights[k] for k in weights)
# Оценка
if total_score >= 80:
verdict = "READY"
emoji = "🟢"
action = "Карточка готова к промо. Мониторьте позиции."
elif total_score >= 60:
verdict = "PARTIALLY"
emoji = "🟡"
action = "Нужна доработка контента. Есть время — действуйте."
elif total_score >= 40:
verdict = "WEAK"
emoji = "🟠"
action = "Серьёзные пробелы. Срочно обновлять контент."
else:
verdict = "NOT_READY"
emoji = "🔴"
action = "Карточка не готова к п ромо. Нужна полная переработка."
return {
"total_score": round(total_score),
"verdict": verdict,
"emoji": emoji,
"action": action,
"breakdown": scores,
"weeks_until_promo": weeks_before_promo,
}
Пример промо-аудита
Категория: Аксессуары / Сумки / Женские сумки, подготовка к Чёрной пятнице.
| Компонент | Вес | Клиент | Балл | Комментарий |
|---|---|---|---|---|
| Свежесть контента | 30% | 5 фото, нет видео | 35/100 | Конкуренты: в среднем 10 фото + видео |
| Сезонная релевантность | 25% | Нет промо-элементов | 0/100 | Нет праздничных фото, нет бейджей |
| SEO для промо | 20% | Нет промо-слов в title | 0/100 | Нужно добавить «подарок», «набор» |
| Готовность стока | 15% | 150 шт на складе | 60/100 | При x5 трафике хватит на 3 дня |
| Ценовая конкурентоспособность | 10% | Скидка 15% | 50/100 | Конкуренты ставят 20-30% |
| ИТОГО | 100% | 25/100 | 🔴 НЕ ГОТОВ |
WB требуется 10-14 дней на полную переиндексацию карточки после обновления контента. Если загрузить новые фото за 3 дня до Чёрной пятницы — алгоритм не успеет их учесть, и карточка войдёт в промо с прежними позициями.
Жёсткий дедлайн для Чёрной пятницы 2026 (22 ноября):
- Фотосъёмка: до 25 октября
- Загрузка контента: до 8 ноября
- Последний день изменений: 10 ноября
Каждый пропущенный день после дедлайна = потерянные продажи в самый прибыльный период года.
Дерево решений
graph TD
A["Промо через 6 недель\nАудит Promo Readiness"] --> B{"Promo Readiness\nScore?"}
B -->|"80-100 🟢 READY"| C["Мониторинг позиций\nТонкая настройка цен"]
B -->|"60-79 🟡 ЧАСТИЧНО"| D["Обновить SEO\nДобавить промо-бейджи\n2-3 недели"]
B -->|"40-59 🟠 СЛАБО"| E["Промо-пакет:\nФото + инфографика\n+ SEO + видео\n4-5 недель"]
B -->|"0-39 🔴 НЕ ГОТОВ"| F["Полная пересъёмка\n+ промо-стратегия\n6 недель"]
C --> G["Промо: максимум\nпродаж"]
D --> G
E --> G
F --> G
Действие Fotofactor
Услуга «Промо-пакет»
На основе Promo Readiness Score Fotofactor предлагает клиенту пакет экстренной подготовки к распродаже:
Таймлайн: 4-6 недель до события.
Состав промо-пакета:
-
Сезонные фото-оверлеи — накладки на существующие фото с промо-элементами:
- Подарочные коробки и банты (Новый год, 8 марта)
- Бейдж «SALE» / «-30%» / «Хит продаж» (Чёрная пятница)
- Сезонный фон: снежинки, весенние цветы, осенние листья
- Стоимость: 15 000-25 000 руб (быстро и дёшево, если основные фото хорошие)
-
Промо-инфографика — слайды, заточенные под распродажу:
- Сравнение «Обычная цена / Цена на Чёрную пятницу»
- «В подарочном наборе дешевле на 20%»
- Таймер urgency: «Только 5 дней по этой цене»
- Стоимость: 20 000-35 000 руб за комплект
-
SEO-обновление под промо-запросы — добавление ключевых слов:
- «подарок на [праздник]», «набор со скидкой», «акция [категория]»
- Обновление title, description, характеристик
- Стоимость: 5 000-10 000 руб за карточку
-
Промо-видео — короткий ролик (15-30 сек) для карточки:
- Распаковка в праздничном контексте
- Демонстрация подарочной упаковки
- Before/after или lifestyle-использование
- Стоимость: 15 000-30 000 руб
Чёрная пятница / 11.11:
- Инфографика с перечёркнутой старой ценой и крупной новой
- Бейдж «BLACK FRIDAY» на главном фото
- Таблица сравнения: «Этот товар vs аналоги — почему мы дешевле»
Новый год / Рождество:
- Товар в подарочной упаковке с бантом
- Lifestyle-фото: товар под ёлкой, в руках улыбающегося человека
- Инфографика «Идеальный подарок для [мамы/папы/подруги]»
8 марта:
- Цветочные элементы на фото, мягкие пастельные оттенки
- Бейдж «Подарок для неё»
- Набор: основной товар + комплементарный аксессуар
Школьный базар (сентябрь):
- Контекст «обратно в школу/офис»
- Инфографика с чек-листом: «Собери набор к школе»
- Фото в интерьере де тской или рабочего стола
Тарификация промо-пакетов
| Пакет | Состав | Стоимость | Срок | Для кого |
|---|---|---|---|---|
| Экспресс | Оверлеи + SEO | 25 000-35 000 руб | 1 неделя | Promo Score 60-79 (нужна доработка) |
| Стандарт | Фото + инфографика + SEO | 50 000-70 000 руб | 2-3 недели | Promo Score 40-59 (серьёзные пробелы) |
| Полный | Пересъёмка + инфографика + SEO + видео | 80 000-120 000 руб | 4-6 недель | Promo Score < 40 (не готов) |
| VIP | Полный + стратегия ценообразования + мониторинг | 150 000-200 000 руб | 4-6 недель | Топ-клиенты с 50+ SKU |
Годовой промо-календарь для клиента
Fotofactor составляет клиенту персональный промо-план на год — когда и что обновлять:
gantt
title Промо-календарь клиента на 2026 год
dateFormat YYYY-MM-DD
axisFormat %b
section Подготовка контента
К 23 февраля :prep1, 2026-01-10, 2026-02-01
К 8 марта :prep2, 2026-01-20, 2026-02-15
К летнему сезону :prep3, 2026-04-15, 2026-05-15
К школьному базару :prep4, 2026-07-01, 2026-08-01
К 11.11 :prep5, 2026-09-20, 2026-10-25
К Чёрной пятнице :prep6, 2026-10-01, 2026-11-08
К Новому году :prep7, 2026-10-15, 2026-11-15
section Промо-события
23 февраля :milestone, m1, 2026-02-23, 0d
8 марта :milestone, m2, 2026-03-08, 0d
Летний сезон :peak1, 2026-06-01, 2026-07-31
Школьный базар :peak2, 2026-08-15, 2026-09-05
11.11 :milestone, m3, 2026-11-11, 0d
Чёрная пятница :crit, bf, 2026-11-22, 5d
Новогодний пик :crit, ny, 2026-12-01, 2026-12-31
section Замеры результатов
Результат 8 марта :mon1, 2026-03-15, 7d
Результат лета :mon2, 2026-08-01, 7d
Результат 11.11 :mon3, 2026-11-18, 7d
Результат ЧП + НГ :mon4, 2027-01-05, 10d
Предпродажная коммуникация
Шаблон outreach за 6 недель до промо:
Добрый день! Через 6 недель — Чёрная пятница, главная распродажа года на WB.
📊 Мы проанализировали вашу категорию:
- В прошлом ноябре трафик вырос в 5.2 раза
- Из ТОП-100 конкурентов 34% уже обновили контент
- Ваш Promo Readiness Score: 25/100 (не готов к промо)
⚠️ Проблемы карточки:
- 5 фото (у конкурентов в среднем 10)
- Нет видео (63% ТОП-10 уже с видео)
- Нет промо-ключевиков в заголовке
- Инфографика без акционных элементов
💡 Промо-пакет «Стандарт» за 4 недели:
— Промо-фотосъёмка (10 фото с праздничным контекстом)
— Акционная инфографика (5 слайдов: скидка, сравнение, набор)
— SEO-обновление под промо-запр осы
— Стоимость: 60 000 руб
📅 Дедлайн загрузки контента: 8 ноября (за 2 недели до промо).
Начать нужно сейчас.
Отправить персональный промо-аудит с разбивкой по каждой карточке?
Результат для клиента
Эффект подготовленного контента в промо
| Метрика | Без подготовки (только скидка) | С промо-контентом | Разница |
|---|---|---|---|
| Рост продаж в промо | x1.5-2 | x3-5 | В 2-3 раза больше |
| Конверсия карточки | 3-5% (обычная) | 8-15% (промо-контент) | +100-200% |
| Позиция в промо | Падает (конкуренция) | Растёт (свежий контент = буст) | +20-50 позиций |
| Средний чек | Ниже (глубокая скидка) | Выше (меньшая скидка + ценность контента) | +15-25% маржи |
| Остаточный эффект | Возврат к базе через 3 дня | Рост сохраняется 2-4 недели после промо | Долгосрочный эффект |
Экономика для клиента
Пример: селлер сумок, 10 SKU, средняя цена 5 000 руб, обычные продажи 50 шт/день.
| Сценарий | Продажи в промо (7 дней) | Выручка | Затраты на контент | Чистый доп. доход |
|---|---|---|---|---|
| Без подготовки (скидка -25%) | 100 шт/день × 7 = 700 шт | 2 625 000 руб | 0 руб | — |
| С промо-пакетом (скидка -15%) | 200 шт/день × 7 = 1400 шт | 5 950 000 руб | 60 000 руб | +3 265 000 руб |
ROI промо-пакета: 5 342% — каждый вложенный рубль в промо-контент приносит 54 рубля дополнительной выручки.
Экономика для Fotofactor
| Показатель | Значение |
|---|---|
| Средний чек промо-пакета | 50 000-100 000 руб |
| Промо-событий в году | 4-6 |
| Выручка с одного клиента | 200 000-600 000 руб/год (только промо) |
| Маржинальность промо-пакета | 60-70% (шаблоны переиспользуются) |
| Конверсия в годовую подписку | 40%+ (клиент видит результат → подписывается на сезонный план) |
Промо-пакеты — это продукт с встроенным дедлайном. Клиенту не нужно объяснять «зачем» — промо через 4 недели, контент нужен сейчас. Это снимает главное возражение: «подумаю и вернусь». Думать некогда — Чёрная пятница не ждёт.
Шаблон urgency-сообщения:
«До Чёрной пятницы 28 дней. Последний день приёма заказов на промо-контент — через 7 дней. После этого мы физически не успеем сделать и загрузить контент до дедлайна WB. Места ограничены — студия может взять 8 клиентов в этот период.»
Этот формат даёт Fotofactor 4-6 волн продаж в год, привязанных к конкретным датам. Не нужно убеждать — нужно напомнить о дедлайне.
Полный скрипт
Минимальный рабочий пайплайн: от анализа трендов до расчёта Promo Readiness Score.
import httpx
import json
import statistics
from datetime import datetime, timedelta
TOKEN = "YOUR_MPSTATS_TOKEN"
BASE_URL = "https://mpstats.io/api"
HEADERS = {
"X-Mpstats-TOKEN": TOKEN,
"Content-Type": "application/json",
}
def fetch_trends(category_path: str) -> list[float]:
"""72-месячные тренды категории."""
resp = httpx.get(
f"{BASE_URL}/wb/get/category/trends",
headers=HEADERS,
params={"path": category_path},
timeout=30,
)
resp.raise_for_status()
return resp.json()
def fetch_top_products(category_path: str, d1: str, d2: str) -> list[dict]:
"""ТОП-100 товаров в категории."""
resp = httpx.post(
f"{BASE_URL}/wb/get/category",
headers=HEADERS,
json={
"path": category_path,
"d1": d1,
"d2": d2,
"startRow": 0,
"endRow": 100,
},
timeout=30,
)
resp.raise_for_status()
return resp.json().get("data", [])
def analyze_promo_peaks(trends_72: list[float]) -> list[dict]:
"""Находит промо-пики в историческом тренде."""
months = [
"Январь", "Февраль", "Март", "Апрель", "Май", "Июнь",
"Июль", "Август", "Сентябрь", "Октябрь", "Ноябрь", "Декабрь",
]
years = [
trends_72[y * 12 : (y + 1) * 12]
for y in range(6)
if sum(trends_72[y * 12 : (y + 1) * 12]) > 0
]
if not years:
return []
medians = []
for m in range(12):
vals = [y[m] for y in years if y[m] > 0]
medians.append(statistics.median(vals) if vals else 0)
avg = statistics.mean(medians) or 1
return [
{
"month": months[i],
"uplift": round(medians[i] / avg, 2),
"is_peak": medians[i] / avg > 1.3,
}
for i in range(12)
]
def calculate_readiness(product: dict, avg_pics: float) -> dict:
"""Рассчитывает Promo Readiness Score для одной карточки."""
# Свежесть контента (30%)
pics = product.get("picscount", 0)
video = product.get("hasvideo", 0)
content = min(100, (pics / max(avg_pics, 1)) * 60 + (40 if video else 0))
# Сезонная релевантность (25%)
seasonal = min(100, (40 if pics >= 10 else 0) + (30 if video else 0)
+ (30 if product.get("has3d", 0) else 0))
# SEO (20%)
name = (product.get("name", "") or "").lower()
promo_kw = ["подарок", "скидка", "набор", "акция", "sale", "промо"]
seo = min(100, sum(1 for kw in promo_kw if kw in name) * 35)
# Сток (15%)
stock = 60 if product.get("sales", 0) > 0 else 20
# Цена (10%)
sale = product.get("basic_sale", 0)
price = 100 if sale >= 30 else 75 if sale >= 20 else 50 if sale >= 10 else 25
total = (content * 0.30 + seasonal * 0.25 + seo * 0.20
+ stock * 0.15 + price * 0.10)
if total >= 80:
verdict, emoji = "READY", "🟢"
elif total >= 60:
verdict, emoji = "PARTIALLY", "🟡"
elif total >= 40:
verdict, emoji = "WEAK", "🟠"
else:
verdict, emoji = "NOT_READY", "🔴"
return {
"sku": product.get("id"),
"name": (product.get("name", "") or "")[:50],
"score": round(total),
"verdict": verdict,
"emoji": emoji,
"breakdown": {
"content_freshness": round(content),
"seasonal_relevance": round(seasonal),
"promo_seo": round(seo),
"stock_readiness": stock,
"price_competitiveness": price,
},
}
# --- Основной пайплайн ---
if __name__ == "__main__":
CATEGORY = "Аксессуары/Сумки/Женские сумки"
CLIENT_SKU = 12345678
today = datetime.now()
d1 = (today - timedelta(days=30)).strftime("%Y-%m-%d")
d2 = today.strftime("%Y-%m-%d")
# 1. Анализ промо-пиков
print("=== ПРОМО-ПИКИ КАТЕГОРИИ ===")
trends = fetch_trends(CATEGORY)
peaks = analyze_promo_peaks(trends)
for p in peaks:
bar = "█" * int(p["uplift"] * 10)
marker = " ** ПРОМО **" if p["is_peak"] else ""
print(f"{p['month']:>10} | x{p['uplift']:.2f} | {bar}{marker}")
# 2. Аудит конкурентов
print("\n=== АУДИТ КОНКУРЕНТОВ ===")
products = fetch_top_products(CATEGORY, d1, d2)
avg_pics = sum(p.get("picscount", 0) for p in products) / len(products) if products else 8
prepared = sum(
1 for p in products
if (p.get("picscount", 0) > avg_pics + 3)
and p.get("hasvideo", 0) == 1
)
print(f"ТОП-100: {prepared}% уже готовятся к промо")
print(f"Среднее кол-во фото в ТОПе: {avg_pics:.1f}")
# 3. Promo Readiness Score клиента
print("\n=== PROMO READINESS SCORE ===")
client = next((p for p in products if p.get("id") == CLIENT_SKU), None)
if client:
readiness = calculate_readiness(client, avg_pics)
print(f"SKU: {readiness['sku']}")
print(f"Score: {readiness['emoji']} {readiness['score']}/100 ({readiness['verdict']})")
print(f"Breakdown:")
for k, v in readiness["breakdown"].items():
print(f" {k}: {v}/100")
else:
print(f"SKU {CLIENT_SKU} не найден в ТОП-100. Используйте GET /wb/get/item для прямого запроса.")
# 4. Сохранение
output = {
"category": CATEGORY,
"client_sku": CLIENT_SKU,
"promo_peaks": peaks,
"competitor_audit": {
"total": len(products),
"prepared_pct": prepared,
"avg_pics": round(avg_pics, 1),
},
"client_readiness": readiness if client else None,
"generated_at": datetime.now().isoformat(),
}
filename = f"promo_readiness_{CLIENT_SKU}_{today:%Y%m%d}.json"
with open(filename, "w", encoding="utf-8") as f:
json.dump(output, f, ensure_ascii=False, indent=2)
print(f"\nСохранено: {filename}")
Что дальше
- Сезонный контент-календарь -- годовой план обновлений, в который встраиваются промо-пакеты
- До/После трекер -- измерение ROI промо-контента в 5 контрольных точках
- Конкурентный рентген -- глубокое сравнение с конкурентами перед промо
- Фото-формула ТОПа -- базовые требования к фото, которые усиливаются промо-элементами
- Поисковый магнит -- SEO-стратегия, адаптированная под промо-запросы
- Примеры API-запросов -- готовые сниппеты для работы с MPStats API