Django实现聊天机器人


Posted in Python onMay 31, 2021

演示效果如下所示:

Django实现聊天机器人

实现原理

用户在聊天界面调用Celery异步任务,Celery异步任务执行完毕后发送结果给channels,然后channels通过websocket将结果实时推送给用户。对于简单的算术运算,Celery一般自行计算就好了。对于网上查找诗人简介这样的任务,Celery会调用Python爬虫(requests+parsel)爬取古诗文网站上的诗人简介,把爬取结果实时返回给用户。

接下来我们来看下具体的代码实现吧。

第一步 安装环境依赖

首先在虚拟环境中安装django和以下主要项目依赖。本项目使用了最新版本,为3.X版本。

 # 主要项目依赖
 pip install django
 pip install channels
 pip install channels_redis
 pip install celery
 pip install redis
 pip install eventlet # windows only

 # 爬虫依赖
 pip install requests
 pip install parsel

 新建一个名为myproject的项目,新建一个app名为bots。如果windows下安装报错,如何解决自己网上去找吧,很容易解决。修改settings.py, 将channels和chat加入到INSTALLED_APPS里,并添加相应配置,如下所示:

INSTALLED_APPS = [
       'django.contrib.admin',
       'django.contrib.auth',
       'django.contrib.contenttypes',
       'django.contrib.sessions',
       'django.contrib.messages',
       'django.contrib.staticfiles',
       'channels', # channels应用     
       'bots', # bots应用
    ]

 # 设置ASGI应用
 ASGI_APPLICATION = 'myproject.asgi.application'

# 生产环境中使用redis做后台,安装channels_redis
import os
CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels_redis.core.RedisChannelLayer",
        "CONFIG": {
            "hosts": [os.environ.get('REDIS_URL', 'redis://127.0.0.1:6379/2')],
        },
    },
}

最后将bots应用的urls.py加入到项目urls.py中去,这和常规Django项目无异。

 # myproject/urls.py
 from django.conf.urls import include
 from django.urls import path
 from django.contrib import admin
 
 urlpatterns = [
     path('bots/', include('bots.urls')),
     path('admin/', admin.site.urls),
 ]

第二步 配置Celery

pip安装好Celery和redis后,我们要对其进行配置。分别修改myproject目录下的__init__.py和celery.py(新建), 添加如下代码:

# __init__.py
from .celery import app as celery_app
__all__ = ('celery_app',)

# celery.py
import os
from celery import Celery

# 设置环境变量
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
# 实例化
app = Celery('myproject')

# namespace='CELERY'作用是允许你在Django配置文件中对Celery进行配置
# 但所有Celery配置项必须以CELERY开头,防止冲突
app.config_from_object('django.conf:settings', namespace='CELERY')

# 自动从Django的已注册app中发现任务
app.autodiscover_tasks()

# 一个测试任务
@app.task(bind=True)
def debug_task(self):
    print(f'Request: {self.request!r}')

接着修改settings.py, 增加如下Celery配置:

# Celery配置
CELERY_BROKER_URL = "redis://127.0.0.1:6379/0"
CELERY_TIMEZONE = TIME_ZONE

# celery内容等消息的格式设置,默认json
CELERY_ACCEPT_CONTENT = ['application/json', ]
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'

完整Celery配置见:Django进阶:万字长文教你使用Celery执行异步和周期性任务(多图)

第三步 编写机器人聊天主页面

本例我们只需要利用django普通视图函数编写1个页面,用于展示首页(index)与用户交互的聊天页面。这个页面对应的路由及视图函数如下所示:

# bots/urls.py
 from django.urls import path
 from . import views
 
 urlpatterns = [
     path('', views.index, name='index'),
 ]
 
 # bots/views.py
 from django.shortcuts import render
 
 def index(request):
     return render(request, 'bots/index.html', {})

接下来我们编写模板文件index.html,它的路径位置如下所示:

bots/
     __init__.py
     templates/
         bots/
             index.html
     urls.py
     views.py

index.html内容如下所示。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8"/>
    <title>Django+Channels+Celery聊天机器人</title>
</head>
<body>

<textarea id="chat-log" cols="100" rows="20" readonly></textarea>
<br/>
<input id="chat-message-input" type="text" size="100" 
      placeholder="输入`help`获取帮助信息."/><br/><input id="chat-message-submit" type="button" value="Send"/>
   <script>
    var wss_protocol = (window.location.protocol == 'https:') ? 'wss://': 'ws://';
    var chatSocket = new WebSocket(
        wss_protocol + window.location.host + '/ws/bots/'
        );

    chatSocket.onopen = function(e) {
document.querySelector('#chat-log').value +=
('欢迎来到大江狗Django聊天机器人. 请输入`help`获取帮助信息.\n')}

    chatSocket.onmessage = function(e) {
        var data = JSON.parse(e.data);
        var message = data['message'];
        document.querySelector('#chat-log').value += (message + '\n');
    };

    chatSocket.onclose = function(e) {
document.querySelector('#chat-log').value +=
('Socket closed unexpectedly, please reload the page.\n')};

    document.querySelector('#chat-message-input').focus();
    document.querySelector('#chat-message-input').onkeyup = function(e) {
        if (e.keyCode === 13) {  // enter, return
            document.querySelector('#chat-message-submit').click();
        }
    };

    document.querySelector('#chat-message-submit').onclick = function(e) {
        var messageInputDom = document.querySelector('#chat-message-input');
        var message = messageInputDom.value;
        chatSocket.send(JSON.stringify({
            'message': message
        }));
     messageInputDom.value = '';
    };
</script>

</body>
</html>

第四步 编写后台websocket路由及处理方法

当 channels 接受 WebSocket 连接时, 它也会根据根路由配置去查找相应的处理方法。只不过channels的websocket路由不在urls.py中配置,处理函数也不写在views.py。在channels中,这两个文件分别变成了routing.py和consumers.py。

在bots应用下新建routing.py, 添加如下代码。它的作用是将发送至ws/bots/的websocket请求转由BotConsumer处理。

from django.urls import re_path

from . import consumers

websocket_urlpatterns = [
    re_path(r'ws/bots/$', consumers.BotConsumer.as_asgi()),
]

注意:定义websocket路由时,推荐使用常见的路径前缀 (如/ws) 来区分 WebSocket 连接与普通 HTTP 连接, 因为它将使生产环境中部署 Channels 更容易,比如nginx把所有/ws的请求转给channels处理。

与Django类似,我们还需要把这个app的websocket路由加入到项目的根路由中去。编辑myproject/asgi.py, 添加如下代码:

# myproject/asgi.py
import os

from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from django.core.asgi import get_asgi_application
import chat.routing
import bots.routing

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")

application = ProtocolTypeRouter({
    "http": get_asgi_application(),
    # websocket请求使用的路由
    "websocket": AuthMiddlewareStack(
        URLRouter(
            bots.routing.websocket_urlpatterns
        )
    )
})

接下来在bots应用下新建consumers.py, 添加如下代码:

import json
from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer

from . import tasks

COMMANDS = {
    'help': {
        'help': '命令帮助信息.',
    },
    'add': {
        'args': 2,
        'help': '计算两个数之和, 例子: `add 12 32`.',
        'task': 'add'
    },
    'search': {
        'args': 1,
        'help': '通过名字查找诗人介绍,例子: `search 李白`.',
        'task': 'search'
    },
}



class BotConsumer(WebsocketConsumer):
    def receive(self, text_data):
        text_data_json = json.loads(text_data)
        message = text_data_json['message']

        response_message = '请输入`help`获取命令帮助信息。'
        message_parts = message.split()
        if message_parts:
            command = message_parts[0].lower()
            if command == 'help':
                response_message = '支持的命令有:\n' + '\n'.join(
                    [f'{command} - {params["help"]} ' for command, params in COMMANDS.items()])
            elif command in COMMANDS:
                if len(message_parts[1:]) != COMMANDS[command]['args']:
                    response_message = f'命令`{command}`参数错误,请重新输入.'
                else:
                    getattr(tasks, COMMANDS[command]['task']).delay(self.channel_name, *message_parts[1:])
                    response_message = f'收到`{message}`任务.'
                    
        async_to_sync(self.channel_layer.send)(
            self.channel_name,
            {
                'type': 'chat.message',
                'message': response_message
            }
        )

    def chat_message(self, event):
        message = event['message']

        # Send message to WebSocket
        self.send(text_data=json.dumps({
            'message': f'[机器人]: {message}'
        }))

上面代码中最重要的一行如下所示。BotConsumer在接收到路由转发的前端消息后,对其解析,将当前频道名和解析后的参数一起交由Celery异步执行。Celery执行任务完成以后会将结果发到这个频道,这样就实现了channels和Celery的通信。

getattr(tasks, COMMANDS[command]['task']).delay(self.channel_name, *message_parts[1:])

第五步 编写Celery异步任务

在bots目录下新建`tasks.py`,添加如下代码:

from asgiref.sync import async_to_sync
from celery import shared_task
from channels.layers import get_channel_layer
from parsel import Selector
import requests

channel_layer = get_channel_layer()

@shared_task
def add(channel_name, x, y):
    message = '{}+{}={}'.format(x, y, int(x) + int(y))
    async_to_sync(channel_layer.send)(channel_name, {"type": "chat.message", "message": message})
    print(message)

@shared_task
def search(channel_name, name):
    spider = PoemSpider(name)
    result = spider.parse_page()
    async_to_sync(channel_layer.send)(channel_name, {"type": "chat.message", "message": str(result)})
    print(result)

class PoemSpider(object):
    def __init__(self, keyword):
        self.keyword = keyword
        self.url = "https://so.gushiwen.cn/search.aspx"
        
    def parse_page(self):
        params = {'value': self.keyword}
        response = requests.get(self.url, params=params)
        if response.status_code == 200:
            # 创建Selector类实例
            selector = Selector(response.text)
            # 采用xpath选择器提取诗人介绍
            intro = selector.xpath('//textarea[starts-with(@id,"txtareAuthor")]/text()').get()
            print("{}介绍:{}".format(self.keyword, intro))
            if intro:
                return intro

        print("请求失败 status:{}".format(response.status_code))
        return "未找到诗人介绍。"

以上两个任务都以channel_name为参数,任务执行完毕后通过channel_layer的send方法将结果发送到指定频道。

注意:

- 默认获取channel_layer的方式是调用接口:channels.layers.get_channel_layer()。如果是在consumer中调用接口的话可以直接使用self.channel_layer。

- 对于channel layer的方法(包括send()、group_send(),group_add()等)都属于异步方法,这意味着在调用的时候都需要使用await,而如果想要在同步代码中使用它们,就需要使用装饰器asgiref.sync.async_to_sync

第六步 运行看效果

如果不出意外,你现在的项目布局应该如下所示。说实话,整个项目一共没几个文件,Python的简洁和效率真是出了名的好啊。

Django实现聊天机器人

连续运行如下命令,就可以看到我们文初的效果啦。

# 启动django测试服务器
 python manage.py makemigrations
 python manage.py migrate
 python manage.py runserver
 
 # windows下启动Celery需eventlet
 # 启动Celery前确定redis服务已开启哦
 Celery -A myproject worker -l info -P eventlet

小结

本文我们使用Django + Channels + Celery + Redis打造了一个聊天机器人,既会算算术,还会查古诗文。借用这个实现原理,你可以打造非常有趣的实时聊天应用哦,比如在线即时问答,在线客服,实时查询订单,Django版的siri美女等等。

Django Channels + Websocket + Celery聊天机器人项目源码地址:https://github.com/shiyunbo/django-channels-chatbot

以上就是Django实现聊天机器人的详细内容,更多关于Django 聊天机器人的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
linux系统使用python监测系统负载脚本分享
Jan 15 Python
Python中解析JSON并同时进行自定义编码处理实例
Feb 08 Python
Python通过递归遍历出集合中所有元素的方法
Feb 25 Python
使用Python读写文本文件及编写简单的文本编辑器
Mar 11 Python
Python爬取京东的商品分类与链接
Aug 26 Python
Python pass详细介绍及实例代码
Nov 24 Python
pytorch中tensor的合并与截取方法
Jul 26 Python
python画微信表情符的实例代码
Oct 09 Python
win10子系统python开发环境准备及kenlm和nltk的使用教程
Oct 14 Python
python 在threading中如何处理主进程和子线程的关系
Apr 25 Python
Python如何使用input函数获取输入
Aug 06 Python
Python tkinter之ComboBox(下拉框)的使用简介
Feb 05 Python
Python趣味挑战之教你用pygame画进度条
Python趣味挑战之用pygame实现简单的金币旋转效果
May 31 #Python
解决pytorch读取自制数据集出现过的问题
Python爬虫基础初探selenium
只用40行Python代码就能写出pdf转word小工具
pytorch 如何把图像数据集进行划分成train,test和val
May 31 #Python
Python图片检索之以图搜图
You might like
PHP的分页功能
2007/03/21 PHP
解析PHP处理换行符的问题 \r\n
2013/06/13 PHP
php计算两个整数的最大公约数常用算法小结
2015/03/05 PHP
php去除二维数组的重复项方法
2015/11/03 PHP
PHP文件与目录操作示例
2016/12/24 PHP
php swoole多进程/多线程用法示例【基于php7nts版】
2019/08/12 PHP
浅谈Javascript嵌套函数及闭包
2010/11/09 Javascript
zTree插件之单选下拉菜单实例代码
2013/11/07 Javascript
jQuery+正则+文本框只能输入数字的实现方法
2016/10/07 Javascript
Vue封装Swiper实现图片轮播效果
2018/02/06 Javascript
JavaScript剩余操作符Rest Operator详解
2019/07/20 Javascript
VUE实现图片验证码功能
2020/11/18 Javascript
JavaScript setInterval()与setTimeout()计时器
2019/12/27 Javascript
解决iview table组件里的 固定列 表格不自适应的问题
2020/11/13 Javascript
python在linux系统下获取系统内存使用情况的方法
2015/05/11 Python
基于python时间处理方法(详解)
2017/08/14 Python
基于windows下pip安装python模块时报错总结
2018/06/12 Python
python递归函数绘制分形树的方法
2018/06/22 Python
解决python中os.listdir()函数读取文件夹下文件的乱序和排序问题
2018/10/17 Python
使用TensorFlow实现二分类的方法示例
2019/02/05 Python
Python matplotlib生成图片背景透明的示例代码
2019/08/30 Python
初次部署django+gunicorn+nginx的方法步骤
2019/09/11 Python
python实现广度优先搜索过程解析
2019/10/19 Python
Python之——生成动态路由轨迹图的实例
2019/11/22 Python
python时间与Unix时间戳相互转换方法详解
2020/02/13 Python
Python图像处理库PIL中图像格式转换的实现
2020/02/26 Python
Python MySQLdb 执行sql语句时的参数传递方式
2020/03/04 Python
Windows 平台做 Python 开发的最佳组合(推荐)
2020/07/27 Python
python RSA加密的示例
2020/12/09 Python
安德玛加拿大官网:Under Armour加拿大
2019/10/02 全球购物
保护动物倡议书
2014/04/15 职场文书
2014保险公司内勤工作总结
2014/12/16 职场文书
安全教育日主题班会
2015/08/13 职场文书
请病假条范文
2015/08/17 职场文书
个人工作总结(管理人员)范文
2019/08/13 职场文书
Python读取文件夹下的所有文件实例代码
2021/04/02 Python