Как устроен Bot API: polling vs webhook
Когда ты нажал «Запустить» в прошлом уроке и Цыплёнок ответил на /start — между твоим сообщением и его ответом произошёл маленький фокус. Сейчас мы его раскроем.
Главная мысль урока: бот не сидит в Telegram. Он живёт на твоём компьютере, а сообщения от пользователей до него надо как-то довезти. Есть два способа доставки: бот сам бегает за новостями (polling) или Telegram приносит их ему сам (webhook). От выбора зависит, нужен тебе сервер с белым адресом или хватит ноутбука под столом.
Зачем тебе вообще это знать
В прошлом уроке ты собрал самого первого Цыплёнка и запустил его. Скорее всего, в коде была строчка вроде dp.run_polling(bot), и ты не особо задумывался, что это значит — главное, что работало. Так вот, в этом слове polling прячется целый механизм, и без понимания его легко наступить на грабли, на которые наступают вообще все новички.
Типичная история: ты написал бота, всё классно, выложил код на GitHub, чтобы похвастаться другу. А через час тебе приходит письмо от Telegram: «ваш токен скомпрометирован, мы его отозвали». Или другое: ты запускаешь второго бота с тем же токеном, и оба начинают сходить с ума — отвечают через раз, теряют сообщения. Или третье: ты прочитал в умной статье слово webhook, попробовал его включить, и бот замолчал намертво. Все три беды растут из одного корня — из непонимания, как именно бот получает сообщения.
К концу урока ты будешь чётко представлять путь сообщения от пальца пользователя до кода твоего хэндлера, поймёшь разницу между двумя способами доставки и будешь знать, какой выбрать сейчас, а какой — когда дорастёшь до настоящего сервера.
Сначала разберёмся, что такое обновление (update)
Update (обновление) — единица входящих данных от Telegram: новое сообщение, нажатая кнопка или другое событие.
Представь школьный ящик для записок у входа в класс. Каждый раз, когда кто-то хочет тебе что-то сказать, он не кричит на весь коридор — он пишет записку и кидает её в ящик. Записка про домашку, записка «выйди на перемене», записка с мемом — всё это разные записки, но лежат они в одном ящике, по порядку.
У Telegram для твоего бота тоже есть такой ящик. Только записки в нём называются обновлениями (по-английски update). Кто-то написал боту «привет» — в ящик упало обновление. Кто-то нажал кнопку под сообщением — ещё одно обновление. Кто-то добавил бота в группу — снова обновление. Telegram аккуратно складывает их по порядку и нумерует, чтобы ничего не потерялось и не перепуталось.
Важно понять вот что: Telegram не знает, где живёт твой бот, и сам стучаться к нему не будет (по умолчанию). Он просто копит обновления у себя на сервере и ждёт, когда их заберут. Это как почтовое отделение: посылка пришла, лежит на полке, но почтальон к тебе домой её не понесёт, пока ты сам не оставишь заявку на доставку.
Способ первый: polling — бот сам бегает за новостями
Polling — способ получать обновления, при котором бот сам регулярно спрашивает у Telegram «есть ли что-то новое?».
Вспомни, как ты ждёшь ответа на важное сообщение в мессенджере. Телефон вроде должен звякнуть сам, но ты всё равно каждые тридцать секунд разблокируешь экран и проверяешь: пришло? пришло? а сейчас? Вот это и есть polling — бот ведёт себя ровно так же.
Слово poll по-английски значит «опрашивать». Бот в режиме polling работает по кругу:
- Бот сам подключается к серверам Telegram и спрашивает: «Привет, для меня есть новые обновления?»
- Если есть — Telegram отдаёт их пачкой, бот разбирает каждое и зовёт нужный хэндлер.
- Если новостей нет — Telegram просто немного держит соединение и через время отвечает «пока пусто».
- Бот тут же спрашивает снова. И снова. И снова — пока работает программа.
Главная прелесть в том, что инициатива всегда у бота. Это значит, что боту не нужен ни белый IP-адрес, ни домен, ни сертификат — вообще ничего, кроме доступа в интернет. Он сам исходящим соединением дотягивается до Telegram, как ты сам открываешь сайт в браузере. Поэтому polling прекрасно работает с ноутбука, из дома, из школьного компьютера — откуда угодно.
Как это выглядит в коде Цыплёнка
Открой свой bot.py из прошлого урока. Внизу почти наверняка есть что-то такое:
import asyncio
from aiogram import Bot, Dispatcher
from aiogram.filters import CommandStart
from aiogram.types import Message
import os
bot = Bot(token=os.getenv("BOT_TOKEN"))
dp = Dispatcher()
@dp.message(CommandStart())
async def start_handler(message: Message):
await message.answer("Привет! Я Цыплёнок-помощник 🐥")
async def main():
await dp.start_polling(bot)
if __name__ == "__main__":
asyncio.run(main())Результат: в чате бот ответит «Привет! Я Цыплёнок-помощник» на команду /start. Строка dp.start_polling(bot) запускает тот самый бесконечный круг опроса: пока программа в терминале не остановлена, Цыплёнок снова и снова спрашивает Telegram про новые обновления и раздаёт их хэндлерам.
Разберём по шагам, что тут происходит:
bot = Bot(token=...)— создаём объект Bot, который умеет ходить в Telegram. Токен берём не из кода напрямую, а из переменной окруженияBOT_TOKEN— почему так, скоро поймёшь.dp = Dispatcher()— создаём Dispatcher, диспетчера. Это он будет разбирать прилетающие обновления и решать, какому хэндлеру их отдать.dp.start_polling(bot)— говорим диспетчеру: «запускай цикл опроса, забирай обновления через объектbotи раздавай их моим хэндлерам». Эта строка не возвращает управление — она крутится, пока ты не нажмёшь Ctrl+C.
Заметь: нигде нет ни адреса сервера, ни порта, ни сертификата. Боту это и не нужно — он сам инициатор связи.
Способ второй: webhook — Telegram сам приносит новости
Webhook — способ получать обновления, при котором Telegram сам присылает их на адрес твоего сервера.
А теперь представь, что ты не дёргаешь телефон каждые полминуты, а просто включил пуш-уведомления. Пришло сообщение — телефон сам звякнул. Не пришло — он молчит и не тратит твоё внимание. Вот это webhook.
В этом режиме инициатива переворачивается: теперь стучится Telegram, а не бот. Ты один раз говоришь Telegram: «Слушай, когда для моего бота появится обновление, не жди, пока я приду, — сразу отправь его вот на этот адрес: https://мой-сервер.ру/webhook». Telegram запоминает адрес. И как только кто-то пишет боту, Telegram мгновенно делает HTTP-запрос на твой сервер с этим обновлением внутри.
Звучит удобнее, да? Никаких бесконечных «а сейчас? а сейчас?». Но за удобство надо платить, и плата серьёзная для новичка: у тебя должен быть сервер, до которого Telegram сможет дотянуться из интернета. А это значит:
- публичный адрес (домен или белый IP), который видно из внешнего мира — ноутбук под столом так просто не подходит;
- обязательно HTTPS — Telegram отказывается слать обновления на незащищённое соединение;
- работающая веб-программа, которая принимает входящие запросы и не падает.
Как это выглядит в коде
Webhook мы по-настоящему подключим в самом конце курса, когда будем деплоить Цыплёнка на сервер. Но чтобы ты увидел разницу глазами, вот упрощённый скелет:
from aiohttp import web
from aiogram import Bot, Dispatcher
from aiogram.webhook.aiohttp_server import SimpleRequestHandler, setup_application
import os
bot = Bot(token=os.getenv("BOT_TOKEN"))
dp = Dispatcher()
WEBHOOK_PATH = "/webhook"
WEBHOOK_URL = "https://moy-server.ru" + WEBHOOK_PATH
async def on_startup(app):
# один раз говорим Telegram, куда слать обновления
await bot.set_webhook(WEBHOOK_URL)
app = web.Application()
SimpleRequestHandler(dispatcher=dp, bot=bot).register(app, path=WEBHOOK_PATH)
setup_application(app, dp, bot=bot)
app.on_startup.append(on_startup)
if __name__ == "__main__":
web.run_app(app, host="0.0.0.0", port=8080)Результат: бот будет вести себя для пользователя точно так же — ответит на /start «Привет, я Цыплёнок». Но под капотом всё иначе: вместо цикла опроса поднимается маленький веб-сервер на aiohttp, который слушает порт 8080 и ждёт, пока Telegram сам постучится на адрес /webhook с новым обновлением. Строка bot.set_webhook(...) — это и есть та самая «заявка на доставку», которую бот один раз оставляет в Telegram.
Обрати внимание на главное отличие: тут есть host, port, WEBHOOK_URL — целый адрес. В polling-версии этого не было вообще. Это и есть та цена, о которой я говорил.
Сравним лоб в лоб
Чтобы всё уложилось, держи табличку. Перечитывай её, когда будешь выбирать способ для своего проекта.
| Вопрос | Polling | Webhook |
| Кто кому стучится? | Бот стучится в Telegram | Telegram стучится боту |
| Нужен публичный адрес и HTTPS? | Нет | Да, обязательно |
| Запустится с ноутбука дома? | Да, без настроек | Нет (нужен сервер) |
| Реакция на сообщение | С маленькой задержкой (раунд опроса) | Практически мгновенно |
| Нагрузка при тысячах пользователей | Бот постоянно тратит соединения | Запрос приходит только когда есть что доставить |
| Сложность настройки | Одна строка кода | Сервер, домен, сертификат, веб-приложение |
Маленький разбор: как Telegram нумерует обновления
Помнишь, я сказал, что обновления пронумерованы по порядку? У каждого есть число update_id. Когда бот в режиме polling забирает пачку обновлений, он запоминает самый большой номер и в следующий раз просит «всё, что новее этого». Так Telegram понимает, какие записки ты уже забрал, и больше их не отдаёт.
Чтобы это не звучало абстрактно, давай чисто на Python смоделируем эту логику маленьким сниппетом — без всякого Telegram, просто чтобы прочувствовать идею «отдавай только то, что новее»:
# Telegram держит обновления как список с номерами
inbox = [
{"update_id": 101, "text": "привет"},
{"update_id": 102, "text": "как дела"},
{"update_id": 103, "text": "/start"},
]
# Бот уже забирал всё до 101 включительно
last_seen = 101
# Просим только то, что новее last_seen
new_updates = [u for u in inbox if u["update_id"] > last_seen]
for u in new_updates:
print(u["update_id"], "->", u["text"])
last_seen = u["update_id"]
print("Теперь last_seen =", last_seen)Вывод:
102 -> как дела 103 -> /start Теперь last_seen = 103
Видишь? Обновление 101 бот пропустил — он его уже видел. Забрал только 102 и 103 и сдвинул свою метку. В реальности aiogram делает ровно это за тебя, ты эту кухню даже не замечаешь. Но теперь ты знаешь, что внутри.
Частые ошибки и подводные камни
1. Два бота с одним токеном в режиме polling
Самая частая беда. Ты запустил Цыплёнка на ноутбуке, забыл выключить, пришёл домой и запустил его же на домашнем компьютере. Теперь два процесса дерутся за один ящик обновлений. Telegram отдаёт каждое обновление только кому-то одному, поэтому половина сообщений уходит одному боту, половина — другому, и оба будто «глючат». Telegram даже честно ругается в логах: terminated by other getUpdates request. Правило простое: один токен — один запущенный polling одновременно.
2. Попытка включить webhook без настоящего сервера
Прочитал, что webhook «быстрее и круче», вызвал bot.set_webhook("https://localhost/webhook") с ноутбука — и бот замолчал. А он и не мог заработать: localhost виден только тебе, Telegram до него не дотянется. Пока у тебя нет публичного домена с HTTPS, webhook просто не для тебя. Это нормально — почти все начинают с polling.
3. Включил webhook — и polling перестал работать (и наоборот)
Это не баг, это правило: у бота может быть активен только один способ доставки. Если ты раньше ставил webhook, а теперь хочешь снова polling, надо сначала его снять — у объекта bot для этого есть delete_webhook(). Иначе Telegram продолжает думать, что обновления надо слать на старый адрес, и твой start_polling получает пустоту.
4. Токен прямо в коде на GitHub
Токен — секретный ключ вида 123456:ABC..., который Telegram выдаёт боту и по которому отличает его от других; его нельзя публиковать.
Заметил, что в обоих примерах токен берётся через os.getenv("BOT_TOKEN"), а не вписан строкой? Это не случайно. Токен — как пароль от твоего аккаунта: кто им завладел, тот управляет ботом. Если ты впишешь токен прямо в код и выложишь репозиторий, специальные роботы найдут его за минуты. Поэтому секреты держат в переменных окружения — отдельно от кода. Подробно мы это настроим в модуле про деплой, а пока просто привыкай к привычке: токен в коде словом не пишем.
5. Ждёшь от polling мгновенной реакции
Polling опрашивает Telegram циклами, поэтому крошечная задержка между «нажал» и «бот ответил» — это норма, а не поломка. Для бота-напоминалки про домашку или опросника для друзей такая задержка вообще незаметна. Гнаться за webhook ради «миллисекунд» на старте смысла нет.
Мини-практика
Теорию закрепляем руками и головой. Сделай три вещи:
- Найди слово polling в своём коде. Открой
bot.pyи убедись, что там естьdp.start_polling(bot)(илиdp.run_polling(bot)). Это и есть способ доставки, на котором сейчас живёт твой Цыплёнок. - Спровоцируй ошибку «двух ботов» специально. Запусти Цыплёнка в одном терминале, а потом во втором терминале запусти его же ещё раз тем же токеном. Понаблюдай, как Telegram начинает ругаться
terminated by other getUpdates. Поймёшь беду номер один на своей шкуре — и больше никогда на неё не попадёшься. Потом закрой второй процесс. - Ответь себе письменно (можно в комментарии в коде): «Мой бот живёт у меня на ноутбуке, у меня нет домена и сервера. Какой способ доставки мне подходит — polling или webhook? Почему?» Если ответил «polling, потому что не нужен публичный адрес» — ты усвоил главное.
Со звёздочкой: загляни в документацию aiogram 3.x и найди метод bot.get_webhook_info(). Подумай, зачем он может пригодиться, когда ты не уверен, в каком режиме сейчас твой бот. (Подсказка: он покажет, висит ли на боте webhook-адрес.)
Итоги
Давай соберём всё в три коротких мысли, которые надо унести с собой:
- Обновление (update) — это любая «записка» от Telegram боту: сообщение, нажатие кнопки, добавление в группу. Telegram копит их у себя и нумерует.
- Polling — бот сам бегает за обновлениями. Просто, работает с ноутбука, не нужен сервер. Это твой выбор на весь курс, кроме самого финала.
- Webhook — Telegram сам приносит обновления на твой сервер. Быстрее и экономнее под нагрузкой, но требует домена, HTTPS и работающего веб-приложения. Подключим в модуле про деплой.
Теперь ты понимаешь, что прячется за невинной строчкой start_polling, и не испугаешься, когда встретишь слово webhook в чужом коде. А в следующих уроках мы перестанем заставлять Цыплёнка отвечать одной фразой и научим его показывать кнопки и меню — чтобы с ним было удобно общаться даже тем, кто не знает ни одной команды. Поехали дальше 🐥