AI Asist
v2
Д
Д

СПП на WB — где брать и как считаем (варианты 1 и 3)

← Заметки  ·  Раздел: Wildberries  ·  Файл: data/notes/wb/spp-daily.md

СПП на WB — где брать и как считаем (варианты 1 и 3)

СПП = Скидка Постоянного Покупателя — скидка, которую WB даёт конечному покупателю за свой счёт (не за счёт продавца). Продавцу платят от ЦЕНЫ ПРОДАВЦА, а СПП — расход WB.

Цена покупателя = Цена продавца − (скидка продавца) − СПП − (скидка WB-кошелёк, если есть)
Выплата продавцу = f(Цена продавца, комиссия, логистика) — СПП на выплату НЕ влияет

СПП разный у каждого артикула и даже у каждой продажи (зависит от покупателя/времени); на Ozon — ещё и по размерам. Поэтому считаем СПП всегда по nm_id / размеру / дню, не «одним числом».


Вариант 1 — РЕАЛИЗОВАННЫЙ СПП из финотчёта (постфактум)

Источник: /api/v5/supplier/reportDetailByPeriodfin_report_detail. Маппинг полей (raw_json → колонка БД → графа кабинета):

raw_json колонка графа WB
retailPrice retail_price «Цена розничная» = цена продавца
spp spp (добавлена) СПП, %
retailAmount retail_amount «Вайлдберриз реализовал Товар (Пр)» = что заплатил покупатель

Проверка: retail_amount ≈ retail_price × (1 − spp/100).

Что сделано: - В модель FinReportDetail (app/models.py) добавлена колонка spp. - В нормализатор _normalize_finreport_row (app/wb/endpoints.py) добавлен "spp": _to_float(item.get("spp")) — будущие пуллы финотчёта заполняют поле автоматически. - Разовый бэкфилл по всей таблице: UPDATE fin_report_detail SET spp = json_extract(raw_json,'$.spp') (≈108k строк, в т.ч. 4970 строк «Продажа»). - Отчёт: scripts/wb_spp_report.py — СПП по артикулам/размерам за период (min/avg/max, цена продавца, цена покупателя). - python scripts/wb_spp_report.py --shop 5 --sku skirt-atlas-max-2 --by-size --from 2026-06-01 --to 2026-06-15

Ограничение: это СПП только по проданным товарам и по закрытым неделям финотчёта (с лагом, лимит finance-api 2 req/24ч). Для «СПП по дням на лету по всем SKU» → вариант 3.


Вариант 3 — ЕЖЕДНЕВНЫЙ снимок цен/СПП, БЕЗ финотчёта

Файл: scripts/ingest_wb_spp_daily.py (самодостаточный, своя таблица wb_price_daily). Зачем: снимать СПП каждый день по всем SKU, чтобы строить корреляцию «заказы ↔ СПП по дням».

Два источника на каждый размер:

  1. Цена продавца + скидкиdiscounts-prices-api/api/v2/list/goods/filter (авторизованный, токен магазина): - price — цена до скидки продавца - discountedPrice — после скидки ПРОДАВЦА (⚠ СПП тут ещё НЕ применён) - discount / clubDiscount — % скидки продавца и WB Клуба
  2. Цена покупателя (со СПП)ВИТРИНА wildberries.ru через реальный браузер (Playwright + stealth, persistent-профиль _wbprofile). Со страницы товара берём из DOM: - buyer_price — обычная цена покупателя (цена продавца − СПП), БЕЗ кошелькаins[class*="priceBlockFinalPrice"] - buyer_wallet_price — та же − скидка WB-кошелька → [class*="priceBlockWalletPrice"] - СПП% = (discountedPrice − buyer_price) / discountedPrice × 100 (база — цена БЕЗ кошелька).

Таблица wb_price_daily, PK (snapshot_date, shop_id, nm_id, tech_size) — один снимок/день/размер. Поля: price, discounted_price, discount_pct, club_discount_pct, club_discounted_price, buyer_price, buyer_wallet_price, spp_pct, snapshot_at. (buyer_wallet_price добавляется авто-миграцией.)

Запуск (раз в день, вручную — см. ниже про окно браузера):

python scripts/ingest_wb_spp_daily.py            # оба WB-магазина (3,5), с витриной
python scripts/ingest_wb_spp_daily.py --shop 5   # один магазин
python scripts/ingest_wb_spp_daily.py --no-public # только цена продавца, БЕЗ браузера
python scripts/ingest_wb_spp_daily.py --show      # последний снимок

⚠ Почему витрина через браузер, а не card.wb.ru

Сырой запрос к card.wb.ru/cards/v2/detail (httpx, любые заголовки/хосты/dest) → 403 text/html (антибот режет по TLS/JA3). Headless-браузер → антибот-челлендж «Почти готово...». Работает только headed-браузер: один раз проходит антибот, профиль _wbprofile хранит куки, дальше цены снимаются с DOM.

Следствия: - Только локально, с открывающимся окном. Тихим/headless cron'ом НЕ сделать → прод-cron сознательно не ставим. Зависимости: pip install playwright playwright-stealth (браузер chromium ставится python -m playwright install chromium, кэш общий с другими проектами). - Распроданные товары пропускаются. Перед обходом витрины скрипт сам снимает свежие остатки (ingest_stocks); если сток = 0 — витрину не дёргаем, у распроданного цены нет, buyer_price/spp_pct = NULL, это норма (не сбой). Отключить авто-обновление: --no-fresh-stocks (тогда берётся последний снимок из БД, возможны ложные «сбои» по уже распроданным).

Итог прогона печатается честно: витрина: ok=N нет_в_наличии=M сбой=K. сбой>0 = товар в наличии, но цену снять не удалось → разбираться (изменился селектор / антибот).

Проверено 2026-06-16: Tote WB — 10 товаров, в наличии 4, распродано 6, сбоев 0. skirt-atlas-max-2: цена продавца 3376 → витрина 2424 (без кошелька) / 2375 (кошелёк) → СПП 28,2% (в коридоре финотчёта 11,6–36,7%, ср. 25,4%). ELS04 → 26,7%.

Корреляция «заказы ↔ СПП по дням» (когда накопятся снимки)

SELECT d.snapshot_date, d.vendor_code, d.spp_pct,
       (SELECT COUNT(*) FROM orders o
        WHERE o.shop_id=d.shop_id AND o.sku=d.vendor_code
          AND substr(o.date,1,10)=d.snapshot_date) AS orders_cnt
FROM wb_price_daily d
WHERE d.shop_id=5 AND d.vendor_code='skirt-atlas-max-2' AND d.tech_size='S'
ORDER BY d.snapshot_date;

(СПП пишется в финотчёте по продажам; здесь — снимок на день, поэтому это два независимых ряда для корреляции.)

Фильтры