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 相关文章推荐
python监控文件或目录变化
Jun 07 Python
利用Python实现图书超期提醒
Aug 02 Python
关于django 数据库迁移(migrate)应该知道的一些事
May 27 Python
基于Python开发chrome插件的方法分析
Jul 07 Python
在IPython中进行Python程序执行时间的测量方法
Nov 01 Python
Python实现定制自动化业务流量报表周报功能【XlsxWriter模块】
Mar 11 Python
python搜索包的路径的实现方法
Jul 19 Python
Python字符串中添加、插入特定字符的方法
Sep 10 Python
Python文本文件的合并操作方法代码实例
Mar 31 Python
Python 整行读取文本方法并去掉readlines换行\n操作
Sep 03 Python
python批量更改目录名/文件名的方法
Apr 18 Python
手把手教你使用TensorFlow2实现RNN
Jul 15 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
一个没有MYSQL数据库支持的简易留言本的编写
2006/10/09 PHP
php下尝试使用GraphicsMagick的缩略图功能
2011/01/01 PHP
如何通过Apache在本地配置多个虚拟主机
2020/07/29 PHP
Code:findPosX 和 findPosY
2006/12/20 Javascript
基于JQuery的一句代码实现表格的简单筛选
2010/07/26 Javascript
基于jquery的15款幻灯片插件
2011/04/10 Javascript
Javascript 中的 call 和 apply使用介绍
2012/02/22 Javascript
IE8下String的Trim()方法失效的解决方法
2013/11/08 Javascript
Bootstrap富文本组件wysiwyg数据保存到mysql的方法
2016/05/09 Javascript
老生常谈js动态添加事件--- 事件委托
2016/07/19 Javascript
Vue 配合eiement动态路由,权限验证的方法
2018/09/26 Javascript
Javascript中parseInt的正确使用方式
2018/10/17 Javascript
使用webpack搭建vue项目实现脚手架功能
2019/03/15 Javascript
Python使用numpy模块实现矩阵和列表的连接操作方法
2019/06/26 Python
利用python实现短信和电话提醒功能的例子
2019/08/08 Python
基于Django实现日志记录报错信息
2019/12/17 Python
详解Python 实现 ZeroMQ 的三种基本工作模式
2020/03/24 Python
Pytest单元测试框架如何实现参数化
2020/09/05 Python
Anaconda的安装与虚拟环境建立
2020/11/18 Python
Python爬虫逆向分析某云音乐加密参数的实例分析
2020/12/04 Python
利用CSS3实现圆角的outline效果的教程
2015/06/05 HTML / CSS
canvas简易绘图的实现(海绵宝宝篇)
2018/07/04 HTML / CSS
Sony C++笔试题
2013/03/10 面试题
EJB包括(SessionBean,EntityBean)说出他们的生命周期,及如何管理事务的?
2013/02/17 面试题
财务主管的岗位职责
2013/12/30 职场文书
公务员政审单位鉴定材料
2014/05/16 职场文书
外联部演讲稿
2014/05/24 职场文书
党的群众路线对照检查材料
2014/08/27 职场文书
销售人员工作自我评价
2014/09/21 职场文书
关于运动会的广播稿50字
2014/10/17 职场文书
2014年学校党建工作总结
2014/11/11 职场文书
事业单位工作人员年度考核个人总结
2015/02/12 职场文书
收银员岗位职责范本
2015/04/07 职场文书
班主任寄语2016
2015/12/04 职场文书
2016年教师学习教师法心得体会
2016/01/20 职场文书
Windows server 2012 配置Telnet以及用法详解
2022/04/28 Servers