Создание бота для Telegram с использованием Python: базовые и продвинутые подходы

Раздел: Разработка на Python -> Телеграм боты

Основные подходы к созданию Telegram ботов на Python

Наиболее эффективное решение: библиотека python-telegram-bot (v20.x, асинхронная)

Эта библиотека поддерживает asyncio, обеспечивает высокую производительность и удобное управление состояниями, клавиатурами, middleware. Рекомендуется для большинства проектов.

Установка и получение токена

Токен выдается BotFather в Telegram. Команда /newbot создаёт бота. Установка библиотеки:

pip install python-telegram-bot

бот python (создание telegram бота на python)

Первый эхо-бот

import logging
from telegram import Update
from telegram.ext import Application, CommandHandler, MessageHandler, filters

logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO)
logger = logging.getLogger(__name__)

async def start(update: Update, context):
    await update.message.reply_text('Привет! Я эхо-бот.')

async def echo(update: Update, context):
    await update.message.reply_text(update.message.text)

async def help_command(update: Update, context):
    await update.message.reply_text('Отправьте любое сообщение, и я повторю его.')

def main():
    application = Application.builder().token('YOUR_TOKEN').build()
    application.add_handler(CommandHandler('start', start))
    application.add_handler(CommandHandler('help', help_command))
    application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, echo))
    application.run_polling()

if __name__ == '__main__':
    main()

Добавление клавиатуры и обработка callback

from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import CallbackQueryHandler

async def button(update: Update, context):
    query = update.callback_query
    await query.answer()
    await query.edit_message_text(text=f'Вы выбрали: {query.data}')

async def start_with_buttons(update: Update, context):
    keyboard = [
        [InlineKeyboardButton('Вариант 1', callback_data='1'),
         InlineKeyboardButton('Вариант 2', callback_data='2')]
    ]
    reply_markup = InlineKeyboardMarkup(keyboard)
    await update.message.reply_text('Выберите вариант:', reply_markup=reply_markup)

def main():
    application = Application.builder().token('TOKEN').build()
    application.add_handler(CommandHandler('start', start_with_buttons))
    application.add_handler(CallbackQueryHandler(button))
    application.run_polling()

Типичные проблемы и их решения

  • Бот не отвечает: проверьте токен, интернет, не запущен ли другой экземпляр бота. Используйте фильтры.
  • Callback данные не приходят: длина callback_data ограничена 64 байтами. Для хранения состояния используйте context.user_data.
  • Asyncio ошибки: все хендлеры должны быть асинхронными. Используйте await.
  • Rate limiting: Telegram ограничивает 30 сообщений в секунду. При большом потоке добавьте задержки или очередь.

Как создать бота синхронно, не углубляясь в asyncio?

Библиотека pyTelegramBotAPI (telebot) работает синхронно, проста в освоении.

import telebot

bot = telebot.TeleBot('TOKEN')

@bot.message_handler(commands=['start'])
def send_welcome(message):
    bot.reply_to(message, 'Привет!')

@bot.message_handler(func=lambda m: True)
def echo(message):
    bot.reply_to(message, message.text)

bot.polling()

Минусы: блокирующий ввод-вывод, трудности с масштабированием. Подходит для небольших простых ботов.

Как использовать aiogram для асинхронной разработки?

aiogram - ещё одна популярная асинхронная библиотека с FSM, middleware и удобным API.

from aiogram import Bot, Dispatcher, types
from aiogram.filters import CommandStart
from aiogram.types import Message
import asyncio

bot = Bot(token='TOKEN')
dp = Dispatcher()

@dp.message(CommandStart())
async def cmd_start(message: Message):
    await message.answer('Привет от aiogram!')

@dp.message()
async def echo(message: Message):
    await message.answer(message.text)

async def main():
    await dp.start_polling(bot)

if __name__ == '__main__':
    asyncio.run(main())

aiogram требует ручного управления лупой asyncio, но предоставляет более гибкие инструменты для сложных проектов.

Как обойтись без библиотек, используя requests?

Достаточно отправлять HTTP-запросы к API Telegram. Подходит для изучения протокола.

import requests

TOKEN = 'TOKEN'
BASE_URL = f'https://api.telegram.org/bot{TOKEN}'
def get_updates(offset=0):
    url = f'{BASE_URL}/getUpdates?offset={offset}&timeout=30'
    response = requests.get(url)
    return response.json()

def send_message(chat_id, text):
    url = f'{BASE_URL}/sendMessage'
    data = {'chat_id': chat_id, 'text': text}
    requests.post(url, data=data)

# Простейший цикл поллинга
last_update_id = 0
while True:
    updates = get_updates(offset=last_update_id+1)
    if updates['ok']:
        for upd in updates['result']:
            chat_id = upd['message']['chat']['id']
            text = upd['message'].get('text', '')
            send_message(chat_id, text)
            last_update_id = upd['update_id']

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

Как реализовать вебхуки вместо поллинга?

Для продакшена рекомендуется webhook; бот получает обновления через POST запросы на ваш сервер с HTTPS.

# Пример на Flask (требуется SSL сертификат)
from flask import Flask, request
import telegram

TOKEN = 'TOKEN'
bot = telegram.Bot(token=TOKEN)
app = Flask(__name__)

@app.route('/webhook', methods=['POST'])
def webhook():
    update = telegram.Update.de_json(request.get_json(force=True), bot)
    # обработка update
    if update.message and update.message.text:
        bot.send_message(chat_id=update.message.chat.id, text=update.message.text)
    return 'OK'

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8443, ssl_context=('cert.pem', 'key.pem'))

Установка webhook: https://api.telegram.org/botTOKEN/setWebhook?url=https://yourdomain.com/webhook

Типичная ошибка: недействительный SSL сертификат. Telegram принимает только доверенные сертификаты (Let's Encrypt).

Как организовать диалог с пользователем с сохранением состояний (FSM)?

Библиотека python-telegram-bot предоставляет ConversationHandler. Пример: опросника с шагами.

from telegram.ext import ConversationHandler, MessageHandler, filters

STATE_NAME, STATE_AGE = range(2)

async def start_poll(update, context):
    await update.message.reply_text('Как вас зовут?')
    return STATE_NAME

async def get_name(update, context):
    context.user_data['name'] = update.message.text
    await update.message.reply_text('Сколько вам лет?')
    return STATE_AGE

async def get_age(update, context):
    age = update.message.text
    await update.message.reply_text(f'Спасибо, {context.user_data["name"]}, вам {age} лет.')
    return ConversationHandler.END

async def cancel(update, context):
    await update.message.reply_text('Опрос отменён.')
    return ConversationHandler.END

conv_handler = ConversationHandler(
    entry_points=[CommandHandler('poll', start_poll)],
    states={
        STATE_NAME: [MessageHandler(filters.TEXT & ~filters.COMMAND, get_name)],
        STATE_AGE: [MessageHandler(filters.TEXT & ~filters.COMMAND, get_age)]
    },
    fallbacks=[CommandHandler('cancel', cancel)]
)

Как добавить базу данных SQLite для хранения пользователей?

Простой пример с sqlite3 и хендлером регистрации.

import sqlite3

conn = sqlite3.connect('users.db', check_same_thread=False)
cursor = conn.cursor()
cursor.execute('''CREATE TABLE IF NOT EXISTS users (
    user_id INTEGER PRIMARY KEY,
    username TEXT,
    registered_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)''')
conn.commit()

async def register(update, context):
    user = update.effective_user
    cursor.execute('INSERT OR IGNORE INTO users (user_id, username) VALUES (?, ?)',
                   (user.id, user.username))
    conn.commit()
    await update.message.reply_text('Вы зарегистрированы!')

application.add_handler(CommandHandler('register', register))

Ошибки: блокировка при многопоточности. Для продакшена используйте async базы данных (aiosqlite, asyncpg).

Как обрабатывать медиафайлы (фото, документы)?

from telegram import Update
from telegram.ext import MessageHandler, filters

async def handle_photo(update, context):
    file = await update.message.photo[-1].get_file()
    await file.download_to_drive('photo.jpg')
    await update.message.reply_text('Фото сохранено.')

application.add_handler(MessageHandler(filters.PHOTO, handle_photo))

Как реализовать Inline режим?

from telegram import InlineQueryResultArticle, InputTextMessageContent
from telegram.ext import InlineQueryHandler

async def inline_query(update, context):
    query = update.inline_query.query
    if not query:
        return
    results = [InlineQueryResultArticle(
        id='1',
        title='Результат',
        input_message_content=InputTextMessageContent(f'Вы искали: {query}')
    )]
    await update.inline_query.answer(results)

application.add_handler(InlineQueryHandler(inline_query))

Как интегрировать платёжи Telegram?

Необходимо настроить платежную систему (например, Stripe) через BotFather. Пример отправки счёта:

from telegram import LabeledPrice

async def buy(update, context):
    chat_id = update.message.chat_id
    title = 'Товар'
    description = 'Описание'
    payload = 'unique-payload'
    provider_token = 'PROVIDER_TOKEN'  # от BotFather
    currency = 'RUB'
    prices = [LabeledPrice('Цена', 100*100)]  # 100 RUB в копейках
    await context.bot.send_invoice(chat_id, title, description, payload,
                                    provider_token, currency, prices)

application.add_handler(CommandHandler('buy', buy))

Частая ошибка при платежах: неверный provider_token или не настроен BotFather.

Расширенные примеры разработки Telegram ботов

1. Полноценный бот с регистрацией, FSM, inline клавиатурой и базой данных (python-telegram-bot)

Пример
import logging
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import Application, CommandHandler, CallbackQueryHandler, ConversationHandler, MessageHandler, filters
import sqlite3

logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO)
logger = logging.getLogger(__name__)

conn = sqlite3.connect('users.db', check_same_thread=False)
cursor = conn.cursor()
cursor.execute('''CREATE TABLE IF NOT EXISTS users (
    user_id INTEGER PRIMARY KEY,
    username TEXT,
    bio TEXT,
    age INTEGER
)''')
conn.commit()

# Состояния
BIO, AGE = range(2)

async def start(update: Update, context):
    await update.message.reply_text(
        'Привет! Я бот для сбора информации.\n'
        'Используйте /register для регистрации.'
    )

async def register(update: Update, context):
    await update.message.reply_text('Напишите коротко о себе (биография):')
    return BIO

async def bio(update: Update, context):
    context.user_data['bio'] = update.message.text
    await update.message.reply_text('Укажите ваш возраст:')
    return AGE

async def age(update: Update, context):
    try:
        age = int(update.message.text)
    except ValueError:
        await update.message.reply_text('Пожалуйста, введите число.')
        return AGE
    user = update.effective_user
    cursor.execute('INSERT OR REPLACE INTO users (user_id, username, bio, age) VALUES (?, ?, ?, ?)',
                   (user.id, user.username, context.user_data['bio'], age))
    conn.commit()
    await update.message.reply_text('Регистрация завершена!')
    return ConversationHandler.END

async def cancel(update: Update, context):
    await update.message.reply_text('Регистрация отменена.')
    return ConversationHandler.END

async def show_profile(update: Update, context):
    user = update.effective_user
    cursor.execute('SELECT * FROM users WHERE user_id = ?', (user.id,))
    row = cursor.fetchone()
    if row:
        text = f"ID: {row[0]}\nНик: {row[1]}\nБио: {row[2]}\nВозраст: {row[3]}"
    else:
        text = 'Вы не зарегистрированы. Используйте /register.'
    await update.message.reply_text(text)

async def inline_menu(update: Update, context):
    keyboard = [
        [InlineKeyboardButton('Мой профиль', callback_data='profile'),
         InlineKeyboardButton('Помощь', callback_data='help')],
        [InlineKeyboardButton('О боте', callback_data='about')]
    ]
    reply_markup = InlineKeyboardMarkup(keyboard)
    await update.message.reply_text('Меню:', reply_markup=reply_markup)

async def button_handler(update: Update, context):
    query = update.callback_query
    await query.answer()
    if query.data == 'profile':
        user = query.from_user
        cursor.execute('SELECT * FROM users WHERE user_id = ?', (user.id,))
        row = cursor.fetchone()
        text = f"ID: {row[0]}\nНик: {row[1]}\nБио: {row[2]}\nВозраст: {row[3]}" if row else 'Не зарегистрирован.'
        await query.edit_message_text(text)
    elif query.data == 'help':
        await query.edit_message_text('Доступные команды: /start, /register, /profile, /menu')
    elif query.data == 'about':
        await query.edit_message_text('Бот для тестирования написан на python-telegram-bot.')

def main():
    application = Application.builder().token('TOKEN').build()
    application.add_handler(CommandHandler('start', start))
    application.add_handler(CommandHandler('menu', inline_menu))
    application.add_handler(CommandHandler('profile', show_profile))
    conv_handler = ConversationHandler(
        entry_points=[CommandHandler('register', register)],
        states={
            BIO: [MessageHandler(filters.TEXT & ~filters.COMMAND, bio)],
            AGE: [MessageHandler(filters.TEXT & ~filters.COMMAND, age)]
        },
        fallbacks=[CommandHandler('cancel', cancel)]
    )
    application.add_handler(conv_handler)
    application.add_handler(CallbackQueryHandler(button_handler))
    application.run_polling(allowed_updates=Update.ALL_TYPES)

if __name__ == '__main__':
    main()

Результат работы (логирование):

2024-01-01 12:00:00,000 - telegram.ext.Application - INFO - Application started
2024-01-01 12:00:05,123 - telegram.ext.Dispatcher - INFO - Received update 1 from user 123456
...

2. Асинхронный бот на aiogram с FSM и middleware для логирования

Пример
from aiogram import Bot, Dispatcher, types
from aiogram.filters import Command, CommandStart
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import Message
from aiogram import F
import asyncio

BOT_TOKEN = 'TOKEN'
bot = Bot(token=BOT_TOKEN)
dp = Dispatcher()

class Form(StatesGroup):
    name = State()
    age = State()

@dp.message(CommandStart())
async def cmd_start(message: Message):
    await message.answer('Привет! Введите /fill для заполнения анкеты.')

@dp.message(Command('fill'))
async def cmd_fill(message: Message, state: FSMContext):
    await state.set_state(Form.name)
    await message.answer('Как вас зовут?')

@dp.message(Form.name, F.text)
async def process_name(message: Message, state: FSMContext):
    await state.update_data(name=message.text)
    await state.set_state(Form.age)
    await message.answer('Сколько вам лет?')

@dp.message(Form.age, F.text)
async def process_age(message: Message, state: FSMContext):
    if not message.text.isdigit():
        await message.answer('Введите число.')
        return
    data = await state.get_data()
    name = data['name']
    age = message.text
    await message.answer(f'Спасибо, {name}, вам {age} лет.')
    await state.clear()

@dp.message()
async def handle_unknown(message: Message):
    await message.answer('Я не понимаю. Используйте /start или /fill.')

async def main():
    await dp.start_polling(bot)

if __name__ == '__main__':
    asyncio.run(main())

Результат: после команды /fill диалог в несколько шагов.

3. Вебхук на Flask с обработкой команд и отправкой сообщений

Пример
from flask import Flask, request, jsonify
import telegram
import logging

logging.basicConfig(level=logging.INFO)

TOKEN = 'TOKEN'
bot = telegram.Bot(token=TOKEN)
app = Flask(__name__)

@app.route('/webhook', methods=['POST'])
def webhook():
    update = telegram.Update.de_json(request.get_json(force=True), bot)
    # Обработка сообщений
    if update.message:
        chat_id = update.message.chat.id
        text = update.message.text
        if text == '/start':
            bot.send_message(chat_id=chat_id, text='Привет от Flask!')
        else:
            bot.send_message(chat_id=chat_id, text=f'Вы написали: {text}')
    return 'OK', 200

@app.route('/set_webhook', methods=['GET', 'POST'])
def set_webhook():
    url = 'https://yourdomain.com/webhook'
    s = bot.set_webhook(url)
    if s:
        return 'Webhook set!', 200
    else:
        return 'Error', 400

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8443, ssl_context=('cert.pem', 'key.pem'))

После запуска сервера вызвать /set_webhook или напрямую через API: https://api.telegram.org/botTOKEN/setWebhook?url=https://yourdomain.com/webhook. Убедитесь, что сертификат действителен.

4. Использование inline режима для поиска (например, погода)

Пример
import requests
from telegram import InlineQueryResultArticle, InputTextMessageContent
from telegram.ext import InlineQueryHandler

async def inline_weather(update, context):
    query = update.inline_query.query
    if not query:
        return
    # Простой запрос к API погоды (пример)
    api_key = 'YOUR_OPENWEATHER_API'
    url = f'http://api.openweathermap.org/data/2.5/weather?q={query}&appid={api_key}&units=metric'
    try:
        r = requests.get(url)
        data = r.json()
        if data['cod'] == 200:
            temp = data['main']['temp']
            desc = data['weather'][0]['description']
            text = f'Погода в {query}: {temp}°C, {desc}'
        else:
            text = 'Город не найден.'
    except:
        text = 'Ошибка получения данных.'
    results = [InlineQueryResultArticle(
        id='1',
        title=text,
        input_message_content=InputTextMessageContent(text)
    )]
    await update.inline_query.answer(results, cache_time=10)

application.add_handler(InlineQueryHandler(inline_weather))

Пользователь в любом чате вводит @bot_name <город> и получает погоду.

5. Отправка медиа и документов с обработкой ошибок

Пример
async def send_photo(update, context):
    try:
        with open('image.png', 'rb') as f:
            await update.message.reply_photo(f, caption='Пример фото')
    except FileNotFoundError:
        await update.message.reply_text('Файл не найден.')

async def receive_photo(update, context):
    # Сохраняем фото
    file = await update.message.photo[-1].get_file()
    await file.download_to_drive('received_photo.jpg')
    await update.message.reply_text(f'Фото сохранено как received_photo.jpg')

application.add_handler(CommandHandler('sendphoto', send_photo))
application.add_handler(MessageHandler(filters.PHOTO, receive_photo))

6. Платежи: тестовый счёт

Пример
from telegram import LabeledPrice, Update
from telegram.ext import PreCheckoutQueryHandler

async def start_payment(update, context):
    chat_id = update.effective_chat.id
    title = 'Подписка на месяц'
    description = 'Доступ к функциям на 30 дней'
    payload = 'sub-monthly'
    provider_token = '284685063:TEST:YOUR_TOKEN'  # от BotFather для тестов
    currency = 'RUB'
    prices = [LabeledPrice('Подписка', 14900)]  # 149 рублей
    await context.bot.send_invoice(
        chat_id, title, description, payload,
        provider_token, currency, prices,
        need_name=True,
        need_email=True,
        need_phone_number=False
    )

async def pre_checkout(update: Update, context):
    query = update.pre_checkout_query
    if query.invoice_payload != 'sub-monthly':
        await query.answer(ok=False, error_message='Неверный payload')
    else:
        await query.answer(ok=True)

async def successful_payment(update, context):
    await update.message.reply_text('Оплата прошла успешно! Спасибо.')

application.add_handler(CommandHandler('buy', start_payment))
application.add_handler(PreCheckoutQueryHandler(pre_checkout))
application.add_handler(MessageHandler(filters.SUCCESSFUL_PAYMENT, successful_payment))

Для тестов используйте токен из BotFather в разделе Payments.

Создание Telegram бота на Python - comments

En
бот python (python)