Прямой ответ
Современный дефолт для Telegram-ботов на Python — aiogram 3.x (полностью асинхронный, роутеры и middleware как во «взрослых» фреймворках, активное развитие); python-telegram-bot — зрелая альтернатива с большой историей. Архитектурный минимум надёжного бота: long polling для разработки и простых деплоев (бот сам забирает обновления — не нужны публичный адрес и TLS) или webhook для прода под нагрузкой (Telegram пушит обновления на ваш HTTPS-эндпоинт); тяжёлые операции — в фоне (обработчик, думающий дольше нескольких секунд, блокирует очередь обновлений); токен бота — секрет уровня пароля (утёк — бота угнали; хранение — по правилам секретов). И обязательная проверка лимитов Telegram API — бот, упершийся в flood-контроль, молча деградирует.
Выбор библиотеки и каркас
aiogram 3.x: роутеры (Router с хендлерами по типам/командам/фильтрам), middleware (логирование, авторизация, трассировка — сквозные слои), FSM (конечный автомат диалогов — «спросить имя, потом возраст» без лапшы флагов), встроенная валидация типов апдейтов. Каркас продакшн-бота: роутеры по доменам (start/help, заказ, админка), middleware авторизации (не пускать незнакомых — бот публичен по построению), слой сервиса (логика без импортов aiogram — тестируема), хранилище состояний (Redis для FSM при нескольких воркерах; локальный Redis — из dev-стека). python-telegram-bot (v20+ тоже async) — тот же набор концепций другими именами; выбор чаще про документацию и примеры, чем про возможности.
Polling или webhook: честное сравнение
Long polling (bot.polling()): бот ходит за обновлениями сам — работает из-за NAT, без домена/сертификата, идеален для разработки и одиночных ботов; минус — задержка доставки чуть выше, и один polling-клиент на бота (запустил второй инстанс — конфликт). Webhook: Telegram POST'ит апдейты на ваш URL (HTTPS обязателен, порт из разрешённого списка) — мгновенная доставка, горизонтальное масштабирование (приём за балансировщиком); требования — публичный адрес, корректные таймауты ответов (обработка дольше — в фоновую очередь, ответ 200 сразу), защита эндпоинта (secret_token в заголовке — иначе любой может слать вам «обновления»). Правило: разработка — polling, прода с нагрузкой/несколькими инстансами — webhook; локальная отладка webhook-сценария — туннель (SSH-туннели и проброс).
Обработчики, состояния и UX-мелочи
Диалоги с вопросами — FSM (StatesGroup: шаги, переходы, отмена по /start — обязательна, иначе пользователь застревает в забытом сценарии); inline-кнопки (callback_query — с обязательным answer — иначе у пользователя «часики» вечные); команды — зарегистрированы (set_my_commands — меню клиента показывает список). UX-практики из опыта: приветствие с явными следующими шагами (не «привет» в пустоту); ошибки — человеческим языком с вариантом действия («не получилось, попробуйте ещё раз или /help»); долгие операции — «обрабатываю…» сразу и результат следом (молчание пугает); идемпотентность действий с деньгами/подписками (двойной клик по кнопке не должен списывать дважды — дедупликация по callback-данным).
Ограничения API и надёжность отправки
Лимиты Bot API — факт жизни: ~30 сообщений/сек globally, ~20 сообщений/минуту в одну группу, ~1 сообщение/сек одному чату (broadcast тысячам — очередь с троттлингом, не цикл в лоб); ошибки 429 несут retry_after — уважать буквально (повтор раньше — продление бана). Надёжность: исходящие — с ретраями на сетевые сбои и учётом «чат забанил бота» (403 — чистить базу подписчиков, не долбить); входящие апдейты при webhook — подтверждать быстро, обработку выносить (асинхронная дисциплина и блокировки — в статье про asyncio); плановые рассылки/тяжёлые задачи — через очередь (Celery с rate_limit идеально ложится на телеграм-лимиты). Логи — структурные, с chat_id (разбор «пользователь жалуется» начинается с его истории).
Деплой и эксплуатация
Минимальный прод: один процесс (polling) в контейнере/systemd-юните с авторестартом, Redis для FSM, логи в файл/стек; горизонталь — webhook + N воркеров за прокси (polling так не масштабируется). Обновления бота — graceful (дообработать текущие апдейты; webhook-режим делает это почти бесплатно). Мониторинг — «бот жив» (healthcheck + алерт на молчание — упавший бот молчит по определению) и очередь необработанных (getUpdates-лаг). Секреты: токен в env, никогда в репозиторий (утечка токена = полный контроль бота, лечение — немедленный revoke у BotFather); вебхук-секрет — отдельный; данные пользователей (chat_id — персональный идентификатор) — по правилам приватности продукта.
Практический сценарий: бот-напоминалка целиком
Соберём характерного бота: пользователь присылает текст и время — бот напоминает. Архитектура: aiogram 3, роутер reminders с FSM из двух шагов (текст → время с валидацией парсером дат; отмена по /start из любого шага обязательна); данные — SQLite через aiosqlite (напоминание: user_id, текст, когда, статус «активно/отправлено»). Хранилище FSM — Redis (переживёт рестарт процесса; память-хранилище теряет диалоги при каждом деплое). Доставка — отдельный фоновый цикл: каждые десять секунд выбирает напоминания с due <= now и статусом «активно», отправляет и помечает — а не asyncio-таск на каждое напоминание (сотни тасков с sleep на часы — рецепт утечек и потерь при рестарте); при лимите 429 — пауза по retry_after. Кнопки: «Отменить» и «Отложить на час» — inline-кнопки с callback_data, на нажатие — обязательный answerCallbackQuery (иначе у пользователя вечные «часики»). Авторизация: middleware проверяет, что chat_id в списке разрешённых (бот публичен — без проверки им воспользуется кто угодно). Токен — env-переменная, в логах не светится. Деплой — один контейнер с polling и рестартом, healthcheck «бот отвечал на getMe в последнюю минуту». Весь бот — триста строк, и каждая архитектурная мелочь в нём (FSM, Redis-состояния, фоновый цикл доставки, answer на callback) закрывает конкретный способ сломаться.
FAQ
Можно ли одним ботом слушать и polling, и webhook? Нет: setWebhook и getUpdates взаимоисключающи (активный вебхук отключает поллинг — и наоборот). Переключение режимов — явная операция при деплое.
aiogram 2 или 3? Только 3.x для новых проектов: 2.x — устаревшая архитектура (магические фильтры, нет роутеров), документация и сообщество давно на третьей. Туториалы под 2.x в сети — источник путаницы, проверяйте версию в примере.
Как хранить данные бота? Как в любом приложении: SQLite для маленьких (одиночный инстант), PostgreSQL при росте; Redis — FSM-состояния и кеши. «Данные в глобальных dict процесса» умирают при первом рестарте — типовой баг первых ботов.
Нужна ли отдельная инфраструктура для бота на 100 пользователей? Нет: один контейнер на дешёвом VPS/бесплатном тире покрывает тысячи пользователей (лимит Telegram — не нагрузка, а правила); инфраструктура растёт от фактической нагрузки, а не заранее.
Источники: документация aiogram, Telegram Bot API, python-telegram-bot — ссылки в шапке.