RUEN
Обсудить проект

Инженерный кейс

Мониторинг витрин в играх

Панель следит за внутриигровыми магазинами двух десятков игр: что появилось, что убрали, что подорожало. Изменения уходят подписчикам, история хранится по каждому товару.

источника за одним контрактом
23
промаха до пометки об удалении
3
порог блокировки удалений
50%

Панель непрерывно следит за внутриигровыми магазинами двух десятков игр и показывает в одном окне, что появилось, что убрали и что подорожало. По каждому товару ведётся история, изменения уходят подписчикам в мессенджер. Главная сложность в том, что почти ни одна игра не отдаёт витрину добровольно: у каждой свой способ добычи, и все они спрятаны за одним интерфейсом.

Единый контракт прячет несопоставимые способы добычи

Все сканеры наследуют один абстрактный класс: обязательные имя игры и метод обхода, необязательные уборка и проверка доступности. Обёртка превращает любое исключение в результат со статусом «не удалось».

Способы получить витрину несопоставимы между собой: где-то открытый веб-магазин, где-то данные из кэша клиента, где-то разбор трафика, где-то распознавание экрана. Без общего контракта каждый источник протёк бы в расписание, обход и рассылку, и добавление игры означало бы правки в пяти местах. А обёртка гарантирует, что падение одного сканера не роняет весь цикл.

Товар не удаляется по первому исчезновению

У каждой записи есть счётчик промахов: пометка об удалении ставится только после трёх подряд успешных обходов, в которых товара не было. Возвращение сбрасывает счётчик.

Очевидная реализация «нет в новом обходе — удалён» ловит собственный хвост. Источники периодически отдают неполный список без видимых причин, и наивное сравнение порождает пары-призраки: товар помечается удалённым, а в следующем обходе тем же товаром «появляется» — и оба события улетают подписчикам как настоящие.

Массовое удаление блокируется, если обход выглядит неудачным

Перед пометкой считается доля пропавшего. Пустой ответ при известных активных товарах, исчезновение больше половины, а для нестабильных источников и меньшая доля — все удаления пропускаются с записью в журнал.

Истёкшая сессия, сбой сети или магазин на обслуживании дают пустой ответ. Без предохранителя один такой обход «удалит» весь магазин, разошлёт подписчикам сотню удалений, а следующий удачный разошлёт те же сто как новинки. Порог отделяет «магазин правда опустел» — редкость — от «обход не удался», и в сомнительном случае система молчит, а не врёт.

Призраки гасятся сопоставлением по имени и цене

Перед рассылкой списки «добавлено» и «удалено» пересекаются по паре «название и цена», и совпавшее выбрасывается из обоих.

Часть игр меняет внутренний идентификатор товара между обходами, оставляя сам товар прежним. По идентификатору это выглядит как «старый удалён, новый добавлен» — два ложных уведомления об одном неизменном товаре. Сопоставление по тому, что человек реально видит, убирает пару целиком.

В чат идут только платные изменения

Перед отправкой каждое изменение проверяется дважды: настоящие ли это деньги и человеческое ли это название, а не внутренний код. Самый первый обход в пустую историю не рассылает ничего.

Клиенту важны покупки за реальные деньги, а магазины полны наград за игровую валюту и служебных записей. Без фильтра первый же запуск разошлёт весь каталог сотнями строк, а дальше в чат посыплются внутренние коды, протёкшие из кэша.

Ход обхода показывается лентой, а не одним ответом

Тяжёлые сканеры отдают поток событий: шаг, ещё шаг, готово со сводкой.

Отдельный обход идёт от секунд до минут — запуск игры, ожидание загрузки, разбор. Обычный запрос-ответ повесил бы вкладку на это время без признаков жизни, и любой промежуточный таймаут оборвал бы его как ошибку. Лента показывает, что процесс идёт, и на каком шаге он встал, если встал.

Лёгкие и тяжёлые сканеры разведены по паузам

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

Тяжёлые конкурируют за одно физическое устройство: гнать их вплотную — гарантированные конфликты. Лёгкие страдать от этой очереди не должны, поэтому идут первыми.

Метка «новое» живёт до реального изменения, а не до следующего обхода

В данных хранятся два времени: когда последний раз обновляли и когда последний раз что-то менялось. Метка в интерфейсе привязана ко второму.

Привяжи её к обходу — и она исчезнет через один цикл: человек, зашедший через час, изменений уже не увидит. Метка должна отмечать событие, а не факт опроса.

При обновлении сессии порядок уборки перевёрнут

Когда сохранённая сессия протухает, система обновляет её сама. Критичен порядок завершения: сначала аккуратно возвращаются сетевые настройки устройства, с проверкой, что вернулись, и только потом завершается вспомогательный процесс. Плюс флаг «обновление уже идёт».

Очевидное «сделал дело — прибил всё разом» оставляет устройство в состоянии, где его запросы упираются в уже мёртвого помощника: любая следующая команда зависает, и чинится это руками. А без флага два параллельных обновления одной сессии затирали бы друг друга.

Python · FastAPI · httpx · protobuf · Telegram Bot API