Зачем ещё одна статья про создание чат-бота?
Возможно я плохо искал, но я не смог найти подробного руководства по созданию бота на python с применением фреймворка Django и подхода webhook, работающего на хостинге от российской компании. В большинстве материалов говориться о применении фреймворка Flask и использования бесплатных хостингов Heroku и PythonAnywhere. Опыт сообщества Хабр меня выручает, поэтому я решил в знак благодарности потратить время на написание данной статьи. Опишу полученный практический опыт, чтобы дать возможность всем кто в этом заинтересован сэкономить время и лучше понять как сделать бота на Python с применением фреймворка Django на своём хостинге, используя подход webhook.
Зачем платный хостинг?
На мой взгляд жизнеспособный вариант бота — это когда он не зависит от вашего локального компьютера и доступен 24/7. Для этого нужен хостинг, на котором есть: веб-сервер, система управления базами данных (для развития возможностей бота), регистрация доменного имени, получение для него SSL-сертификата и техническая поддержка всего этого хозяйства. Такие услуги стоят денег. Я плачу хостингу 138 рублей в месяц за поддержание инфраструктуры для работоспособности бота: поддержка Python + Django, СУБД MySQL 25 Гб, поддержка SSH.
В большинстве уроков я видел что используется персональный компьютер в качестве сервера или бесплатный хостинг с ограничениями по времени работы и т. п. В примерах бот с периодичностью опрашивает сервер мессенджера на предмет появления новых сообщений от пользователей. Это лишняя нагрузка на серверы мессенджера, поэтому бот могут «забанить» на какое-то время. Всё это по моему мнению не жизненно для продуктивного использования. Но для теста и обучения вполне возможно.
Что такое webhook и зачем он?
Для продуктива правильным решением считаю использование webhook’а, т. е. подхода, при котором наш бот ожидает сообщений от сервера мессенжера, а не «долбит» его периодическим запросами: есть новые сообщения или нет. С webhook’ом будет так: написал пользователь сообщение, сервер мессенджера отправил его вашему боту, он сообщение принял, обработал и ответил.
Почему Django?
Я решил делать бота на python, поэтому подключил на хостинге поддержку python. Но выбора фреймворка у меня не было — хостинг располагает только Django. Говорят на нём работает Instagram, Pinterest, Bitbucket и Mozilla. Возможно именно поэтому хостинг предлагает именно его.
Почему ВКонтакте, а не Telegram или Viber?
Чтобы двигаться от простого к сложному для меня было важно найти самый простой и понятный способ настроить webhook. ВКонтакте оказался для меня наиболее понятным ввиду чёткой справке и простоты подключения webhook’a в панели управления сообществом в разделе «Управление — Работа с API». Описание того как я всё настраивал и подключал будет дальше. В перспективе хочу сделать доступным своего бота в Viber. А с Telegram пока не по пути, т. к. мой хостинг в России, а из России Telegram заблокирован. Чтобы не было проблем с Telegram можно купить хостинг заграницей.
Как установить webhook для бота ВКонтакте?
Доменное имя https://. Для начала нужно зарегистрировать доменное имя сайта и получить для него ssl-сертификат.
Я не хотел использовать для чат-бота корневой домен, поэтому после регистрации домена, я сделал поддомен и получил для него ssl-сертификат.
Все эти манипуляции я сделал на сайте хостинга в своём личном кабинете.
В итоге я получил адрес сайта mybot.mysite.ru и ssl-сертификат к нему.
Получаем для бота ключ доступа (токен) ВКонтакте. Сперва создал закрытую группу, далее зашёл в «управление» группой, в раздел «Работа с API». Во вкладке «Ключи доступа» находится токен, а во вкладке «Callback API» настройки webhook’a.
Установка и настройка Django. Возможно для запуска вашего скрипта на python Django не нужен, но я не знаю как можно по другому.
Используя PuTTY я подключился к серверу по SSH, настроил и активировал виртуальное окружение.
SSH:
virtualenv-2.7 virtualenv/myEnv
. virtualenv/myEnv/bin/activate
команда в первой строке создаёт виртуальное окружение, а команда во второй — его активирует (обратите внимание на пробел после точки). Версия 2.7 продиктована хостингом и может отличаться в вашем случае. Поэтому читайте справку хостинга.
Далее установил Django
SSH:
pip install ‘django<2’
установил Джанго версии не ранее второй, т. к. на хостинге используется python 2.7, а с ним работает только Django версии меньше 2.
И установил python-модуль для работы с API ВКонтакте
SSH:
pip install vk
FTP:
Создал папку для django-проектов в корневой директории на хостинге. Назвал её django.
SSH:
Создал новый проект.
cd django/
django-admin.py startproject mybot
в результате создастся папка с именем проекта (в нашем случае — это «mybot») в папке /django. В ней будут файлы начального проекта, созданные автоматически:
/django
/mybot – папка проекта
/mybot – модуль с настройками нашего проекта
__init__.py
settings.py
urls.py
wsgi.py
manage.py
Проект в Django – это группа приложений. Приложение в Django – это программа, которая выполняет действия, заложенные разработчиком.
SSH:
Создал приложение.
cd mybot
python manage.py startapp vk_bot
Я перешёл в папку /django/mybot и создал новое приложение с именем «vk_bot».
В папке проекта создалась папка с именем приложения, содержащая файлы приложения, созданные автоматически:
/django
/mybot – папка проекта
/mybot – модуль с настройками нашего проекта
__init__.py
settings.py
urls.py
wsgi.py
manage.py
/vk_bot – папка приложения
__init__.py
admin.py
apps.py
models.py
tests.py
views.py
FTP:
Я загрузил все файлы проекта на свой ноутбук для работы с кодом.
Для работы с файлами проекта и программирования я использовал приложение Atom.
Atom:
Отредактировал настройки проекта в файле /django/mybot/mybot/settings.py
...
DEBUG = False
...
ALLOWED_HOSTS = [
u'mybot.mysite.ru',
]
...
Atom:
Отредактировал настройки url-маршрутизации адресов в файле /django/mybot/mybot/urls.py
...
urlpatterns = [
url(r'^vk_bot/', include('vk_bot.urls')),
]
...
FTP:
Создал файл /django/mybot/vk_bot/urls.py такого содержания:
from django.conf.urls import url
from . import views
app_name = 'vk_bot'
urlpatterns = [
url(r'^$', views.index, name='index'),
]
Atom:
Отредактировал файл /django/mybot/vk_bot/views.py – добавил в него функцию под названием index, которая будет выполняться при запросе в браузере адреса
https://mybot.mysite.ru/vk_bot/
views.py
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.views.decorators.csrf import csrf_exempt
from django.shortcuts import render
from django.http import HttpResponse
from bot_config import * # import token, confirmation_token and over constants from bot_config.py
import json, vk # vk is library from VK
"""
Using VK Callback API version 5.5
For more ditalies visit https://vk.com/dev/callback_api
"""
"""
From Django documentation (https://docs.djangoproject.com/en/1.11/ref/request-response/)
When a page is requested, Django automatically creates an HttpRequest object that contains
metadata about the request. Then Django loads the appropriate view, passing the
HttpRequest as the first argument to the view function.
This argiment is <request> in def index(request):
Decorator <@csrf_exempt> marks a view as being exempt from the protection
ensured by the Django middleware.
For cross site request protection will be used secret key from VK
"""
@csrf_exempt #exempt index() function from built-in Django protection
def index(request): #url: https://mybot.mysite.ru/vk_bot/
if (request.method == "POST"):
data = json.loads(request.body)# take POST request from auto-generated variable <request.body> in json format
if (data['secret'] == secret_key):# if json request contain secret key and it's equal my secret key
if (data['type'] == 'confirmation'):# if VK server request confirmation
"""
For confirmation my server (webhook) it must return
confirmation token, whitch issuing in administration web-panel
your public group in vk.com.
Using <content_type="text/plain"> in HttpResponse function allows you
response only plain text, without any format symbols.
Parametr <status=200> response to VK server as VK want.
"""
# confirmation_token from bot_config.py
return HttpResponse(confirmation_token, content_type="text/plain", status=200)
if (data['type'] == 'message_new'):# if VK server send a message
session = vk.Session()
api = vk.API(session, v=5.5)
user_id = data['object']['user_id']
# token from bot_config.py
api.messages.send(access_token = token, user_id = str(user_id), message = "Hello, I'm bot!")
return HttpResponse('ok', content_type="text/plain", status=200)
else:
return HttpResponse('see you :)')
В скрипте views.py, в функции index(request) пришлось отключить встроенную в Django CSRF защиту, т.к. я получал ошибку «403 Forbidden». CSRF — Cross Site Request Forgery protection — защита от подделки межсайтовых запросов. Как работает CSRF можете почитать в этой статье.
Для отключения защиты я использовал декоратор @csrf_exempt. Но чтобы все таки эту защиту обеспечить, но более простым способом я использовал секретный ключ, который прописывается в разделе управления группой на сайте ВКонтакте.
Вот это кусок кода отвечает за то, чтобы обрабатывать запросы с сервера, которые он будет посылать для того, чтобы подключить наш webhook для обработки событий. Скажем так, «подтверждение» нашего webhook’a.
if (data['type'] == 'confirmation'):# if VK server request confirmation
"""
For confirmation my server (webhook) it must return
confirmation token, whitch issuing in administration web-panel
your public group in vk.com.
Using <content_type="text/plain"> in HttpResponse function allows you
response only plain text, without any format symbols.
Parametr <status=200> response to VK server as VK want.
"""
# confirmation_token from bot_config.py
return HttpResponse(confirmation_token, content_type="text/plain", status=200)
Обратите внимание, что все конфигурационные настройки я держу в отдельном файле конфигурации бота bot_config.py и поэтому подключаю его в начале скрипта:
from bot_config import * # import token, confirmation_token and over constants from bot_config.py
bot_config.py
# -*- coding: utf-8 -*-
"""
Configuration file for VK bot
"""
# token issued on the VK group web-administration page
token = '...'
# confirmation token issued on the VK group web-administration page in "Callback API" section
confirmation_token = '...'
# secret key for cross site request forgery protection. It will be in each VK server request
secret_key = '...'
А в этот кусок кода обрабатывает сообщения пользователей:
if (data['type'] == 'message_new'):# if VK server send a message
session = vk.Session()
api = vk.API(session, v=5.5)
user_id = data['object']['user_id']
# token from bot_config.py
api.messages.send(access_token = token, user_id = str(user_id), message = "Hello, I'm bot!")
return HttpResponse('ok', content_type="text/plain", status=200)
Если что-то показалось непонятным, можно дополнительно прочитать статью о первой настройке Django.
Магия для веб-сервера. Для настройки адресации запросов к веб-серверу зашёл на сервер в папку с доменами через FTP-клиент FileZilla и создал там папку "mybot.mysite.ru", в неё положил три файла, содержимое которых взято из справки на хостинге:
.htaccess
AddHandler wsgi-script .wsgi
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ /django.wsgi/$1 [QSA,PT,L]
RewriteCond %{HTTP:X-Forwarded-Protocol} !=https
RewriteRule .* https://%{SERVER_NAME}%{REQUEST_URI} [R=301,L]
django.wsgi
import os, sys
virtual_env = os.path.expanduser('~/virtualenv/myEnv')
activate_this = os.path.join(virtual_env, 'bin/activate_this.py')
execfile(activate_this, dict(__file__=activate_this))
sys.path.insert(0, os.path.join(os.path.expanduser('~'), 'django/mybot'))
os.environ['DJANGO_SETTINGS_MODULE'] = 'mybot.settings'
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()
Здесь «myEnv» — название созданного вами виртуального окружения, «django» — папка в корневом разделе файловой системы на хостинге, «mybot» — название проекта, который мы создали с помощью Django.
index.html
Сайт в разработке
«Привязываем» наш webhook к обработке сообщений в созданной группе Вконтакте.
Для этого вернёмся в раздел управления нашей группой на сайте ВКонтакте (см. скриншот выше). Введём в поле «Адрес» наш адрес webhook’a
https://mybot.mysite.ru/vk_bot/и нажмём кнопку «Подтвердить». Если наша функция index(request), написанная в файле /django/mybot/vk_bot/views.py работает корректно, т. е. в ней нет опечаток и ошибок, то появится зелёная галочка, символизирующая что всё хорошо.
Чтобы наш webhook получал от сервера ВКонтакте сообщения о новых сообщениях пользователей, в разделе управления нашей группой на сайте ВКонтакте, во вкладке «Типы событий» поставим галочку напротив «входящие сообщения».
В итоге наш скрипт будет получать вот такие сообщения в json формате:
{"type":"message_new","object":{"id":891,"date":1541599508,"out":0,"user_id":1...1,"read_state":0,"title":"","body":"текст сообщения пользователя"},"group_id":1...4,"secret":"uxSBw"}
Обратите внимание на то, что в json сообщении есть поле «secret». Это тот самый секретный ключ, который я прописал в разделе управления группой на сайте ВКонтакте, взамен встроенной в Django CSRF защиты, которую мне пришлось отключить.
Как сделать бота умнее и лучше?
Можно создать базу данных с ответами пользователю и научить бота выбирать ответ наиболее близкий по смыслу к вопросу пользователя. Об этом расскажу в отдельной статье.
Можно и нужно запрограммировать сценарии взаимодействия с пользователем, так сказать чтобы поддержать разговор.
Успехов в вашем творческом труде!