Уход своей цены ниже согласованного прайса и демпинг соседа по полке видно в тот же день, а не в конце месяца по отчёту — сразу на Wildberries и на Ozon. Человека система зовёт только тогда, когда есть что решать.
Продавец живёт сразу в двух ценах: своя обязана держаться РРЦ, согласованного с брендом, а цена соседа по полке решает, купят у вас или у него. Обе меняются в течение дня — акции, автоскидки, кошелёк.
Ошибка стоит дорого в обе стороны. Ушли ниже РРЦ — вопросы от бренда, а в пределе условия поставки. Ушли выше рынка — заказы уходят соседу, и понимаешь это через месяц по отчёту. Переписать цифры в блокнот можно, но через час они другие, а вчерашней цены конкурента не найти нигде.
Площадок при этом две, и они не копия друг друга: свой прайс, своя витрина, свой набор соседей по полке. Вывод, снятый на одной, на второй может быть прямо противоположным — а сверять руками приходится обе.
В систему не нужно заходить. Она сама опрашивает продавцов каждые четыре часа и пишет человеку только тогда, когда есть что решать: подтянуть позицию к прайсу, ответить на чужое снижение или сознательно ничего не делать. Порог здесь важнее самой рассылки — уведомление, которое приходит всегда, перестают читать через неделю.
Под этим — разделение, ради которого систему стоило собирать инженерно, а не «в таблице». Сбор вынесен в отдельные сервисы на Python: Wildberries блокирует Node по TLS-отпечатку и отдаёт 403 там, где Python получает 200. Дашборд не ждёт сценарий и опрашивает сборщики сам — поэтому история копится, даже когда сценарий упал или его переписывают.
Ровно это разделение и позволило добавить Ozon, не трогая ничего выше. Ozon серверу витрину не отдаёт вовсе: с Railway приходит 403 от антибота, а через официальный Seller API видно только собственные товары — конкурентов там нет и не будет. Поэтому второй сборщик устроен иначе: свои цены он берёт по ключам API, а полку конкурентов снимает живой Chrome на рабочей машине, шесть раз в сутки по расписанию. Наружу оба сборщика отдают одинаковые строки, так что ни сценарии, ни дашборд про эту разницу не знают.
Как это выглядит у человека. Приходит сообщение: позиция, старая и новая цена, отклонение. Дальше решение на месте или клик в дашборд — посмотреть, что творилось с позицией и её группой за неделю. Минуты вместо обхода карточек.
Одна сетка времени на всех. Замер в 11:03 подписывается как 11:00 — ровно той строкой, что уходит в таблицу. Иначе в разговоре с брендом пришлось бы выбирать, какой из двух историй верить.
Разный сбор, одинаковый ответ. Wildberries отвечает по HTTP, Ozon — только живому браузеру. Разница заканчивается внутри сборщика: наружу оба выдают один формат строки, и вторая площадка не потребовала ни второй таблицы формул, ни второй версии дашборда.
Сборщики и дашборд написаны на чистом Python без внешних зависимостей, в Docker на Railway; обход витрины Ozon — единственная часть, которой нужна живая машина с браузером, и она вынесена ровно в эту одну коробку. Практический смысл: нет подписок, которые подорожают, и чужих библиотек, из-за которых через год всё переписывать.
Шаг с прошлого замера ловит резкое движение и пропускает медленное: минус полтора процента за раз не превысит порога, а за неделю это минус двадцать. Вторая величина — отклонение от недельной средней — и ловит тихий демпинг.
Цена бутылки ни о чём не говорит, пока не известен объём. Товары разложены по группам и сравниваются по цене за 100 единиц — иначе решение принимается по ложному сравнению.
Скидки кошелька в данных площадки нет, она выводится формулой. В журнал пишется исходная цена: площадка поменяет условия — историю пересчитают, и двухлетний архив не обесценится.
«Лососевое масло с астаксантином» не содержит слова «мл», и разбор текста давал «шт». Единица берётся из типа товара или из группы: одна такая ошибка переворачивает вывод, дешевле конкурент или дороже.
У нового продавца все товары формально «изменились»: сравнивать не с чем. Такие строки помечаются первичными, уведомления идут со второго замера — иначе подключение конкурента даёт сотню ложных сигналов, и канал перестают читать.
Однажды из-за отказа при отправке не записались собранные данные — потерян был не алерт, а сам замер. Теперь доставка помечена как некритичная: цену в тот час уже не переснять, а сообщение можно послать позже.
Ozon отвечает облаку 403 от антибота, а в Seller API конкурентов нет вовсе — только свои товары. Поэтому витрину снимает Chrome со своим профилем на рабочей машине: расписание поднимает его само, проходит проверку, забирает готовый CSV прямо из страницы и гасит окно. Прятаться в headless бессмысленно — на него антибот реагирует злее, чем на обычное окно.
Дашборд по устройству отбрасывает всё, чего нет в справочниках, а выброшенное потом не восстановить: витрина помнит только «сейчас». Поэтому тот же обход кормит ещё и журнал, который пишет каждый SKU без отбора. Когда в справочнике нашлась дыра на девять позиций, история по ним лежала в журнале: узкий приёмник чинится из широкого, наоборот — никогда.
Прайс обновили — и обновили в одном месте, на позициях Wildberries; на Ozon семь цен из шестнадцати месяц прожили старыми, а сверка при этом выглядела абсолютно рабочей. Теперь перенос односторонний и делается скриптом, который цен не хранит вовсе: берёт РРЦ у позиции WB и кладёт парной позиции Ozon. Пары выписаны руками — по названию их не подобрать, на второй площадке карточки зовутся иначе.
Товар, который на неделю уехал в 4 500-5 700 ₽ при рынке 260-1 440, прижимал всю группу полосой к нижнему краю — график переставал отвечать на вопрос, ради которого нарисован. Шкала теперь ищет в ценах разрыв и обрывается на нём, а то, что за краем, помечено птичкой и подписано в подсказке. Порог разрыва не выбран, а подобран прогоном по всем группам: при более мягком под обрезку попадали живые группы с настоящим разбросом цен.
Формула «с первого сентября берём новый прайс» первого сентября отдавала старый: в поясе книги было ещё тридцать первое августа. Сверка показывала отклонение +95%, и цифра выглядела совершенно настоящей — такие ошибки не падают, а тихо врут. Зависимость витрин от текущей даты убрана совсем.
Сверяли руками, когда доходили руки, и по каждой площадке отдельно. Расхождение с прайсом замечали постфактум, вчерашней цены конкурента было не найти, а на вопрос «почему просели заказы» отвечали догадками.
Расхождение с прайсом правят в день, когда оно возникло, на обеих площадках сразу. Чужое снижение цены видно не по итогам месяца, а в тот же день, и ежедневный обход карточек ушёл из работы менеджера совсем.
4 часа — столько теперь живёт нарушение прайса или чужой демпинг, прежде чем о нём узнают. Раньше это были недели. Плюс своя история цен рынка, которую задним числом не восстановить ничем.
Вторая площадка дала не удвоение отчёта, а вывод, которого раньше не было. Оказалось, что конкуренты на маркетплейсах разные: продавец, который на Wildberries бьётся с нами по маслам, на Ozon торгует в основном косметикой и в этой полке не участвует вовсе, зато по сахарозаменителям там плотно стоят те, кого на WB в группе нет. И собственный прайс площадки держат по-разному: на Wildberries ниже РРЦ ушли 24 позиции из 25, на Ozon — четыре.
Практический смысл прост: решение «снижаться или держать» принимается для каждой площадки отдельно, и перенос вывода с одной на другую — это ошибка, которую раньше нечем было заметить.
Площадку знает только сборщик; всё, что выше, работает с числами во времени. Это была гипотеза — Ozon её проверил: пороги, формулы витрин, вёрстка графиков и логика уведомлений остались нетронутыми, хотя сам способ добычи данных сменился с HTTP-запроса на живой браузер. Добавились ровно две вещи: второй сборщик и справочники второй площадки.
Поэтому третий маркетплейс, остатки, позиции в поиске или отзывы — это правка, а не новый проект: замер → журнал → пороги → человеку показывают только изменение.
Система собрана мной целиком: оба сборщика, сценарии, формулы витрин, дашборд и его вёрстка. На кадрах живые данные, заказчик обезличен: имя компании, названия товаров и артикулы закрыты. Конкуренты оставлены как есть — это публичная витрина.
От вас — прайс, площадки и список конкурентов. Остальное на мне: сбор, история, пороги и уведомление, которое приходит само.