Перейти к основному содержимому

История изменений конкурента — reverse-engineering стратегии

Как отследить каждое изменение в карточке конкурента, сопоставить с динамикой продаж и reverse-engineer'ить работающую стратегию. Вместо гадания — точная реконструкция: что именно сделал конкурент, когда, и какой эффект это дало.

Проблема

Конкурент за месяц удвоил продажи. Весь рынок задаёт один вопрос: «Что он сделал?» А ответа — нет.

Без системного отслеживания изменений в карточках конкурентов селлер оказывается в информационном вакууме:

  • «Наверное, снизил цену» — на самом деле цена не изменилась. Конкурент добавил видеообзор и вырос на 44%
  • «Видимо, выкупы» — а на самом деле он переписал заголовок под высокочастотные запросы и получил +18% к органике
  • «У него просто бюджет больше» — нет. Он добавил 5 инфографик, и конверсия в корзину выросла на 29%
  • «Сезон помог» — но у всех в категории сезон, а вырос только он. Значит, дело не в сезоне

Без истории версий карточки конкурента можно только гадать. С историей — можно точно реконструировать каждый шаг и повторить лучше.

Методология отслеживания версий

MPStats хранит исторические снапшоты карточек товаров — состояние на разные даты. Сопоставляя снапшоты с данными о продажах (item/{sku}/sales), можно построить точную хронологию: какое изменение в карточке привело к какому результату. Это не теория — это forensic-анализ: каждый факт подтверждён данными.

Ключевой принцип: не все изменения дают эффект. Задача — найти те, которые коррелируют с ростом продаж, и отделить шум от сигнала.

Пайплайн данных

Архитектура мониторинга конкурента

flowchart LR
A["Мониторинг\nконкурента\n(ежедневно)"] --> B["Обнаружен\nскачок продаж\n> +30% WoW"]
B --> C["Запрос\nистории версий\nкарточки"]
C --> D["Сравнение\nверсий:\nbefore vs after"]
D --> E["Извлечение\nстратегии:\nчто сработало"]
E --> F["Бриф клиенту:\nповторить лучше"]

Источники данных

ЭндпоинтКлючевые поляЧто показывает
History / Version snapshotsФото, заголовок, описание, видео, инфографики, ценаЧто менялось в карточке: каждый снапшот — состояние на дату
GET /wb/get/item/{sku}/salessales, revenue, price по днямКогда изменились продажи — корреляция с обновлением контента
GET /wb/get/item/{sku}/by_keywordskeyword, position, frequencyИзменения в SEO-позициях после обновления заголовка/описания

Пайплайн шаг за шагом

  1. Выбрать конкурентов — 3-5 ключевых игроков в ценовом диапазоне клиента
  2. Настроить мониторинг — ежедневная проверка продаж по item/{sku}/sales
  3. Обнаружить аномалию — скачок продаж >30% неделя-к-неделе (WoW)
  4. Загрузить историю версий — снапшоты карточки вокруг даты скачка
  5. Сравнить before/after — diff по каждому элементу карточки
  6. Корреляция — какое именно изменение совпало со скачком
  7. Генерация инсайта — конкретная рекомендация для клиента

Анализ

Шаг 1. Мониторинг продаж конкурента

import httpx
from datetime import datetime, timedelta
from dataclasses import dataclass

MPSTATS_TOKEN = "your_token"
BASE_URL = "https://mpstats.io/api"
HEADERS = {
"X-Mpstats-TOKEN": MPSTATS_TOKEN,
"Content-Type": "application/json",
}


def fetch_sales(sku: int, d1: str, d2: str) -> list[dict]:
"""Загружает ежедневные продажи конкурента."""
resp = httpx.get(
f"{BASE_URL}/wb/get/item/{sku}/sales",
headers=HEADERS,
params={"d1": d1, "d2": d2},
timeout=30,
)
resp.raise_for_status()
return resp.json()


def detect_sales_spike(
sales_data: list[dict],
threshold_pct: float = 30.0,
window_days: int = 7,
) -> list[dict]:
"""
Обнаруживает скачки продаж: сравнивает каждую неделю
с предыдущей. Если рост > threshold_pct — фиксируем спайк.
"""
spikes = []

for i in range(window_days, len(sales_data)):
current_week = sales_data[i - window_days + 1 : i + 1]
previous_week = sales_data[i - 2 * window_days + 1 : i - window_days + 1]

if not previous_week:
continue

current_avg = sum(d.get("sales", 0) for d in current_week) / len(current_week)
previous_avg = sum(d.get("sales", 0) for d in previous_week) / len(previous_week)

if previous_avg == 0:
continue

growth_pct = (current_avg - previous_avg) / previous_avg * 100

if growth_pct >= threshold_pct:
spike_date = current_week[-1].get("date", "unknown")
spikes.append({
"date": spike_date,
"previous_avg_sales": round(previous_avg, 1),
"current_avg_sales": round(current_avg, 1),
"growth_pct": round(growth_pct, 1),
})

return spikes


# --- Мониторинг ---
competitor_sku = 98765432
today = datetime.now()
d1 = (today - timedelta(days=60)).strftime("%Y-%m-%d")
d2 = today.strftime("%Y-%m-%d")

sales = fetch_sales(competitor_sku, d1, d2)
spikes = detect_sales_spike(sales, threshold_pct=30)

for spike in spikes:
print(f"СПАЙК обнаружен: {spike['date']}")
print(f" Продажи: {spike['previous_avg_sales']}{spike['current_avg_sales']}/день")
print(f" Рост: +{spike['growth_pct']}%")

Шаг 2. Сравнение версий карточки

После обнаружения спайка загружаем снапшоты карточки до и после даты скачка и сравниваем каждый элемент:

@dataclass
class CardVersion:
"""Снапшот состояния карточки товара на определённую дату."""
date: str
title: str
description: str
photo_count: int
photo_urls: list[str]
has_video: bool
video_url: str | None
infographic_count: int
price: float
keywords_in_title: list[str]


def compare_versions(before: CardVersion, after: CardVersion) -> list[dict]:
"""
Сравнивает два снапшота карточки.
Возвращает список обнаруженных изменений.
"""
changes = []

# 1. Количество фото
if after.photo_count != before.photo_count:
delta = after.photo_count - before.photo_count
changes.append({
"element": "photos",
"description": f"Фото: {before.photo_count}{after.photo_count} ({delta:+d})",
"before": before.photo_count,
"after": after.photo_count,
"impact_category": "visual",
})

# 2. Видео
if after.has_video != before.has_video:
action = "добавлено" if after.has_video else "удалено"
changes.append({
"element": "video",
"description": f"Видео {action}",
"before": before.has_video,
"after": after.has_video,
"impact_category": "visual",
})

# 3. Заголовок (SEO)
if after.title != before.title:
# Находим новые ключевые слова
before_words = set(before.title.lower().split())
after_words = set(after.title.lower().split())
new_words = after_words - before_words
removed_words = before_words - after_words

changes.append({
"element": "title",
"description": f"Заголовок изменён: +{len(new_words)} слов, -{len(removed_words)} слов",
"before": before.title,
"after": after.title,
"new_keywords": list(new_words),
"removed_keywords": list(removed_words),
"impact_category": "seo",
})

# 4. Описание
if after.description != before.description:
len_before = len(before.description)
len_after = len(after.description)
changes.append({
"element": "description",
"description": f"Описание: {len_before}{len_after} символов",
"before_length": len_before,
"after_length": len_after,
"impact_category": "seo",
})

# 5. Инфографики
if after.infographic_count != before.infographic_count:
delta = after.infographic_count - before.infographic_count
changes.append({
"element": "infographics",
"description": f"Инфографики: {before.infographic_count}{after.infographic_count} ({delta:+d})",
"before": before.infographic_count,
"after": after.infographic_count,
"impact_category": "visual",
})

# 6. Цена
if abs(after.price - before.price) > 1:
delta_pct = (after.price - before.price) / before.price * 100
changes.append({
"element": "price",
"description": f"Цена: {before.price:.0f}{after.price:.0f} руб ({delta_pct:+.1f}%)",
"before": before.price,
"after": after.price,
"delta_pct": round(delta_pct, 1),
"impact_category": "pricing",
})

return changes

Шаг 3. Корреляция изменений с продажами

Главная ценность — привязка конкретного изменения к конкретному результату:

def correlate_changes_with_sales(
changes: list[dict],
sales_before: list[dict],
sales_after: list[dict],
window_days: int = 14,
) -> list[dict]:
"""
Для каждого обнаруженного изменения рассчитывает
корреляцию с динамикой продаж.
"""
avg_before = sum(d.get("sales", 0) for d in sales_before[-window_days:]) / window_days
avg_after = sum(d.get("sales", 0) for d in sales_after[:window_days]) / window_days

sales_effect_pct = (
(avg_after - avg_before) / avg_before * 100
if avg_before > 0 else 0
)

enriched = []
for change in changes:
enriched.append({
**change,
"sales_before": round(avg_before, 1),
"sales_after": round(avg_after, 1),
"sales_effect_pct": round(sales_effect_pct, 1),
})

return enriched


def build_reverse_engineering_report(
competitor_sku: int,
enriched_changes: list[dict],
) -> list[dict]:
"""
Формирует хронологический отчёт reverse-engineering:
что изменилось → какой эффект → что делать клиенту.
"""
report = []

for change in enriched_changes:
recommendation = ""

if change["impact_category"] == "visual":
if change["element"] == "video":
recommendation = "Создать видеообзор лучшего качества (60 сек, с инфографикой)"
elif change["element"] == "photos":
recommendation = f"Добавить {change['after'] + 2} фото (больше, чем конкурент)"
elif change["element"] == "infographics":
recommendation = f"Создать {change['after'] + 1} инфографик (включая то, что упустил конкурент)"

elif change["impact_category"] == "seo":
if change.get("new_keywords"):
kw_str = ", ".join(change["new_keywords"][:5])
recommendation = f"Включить ключевые слова ({kw_str}) + добавить свои высокочастотные"

elif change["impact_category"] == "pricing":
recommendation = "Проанализировать ценовое позиционирование, не копировать цену"

report.append({
"sku": competitor_sku,
"change": change["description"],
"category": change["impact_category"],
"sales_before": change["sales_before"],
"sales_after": change["sales_after"],
"effect_pct": change["sales_effect_pct"],
"recommendation": recommendation,
})

# Сортируем по эффекту — самое результативное наверху
report.sort(key=lambda r: abs(r["effect_pct"]), reverse=True)
return report

Пример отчёта reverse-engineering

Результат анализа реального конкурента за 3 недели — хронология каждого изменения и его эффекта:

ДатаИзменениеКатегорияПродажи доПродажи послеЭффект
15 янвДобавил видеообзор (45 сек)Visual50/день72/день+44%
22 янвОбновил заголовок (SEO-оптимизация)SEO72/день85/день+18%
1 февНовые инфографики (5 шт)Visual85/день110/день+29%

Кумулятивный эффект за 3 недели: +120% к продажам.

Три конкретных изменения. Три измеримых результата. Никаких догадок.

Шаг 4. Проверка SEO-позиций после обновления

Дополнительная валидация — если конкурент обновил заголовок, проверяем, как изменились его позиции по ключевым словам:

def fetch_keywords(sku: int) -> list[dict]:
"""Загружает текущие позиции по ключевым словам."""
resp = httpx.get(
f"{BASE_URL}/wb/get/item/{sku}/by_keywords",
headers=HEADERS,
timeout=30,
)
if resp.status_code != 200:
return []
return resp.json()


def analyze_seo_impact(
keywords_before: list[dict],
keywords_after: list[dict],
) -> dict:
"""
Сравнивает SEO-позиции до и после изменения заголовка.
"""
before_map = {kw["keyword"]: kw.get("position", 999) for kw in keywords_before}
after_map = {kw["keyword"]: kw.get("position", 999) for kw in keywords_after}

# Новые ключевые слова (появились после обновления)
new_keywords = [
kw for kw in after_map
if kw not in before_map and after_map[kw] <= 50
]

# Улучшенные позиции
improved = []
for kw in before_map:
if kw in after_map and after_map[kw] < before_map[kw]:
improved.append({
"keyword": kw,
"before": before_map[kw],
"after": after_map[kw],
"improvement": before_map[kw] - after_map[kw],
})

improved.sort(key=lambda x: x["improvement"], reverse=True)

return {
"new_keywords_in_top50": new_keywords,
"improved_positions": improved[:20],
"total_new": len(new_keywords),
"total_improved": len(improved),
}

Действие Fotofactor

Стратегия «Повторить лучше» — фреймворк для контент-агентства

Суть не в копировании конкурента, а в превосходстве по каждому параметру:

Шаг 1. Определить, что сработало

  • Конкурент добавил видео → продажи +44%
  • Конкурент обновил заголовок → органика +18%
  • Конкурент добавил инфографики → конверсия +29%

Шаг 2. Сделать лучше, а не так же

  • Конкурент сделал видео 45 сек → мы делаем 60 сек + с инфографикой внутри
  • Конкурент оптимизировал 3 ключевых слова → мы оптимизируем 10
  • Конкурент добавил 5 инфографик → мы добавляем 7 + в другом стиле

Шаг 3. Добавить то, что конкурент НЕ сделал

  • Конкурент добавил видео, но не обновил фото → мы обновляем ВСЁ
  • Конкурент улучшил визуал, но не тронул SEO → мы делаем и то, и другое
  • Конкурент работал с контентом, но не с отзывами → мы добавляем вкладыши

Шаг 4. Скорость как оружие

  • Конкурент внедрял изменения постепенно за 3 недели
  • Мы внедряем ВСЕ улучшения одним пакетом за 1 неделю
  • Эффект: кумулятивный рост, а не постепенный
Не копируйте конкурентов слепо!

Reverse-engineering — это анализ стратегии, а не копирование контента:

  • Копировать стиль фото — Wildberries штрафует за слишком похожие карточки, вплоть до блокировки
  • Использовать чужие тексты — рискуете получить жалобу на плагиат и потерять карточку
  • Снижать цену под конкурента — это гонка на дно, а не стратегия роста
  • Копировать одно изменение — если конкурент вырос благодаря комбинации 3 факторов, копирование одного не даст результата

Правильный подход: извлечь принцип (видео повышает конверсию), а не копировать реализацию (переснять такое же видео).

Ежемесячный отчёт конкурентной разведки

На основе системного мониторинга Fotofactor предоставляет клиенту ежемесячный отчёт:

Раздел отчётаСодержимое
Обзор конкурентов (3-5 SKU)Что изменилось в карточках за месяц
Хронология измененийТаймлайн: дата → изменение → эффект на продажи
ТОП-3 инсайтаСамые результативные изменения конкурентов
Рекомендации «Повторить лучше»Конкретный план действий для клиента
АлертыЗначимые изменения, требующие немедленной реакции

Система алертов

Автоматическое оповещение при значимых изменениях конкурентов:

@dataclass
class CompetitorAlert:
"""Алерт об изменении у конкурента."""
competitor_sku: int
alert_type: str # "sales_spike", "content_update", "price_change"
severity: str # "high", "medium", "low"
description: str
recommendation: str
detected_at: str


def check_competitor_alerts(
competitor_skus: list[int],
sales_threshold: float = 30.0,
) -> list[CompetitorAlert]:
"""
Проверяет список конкурентов на значимые изменения.
Запускается ежедневно через cron.
"""
alerts = []
today = datetime.now()
d1 = (today - timedelta(days=21)).strftime("%Y-%m-%d")
d2 = today.strftime("%Y-%m-%d")

for sku in competitor_skus:
sales = fetch_sales(sku, d1, d2)
spikes = detect_sales_spike(sales, threshold_pct=sales_threshold)

for spike in spikes:
alerts.append(CompetitorAlert(
competitor_sku=sku,
alert_type="sales_spike",
severity="high" if spike["growth_pct"] > 50 else "medium",
description=(
f"SKU {sku}: продажи выросли на {spike['growth_pct']}% "
f"({spike['previous_avg_sales']}{spike['current_avg_sales']}/день)"
),
recommendation="Запустить reverse-engineering: загрузить историю версий карточки",
detected_at=spike["date"],
))

return alerts

План действий для клиента

После reverse-engineering Fotofactor предоставляет конкретный бриф:

ПриоритетДействиеОбоснованиеСтоимостьСрок
1Видеообзор 60 сек с инфографикойКонкурент: +44% от простого видео. Наше — лучше35 000 руб5 дней
2Обновить заголовок (10 ключевых слов)Конкурент: +18% от 3 ключей. Мы оптимизируем x315 000 руб2 дня
37 инфографик (стиль + размерная сетка)Конкурент: +29% от 5 инфографик. Мы — 7, другой стиль45 000 руб7 дней
4Обновить все фото (12 ракурсов)Конкурент не менял фото — наше преимущество60 000 руб7 дней
ИтогоПолный рестайлинг по инсайтамОжидаемый эффект: +150% (vs +120% конкурент)155 000 руб2 недели

Результат для клиента

Data-driven вместо гадания

  • Точное знание вместо гипотез: что именно сделал конкурент и какой эффект получил
  • Приоритизация: какие изменения дают максимальный ROI (видео +44% > инфографики +29% > заголовок +18%)
  • Скорость реакции: узнать об изменениях конкурента за дни, а не за месяцы
  • Обоснование бюджета: «Конкурент вложил X и получил +120%. Мы сделаем лучше за Y»

Экономика для Fotofactor

УслугаСтоимостьПериодичность
Конкурентная разведка (подписка)20 000 - 30 000 руб/месЕжемесячно
Контент по инсайтам (реализация)80 000 - 150 000 рубПо результатам каждого отчёта
Экспресс-анализ (разовый)15 000 рубПо запросу

Аргумент, который продаёт сам себя

Самый мощный sales-аргумент в арсенале контент-агентства:

«Ваш конкурент обновил карточку 15 января — добавил видео и 5 инфографик. За три недели его продажи выросли на 120%. Вот хронология каждого изменения и его эффект. Мы сделаем лучше: больше фото, лучше видео, плюс SEO-оптимизация, которую он пропустил. Когда начинаем?»

Клиент не может спорить с данными. Он видит, что конкурент уже действует. Вопрос не «нужно ли обновлять карточку?», а «почему мы ещё не начали?».

Unit-экономика

Вход:  3-5 SKU конкурентов + ежедневный мониторинг

Анализ: Обнаружение спайков + reverse-engineering (2-4 часа/мес)

Отчёт: Ежемесячный конкурентный отчёт + алерты

Подписка: 20 000 - 30 000 руб/мес (конкурентная разведка)
+ 80 000 - 150 000 руб (контент по инсайтам)

LTV: 600 000 - 1 200 000 руб/год на клиента

Полный скрипт

Минимальный рабочий пайплайн: от мониторинга конкурента до генерации отчёта reverse-engineering.

import httpx
import json
from datetime import datetime, timedelta
from dataclasses import dataclass, asdict

TOKEN = "YOUR_MPSTATS_TOKEN"
BASE_URL = "https://mpstats.io/api"
HEADERS = {
"X-Mpstats-TOKEN": TOKEN,
"Content-Type": "application/json",
}

# --- Конфигурация ---
COMPETITOR_SKUS = [98765432, 87654321, 76543210] # Конкуренты клиента
SPIKE_THRESHOLD = 30.0 # % роста WoW для срабатывания алерта


def fetch_sales(sku: int, d1: str, d2: str) -> list[dict]:
"""Загружает ежедневные продажи по SKU."""
resp = httpx.get(
f"{BASE_URL}/wb/get/item/{sku}/sales",
headers=HEADERS,
params={"d1": d1, "d2": d2},
timeout=30,
)
resp.raise_for_status()
return resp.json()


def detect_spikes(sales_data: list[dict], threshold: float = 30.0) -> list[dict]:
"""Находит скачки продаж WoW > threshold%."""
spikes = []
window = 7

for i in range(window, len(sales_data)):
curr = sales_data[i - window + 1 : i + 1]
prev = sales_data[i - 2 * window + 1 : i - window + 1]

if not prev:
continue

avg_curr = sum(d.get("sales", 0) for d in curr) / len(curr)
avg_prev = sum(d.get("sales", 0) for d in prev) / len(prev)

if avg_prev == 0:
continue

growth = (avg_curr - avg_prev) / avg_prev * 100
if growth >= threshold:
spikes.append({
"date": curr[-1].get("date", "unknown"),
"sales_before": round(avg_prev, 1),
"sales_after": round(avg_curr, 1),
"growth_pct": round(growth, 1),
})

return spikes


def fetch_keywords(sku: int) -> list[dict]:
"""Загружает ключевые слова для SKU."""
resp = httpx.get(
f"{BASE_URL}/wb/get/item/{sku}/by_keywords",
headers=HEADERS,
timeout=30,
)
return resp.json() if resp.status_code == 200 else []


def generate_report(
sku: int,
spikes: list[dict],
seo_data: list[dict],
) -> str:
"""Генерирует текстовый отчёт reverse-engineering."""
lines = [
f"# REVERSE-ENGINEERING REPORT",
f"# Competitor SKU: {sku}",
f"# Generated: {datetime.now().strftime('%Y-%m-%d %H:%M')}",
"",
]

if spikes:
lines.append("## ОБНАРУЖЕННЫЕ СКАЧКИ ПРОДАЖ")
lines.append("")
for spike in spikes:
lines.append(
f" {spike['date']}: "
f"{spike['sales_before']}{spike['sales_after']}/день "
f"(+{spike['growth_pct']}%)"
)
lines.append("")

if seo_data:
top_keywords = [kw for kw in seo_data if kw.get("position", 999) <= 20]
lines.append(f"## SEO PROFILE: {len(seo_data)} keywords, {len(top_keywords)} in TOP-20")
lines.append("")

lines.append("## РЕКОМЕНДАЦИИ")
lines.append("")
lines.append(" 1. Загрузить историю версий карточки вокруг дат спайков")
lines.append(" 2. Сравнить before/after по каждому элементу")
lines.append(" 3. Определить, какое изменение коррелирует с ростом")
lines.append(" 4. Подготовить бриф «Повторить лучше» для клиента")

return "\n".join(lines)


# --- Основной пайплайн ---
if __name__ == "__main__":
today = datetime.now()
d1 = (today - timedelta(days=60)).strftime("%Y-%m-%d")
d2 = today.strftime("%Y-%m-%d")

all_results = []

for sku in COMPETITOR_SKUS:
print(f"\nАнализ конкурента SKU {sku}...")

# 1. Загрузка продаж
sales = fetch_sales(sku, d1, d2)
print(f" Загружено {len(sales)} дней продаж")

# 2. Обнаружение скачков
spikes = detect_spikes(sales, SPIKE_THRESHOLD)
print(f" Обнаружено скачков: {len(spikes)}")

# 3. SEO-профиль
keywords = fetch_keywords(sku)
print(f" Ключевых слов: {len(keywords)}")

# 4. Генерация отчёта
report = generate_report(sku, spikes, keywords)
print(report)

all_results.append({
"sku": sku,
"spikes": spikes,
"keywords_count": len(keywords),
"report": report,
})

# 5. Сохранение
filename = f"competitor_intel_{datetime.now():%Y%m%d}.json"
with open(filename, "w", encoding="utf-8") as f:
json.dump(all_results, f, ensure_ascii=False, indent=2)
print(f"\nОтчёт сохранён: {filename}")

Что дальше