Практическое руководство по созданию ботов с помощью Python
Введение в создание скриптов ботов на Python
Боты на Python используются для автоматизации общения, сбора данных, управления устройствами. Самое популярное направление – Telegram боты. В статье рассмотрены различные подходы: от простого эхо-бота до продвинутого с машиной состояний и базой данных. Основной упор сделан на библиотеку aiogram 3.x как наиболее эффективное решение для асинхронной работы.
Основное решение: асинхронный бот на aiogram 3 с машиной состояний
Библиотека aiogram 3 позволяет создавать быстрые и масштабируемые боты. Используется асинхронный подход, что важно при большом количестве пользователей. Рассмотрим бота, который собирает имя и возраст пользователя через состояния (FSM).
# bot.py
import asyncio
from aiogram import Bot, Dispatcher, types
from aiogram.filters import Command
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import ReplyKeyboardMarkup, KeyboardButton
BOT_TOKEN = 'ВАШ_ТОКЕН'
class Form(StatesGroup):
name = State()
age = State()
bot = Bot(token=BOT_TOKEN)
dp = Dispatcher()
@dp.message(Command("start"))
async def cmd_start(message: types.Message, state: FSMContext):
await message.answer("Привет! Как вас зовут?")
await state.set_state(Form.name)
@dp.message(Form.name)
async def process_name(message: types.Message, state: FSMContext):
await state.update_data(name=message.text)
await message.answer("Сколько вам лет?")
await state.set_state(Form.age)
@dp.message(Form.age)
async def process_age(message: types.Message, state: FSMContext):
await state.update_data(age=message.text)
data = await state.get_data()
await message.answer(f"Спасибо, {data['name']}! Вам {data['age']} лет.")
await state.clear()
async def main():
await dp.start_polling(bot)
if __name__ == '__main__':
asyncio.run(main())
библиотека бота python (библиотека для создания ботов на python)
Пояснения:
- Form – класс состояний. Наследуется от StatesGroup.
- state.set_state() – переводит пользователя в нужное состояние.
- state.update_data() – сохраняет данные.
- state.get_data() – извлекает все данные.
- state.clear() – сбрасывает состояние.
Такой подход удобен для анкетирования, регистрации, многошаговых действий.
Типичные ошибки и их решения:
- Ошибка: RuntimeError: Timeout context manager should be used... – возникает при попытке использовать asyncio.run() внутри другого цикла. Решение: вызывать asyncio.run() только в главном блоке.
- Ошибка: Bot token неверный – проверьте BOT_TOKEN, убедитесь, что нет лишних пробелов.
- Ошибка: Conflict: can't use update.from_user – если бот не видит пользователя. Убедитесь, что бот запущен и пользователь отправил команду.
Как сделать простого эхо-бота на python-telegram-bot (синхронный вариант)?
Этот вариант подходит для быстрого старта, когда асинхронность не обязательна. Библиотека python-telegram-bot (версия 13.x) использует синхронные обработчики.
from telegram.ext import Updater, CommandHandler, MessageHandler, Filters
def start(update, context):
update.message.reply_text('Привет! Я эхо-бот. Отправь мне что-нибудь.')
def echo(update, context):
update.message.reply_text(update.message.text)
def main():
updater = Updater('ВАШ_ТОКЕН', use_context=True)
dp = updater.dispatcher
dp.add_handler(CommandHandler('start', start))
dp.add_handler(MessageHandler(Filters.text & ~Filters.command, echo))
updater.start_polling()
updater.idle()
if __name__ == '__main__':
main()
Python discord py (создание discord бота на python)
Пояснения:
- Updater – получает обновления от Telegram.
- CommandHandler – обрабатывает команды (начинающиеся с /).
- MessageHandler – обрабатывает обычные текстовые сообщения.
- Фильтр Filters.text – только текст, & ~Filters.command – исключает команды.
Проблемы:
- Устаревшая версия: python-telegram-bot 13.x не поддерживает asyncio. Для новых проектов лучше использовать версию 20+ (async).
- Ошибка при запуске: telegram.error.Conflict – если бот уже запущен в другом процессе. Остановите другой экземпляр.
Как добавить инлайн-клавиатуру и callback-обработчики?
Инлайн-кнопки позволяют пользователю выбирать действия, не покидая чат. Рассмотрим пример на aiogram.
from aiogram import types
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
from aiogram.filters import Command
@dp.message(Command("inline"))
async def show_inline(message: types.Message):
keyboard = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="Кнопка 1", callback_data="btn1")],
[InlineKeyboardButton(text="Кнопка 2", callback_data="btn2")]
])
await message.answer("Выберите кнопку:", reply_markup=keyboard)
@dp.callback_query(lambda c: c.data in ["btn1", "btn2"])
async def process_callback(callback_query: types.CallbackQuery):
await callback_query.answer()
if callback_query.data == "btn1":
await callback_query.message.answer("Вы нажали кнопку 1")
else:
await callback_query.message.answer("Вы нажали кнопку 2")
Python vk py (использование vk api с python)
Для чего: удобные меню, опросы, навигация.
Частая ошибка:
- Callback-запрос не обрабатывается – убедитесь, что зарегистрирован хэндлер callback_query. Проверьте, что callback_data совпадает.
- Кнопки не отображаются – возможно, превышен лимит количества кнопок (максимум 100).
Как подключить базу данных SQLite для хранения пользователей?
Хранение данных необходимо для авторизации, статистики. Используем aiosqlite для асинхронной работы.
import aiosqlite
DB_PATH = "bot.db"
async def init_db():
async with aiosqlite.connect(DB_PATH) as db:
await db.execute('''CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
username TEXT,
registered_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)''')
await db.commit()
async def add_user(user_id: int, username: str):
async with aiosqlite.connect(DB_PATH) as db:
await db.execute('INSERT OR IGNORE INTO users (id, username) VALUES (?, ?)', (user_id, username))
await db.commit()
# В хэндлере:
# await init_db() при старте
# await add_user(message.from_user.id, message.from_user.username)
бот python скрипта (скрипт бота на python)
Цель: постоянное хранение данных даже после перезапуска бота.
Проблемы:
- Ошибка блокировки базы при одновременных запросах – используйте aiosqlite с одним соединением. Лучше создать пул соединений (например, через асинхронный контекстный менеджер).
- Не забывайте коммитить изменения после INSERT/UPDATE.
Как настроить webhook для продакшн-бота?
Вместо поллинга (постоянного опроса) используем webhook – Telegram сам отправляет обновления на ваш сервер. Это быстрее и надежнее.
from aiogram import Bot, Dispatcher, types
from aiogram.webhook.aiohttp_server import SimpleRequestHandler, setup_application
from aiohttp import web
BOT_TOKEN = '...'
bot = Bot(token=BOT_TOKEN)
dp = Dispatcher()
# ... все хэндлеры ...
app = web.Application()
webhook_requests_handler = SimpleRequestHandler(
dispatcher=dp,
bot=bot,
)
webhook_requests_handler.register(app, path='/webhook')
setup_application(app, dp, bot=bot)
if __name__ == '__main__':
web.run_app(app, host='0.0.0.0', port=8080)
Настройка на сервере: установите публичный URL, укажите вебхук:
await bot.set_webhook('https://ваш-домен.com/webhook')
Это решение подходит для постоянной работы бота на VPS или хостинге.
Проблемы:
- SSL сертификат – Telegram требует HTTPS. Используйте Let's Encrypt, nginx как reverse proxy.
- Неверный путь – проверьте, что webhook путь совпадает с тем, что вы указали в set_webhook.
Расширенные примеры и сценарии
Пример 1: Бот с регистрацией, логированием и рассылкой
Этот бот сохраняет пользователей в SQLite, логирует все входящие сообщения и умеет отправлять сообщения всем зарегистрированным пользователям по команде администратора.
import asyncio
import aiosqlite
from aiogram import Bot, Dispatcher, types
from aiogram.filters import Command
from aiogram.types import Message
import logging
logging.basicConfig(level=logging.INFO)
BOT_TOKEN = 'ВАШ_ТОКЕН'
ADMIN_ID = 123456789 # Ваш Telegram ID
bot = Bot(token=BOT_TOKEN)
dp = Dispatcher()
DB = "bot.db"
async def init_db():
async with aiosqlite.connect(DB) as db:
await db.execute('''CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
username TEXT,
first_name TEXT,
registered_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)''')
await db.commit()
async def add_user(user_id: int, username: str, first_name: str):
async with aiosqlite.connect(DB) as db:
await db.execute('INSERT OR IGNORE INTO users (id, username, first_name) VALUES (?, ?, ?)',
(user_id, username, first_name))
await db.commit()
async def get_all_users():
async with aiosqlite.connect(DB) as db:
cursor = await db.execute('SELECT id FROM users')
rows = await cursor.fetchall()
return [row[0] for row in rows]
@dp.message(Command("start"))
async def cmd_start(message: Message):
await add_user(message.from_user.id, message.from_user.username, message.from_user.first_name)
await message.answer("Вы зарегистрированы! Используйте /help для списка команд.")
@dp.message(Command("broadcast"))
async def cmd_broadcast(message: Message):
if message.from_user.id != ADMIN_ID:
await message.answer("Нет прав.")
return
# Отправляем сообщение всем пользователям
text = "Внимание! Бот обновлён."
users = await get_all_users()
success = 0
for uid in users:
try:
await bot.send_message(uid, text)
success += 1
except Exception as e:
logging.error(f"Не удалось отправить {uid}: {e}")
await message.answer(f"Рассылка завершена. Отправлено {success} из {len(users)}.")
@dp.message()
async def log_message(message: Message):
# Логируем все сообщения
logging.info(f"{message.from_user.id}: {message.text}")
async def main():
await init_db()
await dp.start_polling(bot)
if __name__ == '__main__':
asyncio.run(main())
Результат работы (пример логов в консоли):
INFO:root:123456789: Привет INFO:root:987654321: /start INFO:root:123456789: /broadcast
Такой бот подходит для оповещений, новостных рассылок.
Пример 2: Бот с планировщиком задач (apscheduler)
Позволяет выполнять периодические действия, например, публикацию прогноза погоды каждое утро.
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from aiogram import Bot
import asyncio
BOT_TOKEN = '...'
CHAT_ID = -1001234567890 # ID группы
bot = Bot(token=BOT_TOKEN)
scheduler = AsyncIOScheduler()
async def morning_message():
await bot.send_message(CHAT_ID, "Доброе утро! Сегодня хороший день.")
async def main():
scheduler.add_job(morning_message, 'cron', hour=7, minute=0)
scheduler.start()
# keep running
while True:
await asyncio.sleep(1)
if __name__ == '__main__':
asyncio.run(main())
Результат: каждое утро в 7:00 бот отправляет сообщение в указанный чат.
Пример 3: Использование Middleware для проверки подписки на канал
Middleware позволяет выполнять код до или после каждого хэндлера. Проверим, подписан ли пользователь на канал.
from aiogram import BaseMiddleware
from aiogram.types import Message
from typing import Callable, Dict, Any, Awaitable
class SubscriptionMiddleware(BaseMiddleware):
async def __call__(
self,
handler: Callable[[Message, Dict[str, Any]], Awaitable[Any]],
event: Message,
data: Dict[str, Any]
) -> Any:
chat_member = await event.bot.get_chat_member(chat_id='@my_channel', user_id=event.from_user.id)
if chat_member.status in ('member', 'administrator', 'creator'):
return await handler(event, data)
else:
await event.answer("Подпишитесь на канал @my_channel, чтобы пользоваться ботом.")
# При подключении:
dp.message.middleware(SubscriptionMiddleware())
Такой подход универсален: можно проверять доступ по ролям, блокировать спам.
Пример 4: Обработка изображений и файлов
Бот, который принимает фото, сохраняет его на диск и отправляет обратно с наложенным текстом.
from aiogram import types
from aiogram.filters import Command
from PIL import Image, ImageDraw, ImageFont
import io
@dp.message(Command("photo"))
async def cmd_photo(message: types.Message):
await message.answer("Отправьте мне изображение.")
@dp.message(lambda msg: msg.photo)
async def handle_photo(message: types.Message):
photo = message.photo[-1] # самая большая версия
file = await bot.get_file(photo.file_id)
image_data = await bot.download_file(file.file_path)
# Открываем изображение через PIL
image = Image.open(io.BytesIO(image_data.read()))
draw = ImageDraw.Draw(image)
font = ImageFont.load_default()
draw.text((10, 10), "Ваше фото", fill='red', font=font)
# Сохраняем в буфер
buf = io.BytesIO()
image.save(buf, format='JPEG')
buf.seek(0)
# Отправляем обратно
await message.reply_photo(photo=buf, caption="Вот ваше фото с подписью!")
Результат: пользователь отправляет фото, бот добавляет красную надпись и возвращает изменённое изображение.