В простой криптоконвертер добавил алерты. Столкнулся с трудностями и решил их.
036 открытий915 показов
Изначально всё выглядело просто: криптоконвертер, немного кеша в JSON и WebSocket от Binance для алертов. Но с каждой новой фичей бот начинал вести себя странно. Настройки не сохранялись, алерты спамили, структура данных ломалась, сервер выдавал ошибки, а старый скрипт — не сдавался. В этой истории — реальный опыт разработки и отладки телеграм-бота, в котором всё пошло не так, как планировалось.
Начало истории
Это был просто криптоконвертер, который работал так:
REST API CoinGecko — раз в 10 минут обновлял курс. Кэш в JSON — чтобы не дудосить API. Теперь я решил добавить алерты.
Так как мой мой бот в дальнейшем будет обрастать всё новыми и новыми функциями, я решил разделять их на отдельные файлы.
Зачем разработчику знать SQL, если есть NoSQL? Разбираемся на примерахtproger.ru
Чтобы не приходилось постоянно отправлять запросы, реализовал алерты через Websocket binance.
Сделал сохранение настроек в database.json.
Я реально думал что это будет просто, ошибся:
- При деплое выдало Sintaxis Error , хотя на компе всё было нормально;
- Сохранение алертов пропадало после конвертации;
- Алерты дико спамили.
Когда я запускал бота с компа, никакой синтаксической ошибки не наблюдалось, но после того, как закинул бота на сервер, мне выдало ошибку в строке:
f"{random.choice(ALERT_TEMPLATES).format(
Сначала думал, что дело в кодировке и стоит по дефолту UTF-8 BOM. Но нет. Решил: сервер не видит, что скобка открытая в этой строке закрывается ниже. Разделил код:
alert_template = random.choice(ALERT_TEMPLATES) formatted_alert = alert_template.format( crypto=symbol.upper(), direction=direction, change=abs(price_change), advice=random.choice(ADVICE_LIST) )
Ошибка исчезла. Запустил, казалось бы, всё работает. Ставлю галочки на крипте, выхожу в меню, захожу обратно в алерты — галочки на месте. Но стоит мне перейти в конвертер и вернутся обратно, как галок и след простыл.
Долго копался в коде, пока не понял, что сама структура данных была кривой:
uuser_data = { "alerts": ["BTC"], # ← Это перезаписывалось конвертером! "conversion": {"crypto": "ETH"} }
Datetime ломал JSON — нельзя просто так сериализовать datetime.now().
Разделил струтктуру сохранения, чтобы конвертация не влияла на алерты:
user_data = { "alerts": {"cryptos": ["BTC"], "threshold": 5.0}, # Теперь отдельный блок "conversion": {...} # Не влияет на алерты }
И добавил преобразование datetime в str
timestamp = v["timestamp"].isoformat() # Для сохранения datetime.fromisoformat(v["timestamp"]) # Для загрузки
Закинул на сервер. При запуске бот не заработал, поэтому я включил режим отладки и мне выдало такое:
2025-05-07 08:57:50,151 - aiogram.dispatcher - ERROR - Failed to fetch updates - TelegramConflictError: Telegram server says - Conflict: terminated by other getUpdates request; make sure that only one bot instance is running 2025-05-07 08:57:50,151 - aiogram.dispatcher - WARNING - Sleep for 1.774987 seconds and try again... (tryings = 2, bot id = 7617680908)
Старый скрпит не хочет уступать без боя. Это конфликт ботов.
Дело в том, что я запускаю сервисы через systemd и всегда настраиваю там автозапуск на случай падения бота. Но иногда это выходит боком. Приходится включать множество команд по убийству процесса по типу:
Курсы по Big Data, включая онлайн-обучение для аналитиков больших данныхtproger.ru
sudo pkill -f “python.*bot.py” && sleep 2
kill -9 <PID>
pkill -f bot.py
Но старый бот так и не умирает. А через некоторое время сам по себе перестает работать. Я пока не понял эту аномалию. Возможно, после команды sudo systemctl reload надо просто попить чаю и дать время сервису нормально перезагрузиться.
Хорошо, бота мы убили. Нового запустили. Все кнопки работают. Включили алерты на ETH и стали ждать…
И тут началось…
Когда Эфириум подскачил на 7%, бот сошёл с ума, начал материться и спамить при малейшем сдвиге курса.
Было решено игнорировать мелкие колебания курса, установить минимальный порог реагирования 5% и высылать алерт не чаще чем 5 минут от предыдущего:
def _need_alert(self, crypto: str, change: float) -> bool: now = time.time() last_alert = self.last_alerts.get(crypto, 0) return ( abs(change) > self.threshold # Порог (например, 5%) and (now - last_alert) > 300 # 5 минут между алертами )
В итоге мой крипобот заработал, но я уверен: при тестировании вы обнаружите еще кучу багов. Далее хочу добавить кнопки для самостоятельного выбора порогов алерта и графики курса.
Со скриптом можете ознакомится здесь.
P.S. Пишите комментарии, предлагайте свои идеи. Деконструктивная и агрессивная критика приветствуется!))