Инженерный кейс
Продавец на площадке, у которой нет API
Бот держит витрину продавца и выдаёт товар автоматически: принимает заказ, собирает данные покупателя, покупает подписку у поставщика без интерфейса и отчитывается о каждом исходе.
- состояний у заказа
- 9
- способа пересоздать сессию
- 3
- работы без связи с лицензией
- 6 ч
Бот ведёт витрину продавца на торговой площадке: принимает заказ, спрашивает у покупателя данные, покупает подписку у поставщика и отчитывается о результате. Ни у площадки, ни у поставщика машинного интерфейса нет — всё идёт тем же путём, что у обычного посетителя, через антибот-защиту.
Заказ узнаётся по случайной метке в описании, а не по названию
Панель сама создаёт лот и дописывает в конец описания короткую случайную метку. При новом заказе бот достаёт описание и ищет её.
Сначала сопоставляли по словам в названии — «месяц», «30 дней». Продавец правит названия: добавляет эмодзи, меняет формулировку под сезон, и сопоставление разваливается молча. Метка живёт в описании, а не в названии, по той же причине: в названии она занимает место в выдаче, и продавец её выкусывает — код даже принудительно вырезает её оттуда при обновлении.
Успехом покупки считается запись в истории поставщика, а не код ответа
Перед покупкой бот считает, сколько покупок числится на этого получателя. После «успешного» ответа ждёт и считает снова. Не выросло — значит покупки не было.
У поставщика нет ни интерфейса, ни договорённости о кодах ответа: страница возвращает успех и когда покупка не прошла — например, не хватило средств. Доверять ответу нельзя, поэтому проверяется следствие, а не обещание.
Проверку решают снаружи, но отпечаток соединения должен совпасть
Внешний сервис проходит антибот-проверку и отдаёт пропуск вместе с описанием клиента. Бот собирает соединение ровно с этим описанием и подменяет отпечаток под тот же браузер.
Пропуск привязан к отпечатку клиента. Обычная библиотека запросов имеет узнаваемый отпечаток, и защита отклоняет запрос с действительным пропуском сразу — то есть пройти проверку, не подставив тот же отпечаток, бесполезно. Профиль браузера при этом выбран не самый свежий: в коде записано, что новые отпечатки защита отбивает чаще.
Между внешним решателем и сайтом стоит собственный прокси
Схема: работники внешнего сервиса → прокси на нашем сервере → канал провайдера → сайт. Сам бот идёт в тот же канал напрямую.
Решатель обязан видеть сайт с того же адреса, что и бот, иначе выданный пропуск не подойдёт. Но канал провайдера пускает по списку разрешённых адресов, а работники решателя приходят откуда угодно. Свой прокси — единственный способ свести это вместе.
Сессия пересоздаётся тремя способами, и выбор способа — отдельное решение
Есть три режима: переиспользовать текущую, пересобрать соединение не проходя проверку заново, и пройти проверку заново.
После длинных цепочек сайт начинает отказывать, хотя пропуск ещё действителен — протухло само соединение. Проходить проверку заново в этом случае значит платить за решение проверки там, где достаточно пересобрать соединение.
Вход к поставщику идёт через код, который уходит продавцу в чат
Полный путь: пройти защиту, открыть магазин, запросить вход по коду, отправить код картинкой продавцу и опрашивать состояние, пока тот не подтвердит с телефона.
У поставщика нет ни интерфейса, ни пары логин-пароль — вход только через подтверждение с телефона. Автоматизировать это до конца нельзя в принципе, поэтому человек встроен в поток осознанно, в одном месте и с понятным действием.
Состояние заказа — автомат на диске, а не в памяти
Девять состояний, два указателя, файл на диске, все изменения под замком. После перезапуска незавершённые заказы поднимаются и разбираются по возрасту: ожидающие ввода истекают по одному сроку, застрявшие в покупке — по другому.
Покупка занимает минуты — проверка, запросы, повторы, — и перезапуск процесса ровно в этот момент неизбежен. Состояние только в памяти означает потерянный оплаченный заказ без следов.
Частичный успех — отдельный исход, а не «ошибка» и не «успех»
Если из нескольких подписок часть выдана, а часть нет, заказ помечается ошибкой, но не пропадает: покупателю уходит построчный список, что выдано и что нет.
Пометить успешным — обмануть покупателя. Пометить провалом и вернуть деньги — подарить уже купленные подписки, они необратимы. Поэтому исход третий, и решение остаётся за продавцом.
Возврат, сделанный руками, отменяет выдачу — но не всякую
Смена статуса на возврат снимает заказ с очереди. Исключение — заказы, которые уже выполнены или прямо сейчас покупаются.
Без этого продавец возвращал деньги руками, а бот через минуту всё равно выдавал товар — двойной убыток. Но отменять заказ в середине покупки нельзя: деньги у поставщика уже потрачены.
Истёкшая авторизация — пауза с кнопкой, а не провал заказа
Если сессия у поставщика протухла, заказ уходит в ожидание: покупателю — нейтральное «техническая пауза», продавцу — оповещение с кнопкой «повторить».
Истёкшая сессия — не отказ поставщика и не ошибка покупателя, а задача продавца на полминуты. Автоматический возврат денег сжигал бы продажу из-за ситуации, которая чинится одним нажатием. Подробности ошибок покупателю не показываются вовсе — наружу идёт нейтральный текст, техника уходит в оповещение.
Клиенту отгружается байткод, а не исходники
Сборка компилирует проект, кладёт в архив только разрешённый список файлов и перекомпилирует уже на целевом сервере.
Бот содержит рабочие обходы защиты и логику покупки за реальные деньги: раздача исходников означает мгновенное растекание. Компиляция на целевой машине нужна потому, что байткод привязан к версии интерпретатора.
Python · curl_cffi · Telegram Bot API · Docker