Читаем статьи с Хабра с помощью Slack-бота

image-loader.svg

Привет, Хабр! Сегодня расскажу, как на хакатоне для студентов SkillFactory я сделал Slack-бота, который оповещает студентов разных курсов о выходе статей на Хабре по интересующей их тематике. На КДПВ вы видите тестирование внедрённого бота; ссылку на его код вы найдёте в конце статьи.

Раньше наш редактор ежедневно заходил на Хабр, просматривал только что вышедшие статьи и вручную оповещал сотрудников SF о всех новых публикациях в специальном канале в Slack. Редактору это надоело, и мы придумали, как упростить ему жизнь и автоматизировать процесс.

Чтобы не утомлять, рассказываю о самых основных моментах, которые касаются объединения трёх основных частей проекта: Django, Celery и парсера Slack-бота. Опущены такие моменты, как получение токена Slack-бота на сайте, построение шаблона и заполнение URL проекта. Об этом уже есть много статей, а строение этого проекта ничем не отличается от большинства других. Я выполнял проект на Windows WSL 2, поэтому указаны команды для Linux. При этом время на выполнение проекта было ограничено, поэтому я старался упрощать все задачи. Поехали!

Вначале создадим виртуальное окружение и проект Django:

python3 -m venv venv
source venv/bin/activate
# В репозитории есть requirements.txt для установки нужных версий библиотек
pip install django
django-admin startproject slackbot

В корневом каталоге проекта создаём новое приложение:

cd slackbot
python3 manage.py startapp interface

Не забываем добавить interface в settings.py и в INSTALLED_APPS.

Теперь продумаем модели нашего проекта. Напоминаю: программа должна парсить Хабр и отправлять новые статьи в каналы Slack, делать это будет Slack-бот, то есть потребуются две модели — модель бота и модель статьи. Построим модель бота:

image-loader.svg

  • name — имя бота;

  • token — токен бота;

  • channel — имя канала, куда отправляются сообщения;

  • task — по условиям есть 2 задачи. Чтобы не писать код автовыбора задачи, я решил прикреплять бота к определённой задаче в момент его создания;

  • editor_text — текст перед ссылкой на статью;

  • bot_tags — здесь пропишите теги, по которым бот будет искать статьи.

Теперь построим модель статей, чтобы хранить в базе данных то, что распарсил бот:

image-loader.svg

  • headline — заголовок статьи;

  • public_time — время публикации статьи;

  • link — ссылка на статью;

  • status — это поле-флаг определяет, в каком состоянии находится статья. Если она уже была отправлена ботом на канал Slack, статус нужно изменить на sended;

  • tags — теги статьи;

  • task — дополнительное поле принадлежности к статье на всякий случай.

Также я решил создать модель конфигурации бота. По условиям в задаче у пользователя должна быть возможность конфигурировать ботов отдельно по времени отправки сообщения и на задержку парсинга.

image-loader.svg

  • task — у нас 2 задачи, поэтому нужно выбирать из них;

  • task_id — нужен для управления ботом. Бот будет запускаться асинхронно из под Celery, поэтому, чтобы запускать и останавливать конкретного бота, лучше сразу сохранить идентификатор его задачи;

  • mode — выбор режимов отправки нового контента в каналы Slack;

  • minute — если пользователь выбрал отправку каждый час, можно задать минуту часа;

  • hour — если раз в день, можно задать час отправки;

  • day — при отправке раз в неделю можно задать день недели отправки.

Модели проекта готовы, пора применить миграции, создать суперпользователя и приступить к работе над интерфейсом для пользователя.

python3 manage.py makemigrations
python3 manage.py migrate
python3 manage.py createsuperuser
python3 manage.py runserver

Самый быстрый способ сделать шаблон для проекта — взять готовый и доработать его. Я выбрал этот шаблон.

Создаём папку static/interface и кладём туда скачанный шаблон. Все шаблоны будут наследоваться от этого шаблона. В нём есть готовая шапка, подвал, некоторые элементы js; остаётся вырезать лишнее и подправить section. Вьюха получилась довольно длинной, поэтому опишу только интересные моменты.

image-loader.svg

Это вьюха главной страницы, где можно создать и просмотреть список ботов, поэтому она одновременно наследуется от CreateView и ListView. Форма стандартная, а в конце вы найдёте ссылку на репозиторий. Функциональная часть страницы выглядит так:

image-loader.svg

Здесь можно зарегистрировать, запустить, остановить Slack-бота, а также сконфигурировать задачи и просмотреть список зарегистрированных ботов. Интерфейс между пользователем и ботом готов. Следующий этап — реализация бота.

Перед тем как писать код парсера, нужно прояснить одну вещь. Задача Django — принимать команды пользователя и передавать их боту, а бот должен работать сам по себе, не отвлекая Django от его задач; самым верным решением в этой ситуации будет асинхронность. Поэтому вначале нужно подключить к проекту Celery, а логику бота реализовывать как задачи Celery. Для работы Celery необходим Redis, запускайте его в отдельном окне терминала.

sudo apt-get update
sudo apt-get install redis
redis-server

После необходимо установить celery и redis в окружение Django:

pip install celery
pip install redis

Далее мы должны добавить некоторые настройки в конфигурацию проекта — settings.py, дописав следующие строки:

CELERY_BROKER_URL = 'redis://localhost:6379'
CELERY_RESULT_BACKEND = 'redis://localhost:6379'
CELERY_ACCEPT_CONTENT = ['application/json']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'

Затем согласно документации Celery переходим в директорию проекта и рядом с settings.py добавляем файл celery.py:

import os
from celery import Celery

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mcdonalds.settings')
app = Celery('mcdonalds')
app.config_from_object('django.conf:settings', namespace = 'CELERY')
app.autodiscover_tasks()

Также по рекомендациям в документации Celery мы должны добавить следующие строки в файл init.py в той же папке, что и settings.py:

from .celery import app as celery_app
all = ('celery_app',)

Задачи Celery запускаются в отдельном окне из той же директории, что и runserver. Запускаем:

celery -A slackbot worker -l INFO

image-loader.svg

Готово! Celery настроен и может принимать задачи.

Наконец, переходим к самому интересному — разработке функций бота! Базовая структурная единица Celery — задача (task). По условиям проекта задачи две:

  1. Парсить все статьи в блоге компании и оповещать о выходе новых статей через Slack.

  2. Парсить все статьи в блоге компании, но оповещать только о статьях с определёнными тегами.

Значит у нас будут задачи parse и parse2. Создаём файл task.py в каталоге приложения interface. Чтобы зарегистрировать функцию как задачу Сelery, достаточно импортировать декоратор from celery import shared_task и задекорировать функцию. Я создал новый файл tasks_extension.py и вынес туда всю логику бота. В конце останется лишь импортировать основные функции триггеры для запуска задачи в tasks.py.

В tasks_extension.py пишем алгоритм работы парсера. Для этого нужны две библиотеки:

pip install requests
pip install beautifulsoup4

Берём html-код страницы Хабра.

image-loader.svg

Функция print здесь используется в качестве простой реализации логирования.

@onceEveryXSeconds(delay) — это декоратор задержки, чтобы не собирать HTML Хабра каждую секунду.

image-loader.svg

Находим на странице статьи и записываем их в переменную items:

image-loader.svg

Метод find_all библиотеки beautifulsoup4 вернёт список, итерируя который мы заполним модель статей в базе данных. Итак, у нас есть логика парсера, а собранные данные лежат в базе, остаётся написать конфигурацию для рассылки статей по каналам.

image-loader.svg

Функция принимает на вход один из режимов работы и отправляет рассылку согласно настроенному времени. Интерфейс настроек выглядит так:

image-loader.svg

И функция отправки сообщений:

image-loader.svg

Теперь импортируем необходимые функции в файл tasks.py и напишем основную задачу для Celery:

image-loader.svg

Сама задача будет запускаться кнопкой на скрине выше; напишем функцию запуска бота:

image-loader.svg

Главное — записать id задачи, чтобы в дальнейшем остановить её. Функция остановки выглядит так:

image-loader.svg

Чтобы задача останавливалась корректно, её нужно декорировать в task.py:

@app.task(bind=True, base=AbortableTask)

Cоздаём нового бота:

image-loader.svg

И запускаем задачу в консоли Сelery:

image-loader.svgimage-loader.svg

Готово! Бот работает:

image-loader.svg

Репозиторий проекта.

В пандемию, когда контакты с людьми сводятся к минимуму, боты незаменимы — и если вам интересна их разработка, то вы можете обратить внимание на наш курс о Fullstack-разработке на Python, а если хочется пойти дальше и создавать программы с ИИ, вы можете присмотреться к нашему курсу«Machine Learning и Deep Learning», который полностью подготовит вас к началу карьеры в области ИИ с нуля или прокачает ваши навыки. Также вы можете узнать, как начать карьеру или продолжить развитие в других направлениях:

image-loader.svg

Python, веб-разработка

Data Science и Machine Learning

Мобильная разработка

Java и C#

От основ — в глубину

А также:

© Habrahabr.ru