Итак, мы подошли к финальной части ботостроительной трилогии. Наш бот уже умеет смотреть RSS-ленту Мотора, подгружать свежие новости, слать сообщения администратору бота, а также реализована вся логика работы с модерацией сообщений перед отправкой в канал и работой с ChatGPT. Осталась одна проблема - обновление ленты происходит единожды, при запуске скрипта. Исправим эту оплошность.
Для тех кто пропустил: первая и вторая части.
Обновление новостей из бота
Обновление ленты происходит при вызове процедуры feed_reader()
. Сейчас она вызывается единожды - при запуске бота из процедуры main()
. Логично сделать функционал, который позволит администратору бота подгружать новости по команде. Проще всего это сделать по вводу текстовой команды через "/". Создадим еще один короткий хэндлер в файле handlers.py:
#подгружаем дополнительные модули из aiogram
from aiogram.types import CallbackQuery, Message
from aiogram.filters import Command
#импортируем процедуру feed_reader()
from bot import feed_reader
@router.message (Command(commands=['start', 'load']))
async def load_news(message: Message):
await feed_reader()
Теперь администратор сможет обновлять новостную ленту по командам /start (включая нажатие на кнопку "start" при очистке истории бота) и /load.
Работа по расписанию
Принудительное обновление новостной ленты - удобно, но хочется чего-то большего. Например, вызывать процедуру feed_reader()
по расписанию. Сделать это, как оказалось, очень просто.
Для реализации задуманного воспользуемся библиотекой Apscheduler. Информацию о работе с ней на английском можно прочитать тут. Эта библиотека имеет много разных режимов, но в целом она позволяет запускать процедуры по расписанию.
Установим библиотеку в виртуальное окружение pip install apscheduler
.
Доработаем процедуру main()
в bot.py:
#импортируем модуль AsyncIOScheduler из apscheduler
from apscheduler.schedulers.asyncio import AsyncIOScheduler
async def main():
#Не забываем про диспетчера и удаляем webhook на всякий случай
dp = Dispatcher()
dp.include_router(handlers.router)
await bot.delete_webhook(drop_pending_updates=True)
#создаем БД, есои ее нет
await create_table()
#вызываем чтение ленты
await feed_reader()
#создаем экземпляр класса AsyncIOScheduler
scheduler = AsyncIOScheduler()
#создаем задачу в режиме 'cron' которая будет запускать процедуру feed_reader() в 10:30 и 17:30
scheduler.add_job(feed_reader, 'cron', hour='10,17', minute='30')
#стартуем работу
scheduler.start()
#пуллим бот - чтобы наш скрипт постоянно запрашивал сервер ТГ о состоянии нашего бота
await dp.start_polling(bot)
Все достаточно просто и реализуется в 3 строки кода. Создаем экземпляр класса AsyncIOScheduler - scheduler, устанавливаем job в режиме 'cron' на запуск процедуры feed_reader()
по расписанию. В примере я сделал два запуска в 10:30 и в 17:30. Можно сделать другие интервалы, запускать чаще чем два раза в день - все как вам удобно.
Поздравляю, разработка бота полностью завершена. Весь код проекта можно взять в Git.
А дальше?
Итак, на текущий момент бот полностью разработан и даже как-то работает на компьютере. Но оставлять его "жить" у себя на локальной машине не самая разумная мысль - "домашний" компьютер засыпает, зависает, перезагружается при обновлениях и вообще редко работает 24 часа в сутки. А если ноутбук? Можно, конечно, собрать и сконфигурировать небольшой компьютер и засунуть его куда-то на антресоль гудеть. Но, согласитесь, это очень радикально.
Самое разумное решение арендовать небольшой виртуальный (VDS) сервер и поселить туда нашего бота. В сети вы сможете найти огромное количество предложений, я не ставлю цель кого-то рекламировать и рекомендовать. Могу сказать, что я арендовал под свой проект VDS со следующими характеристиками: Cores 1 / RAM 0.5 Gb / Disk 5 Gb на Debian 11 64 bit. Это было минимальное предложение. Все это в месяц стоит примерно как чашка капуччино.
Ок, робота написали, VDS сервер арендовали. Осталось переселить нашего подопечного жить и работать на ново место.
Запуск бота на сервере
Самое простое решение - скопировать все файлы бота на сервер и потом настроить его запуск в фоновом режиме.
Во-первых необходимо освоить базовые команды работы с Linux. Если раньше вы не работали с командной строкой - будет легкий шок. Почитайте, например, это.
Во-вторых, перед загрузкой файлов на сервер, крайне рекомендую обновить файл requirements.txt командой pip freeze > requirements.txt
из терминала VSCode на вашем компьютере. Необходимо перенести в него все зависимости, включая зависимости библиотек которые мы подключили.
После загрузки всех файлов на сервер необходимо зайти в папку с файлами бота, где должен лежать, в том числе, наш файл requirements.txt и установить все зависимости из него командой pip install -r requirements.txt
Дальше необходимо настроить запуск нашего бота в фоновом режиме. Очень подробную инструкцию о том, как это сделать читайте тут.
Кажется, что это наиболее простой способ. Однако он таит в себе много проблем с отладкой. Например, я столкнулся с тем, что бот просто не запускался - были конфликты с зависимостями. Пробовал переставлять, перегружать, обновлять. В итоге мне эти пляски с бубном в командной строке надоели и я решил освоить Docker.
Docker или дивный мир DevOps
Если очень поверхностно, то Docker это система, которая позволяет разворачивать свои приложения/сервисы внутри замкнутой среды - контейнера. Контейнер представляет собой изолированную среду (по сути свой "виртуальный сервер") с инстансом ОС, обычно какой-то сборки Linux, в который устанавливается нужное окружение и ваша программа. Контейнеры могут взаимодействовать с внешним миром и друг с другом. Развернув и отладив свое приложения в контейнере (или в группе контейнеров), вы можете легко перенести его вместе с контейнером на другую машину где оно будет работать также. Тема очень сложная, интересная. Советую почитать/посмотреть больше материала про контейнеры и Docker, например, тут.
Далее я опишу как поместить нашего бота в контейнер Docker и как его развернуть на сервере. Понимаю, что описанный ниже алгоритм не самый оптимальный и можно сделать лучше, если глубже изучить Docker. Возможно будет путаница в терминологии.
Первое, что необходимо сделать, это установить на ваш компьютер приложение Docker c официального сайта под вашу ОС. Также необходимо создать аккаунт и зайти через него в учетную запись в приложении. Уже можно ставить разные готовые образы из Docker-Hub и играть с ними.
После установки приложения переходим в VSCode с проектом и устанавливаем расширение Docker от Microsoft. После его установки создаем в корне нашего проекта файл с именем "Dockerfile" без расширения. Если до этого все было сделано корректно, то VSCode сразу присвоит файлу лого Docker'a. В этом файле мы напишем скрипт, который соберет Image нашего контейнера.
Логика создания Image следующая: выбираем один из стандартных образов из Docker-Hub, доустанавливаем в него нужные нам пакеты, загружаем файлы нашей программы, и говорим, какой файл нужно запускать при запуске контейнера. Все это нужно отразить в файле Dockerfile.
Для нашего проекта я выбрал минималистичный образ alpine. Описание на Docker-Hub его звучит как:
A minimal Docker image based on Alpine Linux with a complete package index and only 5 MB in size!
Прочитав такое, я решил, что смогу запихнуть своего бота в файл до 10 mb и все будет супер легко и быстро перенесено на мой хилый VDS. Но дальше все пошло не по плану. Во-первых, в alpine не идет утилита pip, при помощи который мы привычно устанавливаем библиотеки в python, а во-вторых в ней невозможно установить тайм-зону, что для нашего проекта очень важно, поскольку у нас есть события, которые работают по расписанию. Все это можно исправить "доустановкой" нужного, но Image раздувается до 160+ mb:))
В итоге, для нашего проекта, скрипт в Dockerfile должен выглядеть следующим образом:
#Базовая версия инстанса ОС
FROM alpine:3.18.4
# Установка таймзоны
RUN apk add -U tzdata
ENV TZ=Europe/Moscow
RUN cp /usr/share/zoneinfo/$TZ /etc/localtime
# Создаем внутри образа папку под нашего бота
WORKDIR /motor
# Копируем файл с окружением
COPY requirements.txt .
# Устанавливаем утилиту pip и после запускаем установку окружения в образ
RUN apk add --update py-pip
RUN pip install -r requirements.txt
# Копируем файлы бота
COPY entrails/ entrails/
COPY bot.py .
COPY gptai.py .
COPY parse.py .
COPY sql.py .
COPY variables.py .
#Говорим, какой файл необходимо запустить
CMD ["python", "bot.py"]
Теперь в терминале VSCode необходимо ввести следующую команду (при этом приложение Docker должно быть запущено, а файл Dockerfile сохранен):
docker build -t motor .
Создание образа займет несколько минут. Если все завершилось удачно, то по команде docker images
вы увидите созданный Image:
Настало время потестировать то, что мы собрали. Для этого переходим в приложение Docker и заходим в раздел Images. Там уже должен быть наш Image с именем motor. Нажимаем на кнопку Run в группе Actions, подтверждаем еще раз запуск. После этого Docker создает тот-самый контейнер и запускает через Python скрипт bot.py. У вас откроется окно с контейнером и на вкладке Logs вы увидите все, что происходит с приложением - если все нормально, то там должно быть пусто. Можно пользоваться ботом - посылать команды и все такое. Вверху логичные кнопки управления - остановка, запуск.
Будем считать, что все у вас заработало нормально и image можно переносить на сервер.
В терминале VSCode вводим команду docker save motor > motor.tar
После выполнения команды, в корне проекта должен появится файл motor.tar. Его можно перенести на VDS-сервер.
Отдельная история установки Docker на VDS-сервере. У некоторых провайдеров он уже стоит, тогда вы избавляетесь от этой проблемы. Если нет, то советую прочитать эту статью.
После установки Docker на VDS сервер, копируем туда наш motor.tar. В терминале переходим в каталог с файлом и выполняем команду: docker load -i motor.tar
Проверить загрузку образа можно командой docker images
Осталось запустить контейнер из образа в фоновом режиме. Для этого выполняем команду docker run -d motor
Посмотреть, что контейнер создан и запущен можно по команде docker ps -a
Заключение
Целью трилогии было показать, как имея совсем скромные знания в программировании сделать что-то рабочее и действительно полезное, что можно использовать каждый день. Затрагиваются все аспекты жизненного цикла ПО (SDLC) - от идеи до деплоя на сервере, что на мой взгляд, дает целостную картину начинающему, представление о жизненном цикле ПО.
Понятно, что рассматривать этот проект как "законченное" решение не стоит. В целом, это некая база, которую можно и нужно допиливать дальше: можно добавить картинку из новости в сообщение, сделать сбор новостей с нескольких сайтов (агрегатов новостей), усложнить логику модерации.