最近我自己搞了个简单的聊天机器人(类似淘宝机器客服一般的),他可以帮你查询疫情的最新消息、汇报天气情况、给你讲笑话、陪你聊天等一些基本的功能。下面就来介绍一下它。
制作思路
要做一个聊天机器人那么首先你要有一个聊天的界面,设计这个聊天界面,让它尽量好看,符合你的想法就好。然后就要想一下这个聊天界面需要什么内容:a.图标,我们打开这个页面左上方应该要有一个小图标。b.要有显示聊天记录的地方。c.要有一个输入框。d.要有一个发送的按钮。再其次是设计这个聊天机器人及其功能。
再次完善
a.聊天页面:这里就看自己的设计了,我自己设计的比较一般般,效果就会在最后给大家展示。
b.界面的内容:在聊天记录的那一栏,我们要知道是谁发的消息,所以我选择了使用头像这样子来表现。然后就是在发送按钮上面,我设计了一个功能,当你鼠标放在上面的时候可以显示出 “点击发送” 的字样
c.聊天机器人:我是打算接入一个百度unit平台的API,借助百度来使我的机器人更加智能化。当然也有一些部分的应答打算我自己来写函数。
程序框架
要使用的库:
import json
import sys
import requests
from PyQt5 import QtWidgets, QtGui
from PyQt5.QtCore import QSize
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import *
以下是主函数的部分
# 实例化一个应用对象
app = QtWidgets.QApplication(sys.argv)
先建立一个应用对象app
# 实例化聊天窗
win = ChatBox()
win.show()
sys.exit(app.exec_())
然后我们创建了一个窗口类ChatBox(具体这个类如何写的后面会有介绍,这边先这样子说),建立了一个对象win。利用show函数把这个窗口显示出来。然后因为要实现这个窗口的关闭所以要用sys库中的exit函数。
以下是函数封装部分讲解
ChatBox(建立的python类)
class ChatBox(QWidget):
这是一个子类,父类是QWidget。
def __init__(self):
# 初始化父类构造函数
# super会找到ChatBox继承的父类QWindegt, 去实例化父类的构造函数
super(ChatBox, self).__init__()
# 绘制界面方法
# 初始化界面
self.AI_robot = Chat_robot()
self.initUI()
super(ChatBox, self).init()这句的意思是:super会找到ChatBox继承的父类QWindegt, 去实例化父类的构造函数。self.AI_robot = Chat_robot()这里我实例化了一个AI机器人,Chat_robot也是一个类。self.initUI()这是初始化窗口的函数。
下面是initUI()函数的内部组成
self.setWindowTitle("快来聊天啊!")
self.setGeometry(500, 100, 800, 700)
# 美化窗口+添加控件
# 窗口图标
self.icon = QtGui.QIcon()
self.icon.addPixmap(QtGui.QPixmap("picture.png"), # 图标路径
QtGui.QIcon.Normal,
QtGui.QIcon.Off)
self.setWindowIcon(self.icon)
我们先设置窗口的标题,然后就是设置窗口的位置和大小,然后我们用picture这张图做了一个图标(这里要注意这个图片要放在这个工程下)。
self.left_box = QWidget(self)
self.left_box.setGeometry(10, 10, 200, 680)
self.left_box.setStyleSheet("background-color: rgb(200, 200, 169)")
# 设置背景色
self.AI = QLabel("专属机器人在线中", self)
self.AI.setGeometry(11, 11, 200, 120)
self.AI.setStyleSheet("background-color: rgb(0, 245, 255); color: black; font-size:22px")
我设计了一个边框,位置大概是在左边区域(setGeometry这个函数是确定位置的,后面的也是如此。),里面我就做了简单的一个背景色设置和一个标签“专属机器人在线中”以及标签的背景颜色和字的颜色、大小。
self.chatBox = QListWidget(self)
# 设置位置
self.chatBox.setGeometry(210, 10, 590, 600)
# 设置样式
self.chatBox.setStyleSheet("background-image: url(background.png);border:2px solid #c4c4c4; font-size:30px")
# 设置图标大小
self.chatBox.setIconSize(QSize(40, 40))
我这里绘制了一个聊天窗口,显示聊天内容的地方,setIconSize这个函数是设置说话的人的头像大小,我这里还用background这张图片做了个背景图。
self.char_input = QLineEdit(self)
self.char_input.setGeometry(210, 615, 480, 80)
self.char_input.setStyleSheet("color:black; font-size:30px; border: 10px solid #f4f4f4;"
"background-color: rgb(255, 255, 255);")
这个是发送消息的部分,设置了位置,字的颜色大小,待发消息框的背景色。
self.submit = QPushButton('发送', self) # 按钮显示的文字
self.submit.setToolTip('点击发送') # 当鼠标放上去后显示的内容
self.submit.setGeometry(695, 615, 100, 80) # 位置
self.submit.setStyleSheet("color:black; font-size:20px; font-weight:bold; border-radius:2;"
"background-color: rgb(131, 175, 155);")
这是发送按钮的设计,包括了按钮上面的文字,位置,字体的颜色,大小,背景色。
item = QListWidgetItem(QIcon("AI_robot.png"), "你好!有什么可以为你服务的?", self.chatBox)
self.submit.clicked.connect(self.send_message) # 信号与槽的连接
第一句代码是,当你启动程序的时候这个机器人会自动的说一句“你好!有什么可以为你服务的?”。QIcon(“AI_robot.png”)这个是设置机器人的头像。self.chatBox这个是说,显示在chatBox这一部分(即聊天记录框)。后面一句代码是说当你点击这个按钮是程序的反应,反应函数是send_message。
对send_message这个函数的讲解:
def send_message(self):
# 用户输出什么信息
content = self.char_input.text()
if len(content) == 0:
return # 函数终结
# 把输入的信息显示在聊天区
item = QListWidgetItem(QIcon("USER.png"), content, self.chatBox)
# 清空输入框
self.char_input.clear()
我们先从待发消息去获取你要发的消息给content。当然如果是空消息就发不出去了,所以会有个判断。然后就是把这个消息放在聊天区中,同时显示头像,并清除待发消息区的消息。
robot_reply = self.AI_robot.get_reply(content)
我同过AI_robot这个对象里面的get_reply函数来得到机器人的回答。robot_reply 就是机器人要回答的内容部分。
# 当询问天气等缺少地点元素的时候
global connect_flag, word_flag
if connect_flag:
content = content + connect_word # 连接两个字符串
robot_reply = self.AI_robot.get_reply(content)
connect_flag = False
这里做的处理是说,当我们问“天气如何”时机器人会回答一个“你要问的是哪里的天气呢”,那么我就要将这两次的消息进行拼接,在发给机器人,不然机器人没办法做到将这两次的消息连接起来,他只会当成一个问题来回答。connect_flag这是一个标志位,当有连接的时候是True,不需要时是False。
self.deal_message(robot_reply, content)
# 下面的函数是另外定义的,放在这里只是为了一次解释清楚罢了
def deal_message(self, robot_reply, content):
# 处理句子的连接
for index, item in enumerate(key_word):
if item in robot_reply:
global connect_flag
connect_flag = True
# 处理重复输入
global word_flag, connect_word
if word_flag == 0:
connect_word = content
if connect_word == content:
word_flag = word_flag + 1 # word_flag 记录出现的次数
else:
word_flag = 0
# 处理背景的改变
global bg_flag
for index, item in enumerate(weather):
if item in robot_reply:
bg_flag = index+1 # bg_flag 这是背景的选择,0~4,分别对应着不同的天气背景
break
else:
bg_flag = 0
这个函数是用来处理句子连接、重复输入以及背景的改变这三个功能的标志位,具体函数的处理在后面。哦对了,这里使用的标志位都是全局变量。
robot_reply = self.deal_reprtion(robot_reply) # 处理重复输入多次函数
# 下面的函数是另外定义的,放在这里只是为了一次解释清楚罢了
def deal_reprtion(self, robot_reply):
global word_flag
if word_flag == 2:
robot_reply = '这个问题我已经回答过了'
elif word_flag == 3:
robot_reply = '你是憨憨嘛?都说了回答了你还问,再问就不理你了。'
elif word_flag == 4:
robot_reply = '不理你了'
elif word_flag >= 5:
robot_reply = None
return robot_reply
当你重复输入同一个问题时,机器人就不会再回答你一边,而是会说你了
# 改变背景
self.change_background()
# 下面的函数是另外定义的,放在这里只是为了一次解释清楚罢了
def change_background(self):
global bg_flag
if bg_flag == 1:
self.chatBox.setStyleSheet("background-image: url(sunny.png);border:2px solid #c4c4c4; font-size:30px")
elif bg_flag == 2:
self.chatBox.setStyleSheet("background-image: url(foggy.png);border:2px solid #c4c4c4; font-size:30px")
elif bg_flag == 3:
self.chatBox.setStyleSheet("background-image: url(rainy.png);border:2px solid #c4c4c4; font-size:30px")
elif bg_flag == 4:
self.chatBox.setStyleSheet("background-image: url(cloudy.png);border:2px solid #c4c4c4; font-size:30px")
else:
self.chatBox.setStyleSheet("background-image: url(background.png);border:2px solid #c4c4c4; font-size:30px")
每一个标志位对应着一种天气背景,在信息处理函数时,会去遍历机器人回答的字符串,当有涉及到天气的因素时就会记录下来,同时会改变标志的值。效果展示,就像是那个询问天气的图片。
self.reply(robot_reply) # 消息的回复
# 下面的函数是另外定义的,放在这里只是为了一次解释清楚罢了
def reply(self, robot_reply):
if robot_reply is None:
return
if len(robot_reply) >= 16:
count = int(len(robot_reply)/16)
for i in range(0, count):
if i ==0:
res = robot_reply[16*i:(16*(i+1)-1)]
item = QListWidgetItem(QIcon("AI_robot.png"), res, self.chatBox)
else:
res = robot_reply[16*i-1:(16 * (i+1)-1)]
item = QListWidgetItem(res, self.chatBox)
if len(robot_reply)/16 > count:
res = robot_reply[16*count-1:]
item = QListWidgetItem(res, self.chatBox)
return
item = QListWidgetItem(QIcon("AI_robot.png"), robot_reply, self.chatBox)
消息的回复这个函数,我计算了一下它的长度,如果长度超过窗口长度则它会分行、多次输出,同时只有第一次输出的才会带头像。最后一句话是保证不超行的时候可以正常输出。开头的if robot_reply is None:判断是为了配合之前那个输入多次后不理你这个功能的。中间的就是用来处理字符长度的。
下面讲解机器人这个对象:
class Chat_robot:
def __init__(self):
self.AK = AK
self.SK = SK
self.access_token = self.get_access_token()
先建立一个机器人类。__init__这是一个构造函数,里面放的是这个机器人的属性。因为我们的机器人是用百度unit平台的机器人,所以这里要调用人家的API,AK,SK对应这API Key 和 Secret Key 。access_token 对应这token,具体怎么获取百度unit平台的token,参考百度的文档获取access_token。当然这里我也会介绍python获取token的方法。
def get_access_token(self):
host = 'https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=' +\
self.AK + '&client_secret=' + self.SK
response = requests.get(host).json()
return response['access_token']
上面的函数就是python获取token带的方式。要详细了解里面的参数的意义,要通读上面的文档。
def get_reply(self, user_input):
post_data = json.dumps({
"log_id": "UNITTEST_10000",
"version": "2.0",
"service_id": "S29968",
"session_id": "",
"request": {
"query": user_input,
"user_id": "8888",
},
"dialog_state": {
"contexts": {
"SYS_REMEMBERED_SKILLS": ["1028652"]
}
}
})
# json.dumps() 用于将dict类型的数据转成str,因为如果直接将dict类型的数据写入json文件中会发生报错,因此在将数据写入时需要用到该函数
url = 'https://aip.baidubce.com/rpc/2.0/unit/service/chat?access_token=' + self.access_token
headers = {'content-type': 'application/x-www-form-urlencoded'}
response = requests.post(url, data=post_data, headers=headers).json()
if response:
return response['result']['response_list'][0]['action_list'][0]['say']
上面这个函数是,用来得到机器人的回答内容的。这里要注意的是,我们在API文档中他的post_data 这个数据是采用字符串的形式写的,我们在python中没办法识别它,所以我们要用上面的这个dumps函数写入json文件之中。
import json
import sys
import requests
from PyQt5 import QtWidgets, QtGui
from PyQt5.QtCore import QSize
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import *
# 表示从PyQt5的QtWidgets中引用全部函数(*表示全部)
# 1.应答核心--in判断成员是否在list中
# 2. 百度unitAPI接进来
# 常量
AK = 'LNsYReyUKb9idkO9OHHnanm0'
SK = '28iTb65vBPmSR05vyNU6pnYjIa739KP7'
key_word = ('你要找', '你要查')
weather = ('晴', '雾', '雨', '阴')
connect_flag = False
connect_word = '初始化'
word_flag = 0
bg_flag = 0
# 继承
class ChatBox(QWidget):
def __init__(self):
# 初始化父类构造函数
# super会找到ChatBox继承的父类QWindegt, 去实例化父类的构造函数
super(ChatBox, self).__init__()
# 绘制界面方法
# 初始化界面
self.AI_robot = Chat_robot()
self.initUI()
def initUI(self):
self.setWindowTitle("快来聊天啊!")
self.setGeometry(500, 100, 800, 700)
# 美化窗口+添加控件
# 窗口图标
self.icon = QtGui.QIcon()
self.icon.addPixmap(QtGui.QPixmap("picture.png"), # 图标路径
QtGui.QIcon.Normal,
QtGui.QIcon.Off)
self.setWindowIcon(self.icon)
# 左侧栏
self.left_box = QWidget(self)
self.left_box.setGeometry(10, 10, 200, 680)
self.left_box.setStyleSheet("background-color: rgb(200, 200, 169)") # 设置背景色
self.AI = QLabel("专属机器人在线中", self)
self.AI.setGeometry(11, 11, 200, 120)
self.AI.setStyleSheet("background-color: rgb(0, 245, 255); color: black; font-size:22px")
# 右上方聊天区
self.chatBox = QListWidget(self)
# 设置位置
self.chatBox.setGeometry(210, 10, 590, 600)
# 设置样式
self.chatBox.setStyleSheet("background-image: url(background.png);border:2px solid #c4c4c4; font-size:30px")
# 设置图标大小
self.chatBox.setIconSize(QSize(40, 40))
# 右下方内容准备
self.char_input = QLineEdit(self)
self.char_input.setGeometry(210, 615, 480, 80)
self.char_input.setStyleSheet("color:black; font-size:30px; border: 10px solid #f4f4f4; "
"background-color: rgb(255, 255, 255);")
# 发送按钮
self.submit = QPushButton('发送', self)
self.submit.setToolTip('点击发送') # 当鼠标放上去后显示的内容
self.submit.setGeometry(695, 615, 100, 80)
self.submit.setStyleSheet("color:black; font-size:20px; font-weight:bold; border-radius:2;"
"background-color: rgb(131, 175, 155);")
# 点击发送按钮,发送消息
item = QListWidgetItem(QIcon("AI_robot.png"), "你好!有什么可以为你服务的?", self.chatBox)
self.submit.clicked.connect(self.send_message) # 信号与槽的连接
def send_message(self):
# 用户输出什么信息
content = self.char_input.text()
if len(content) == 0:
return # 函数终结
# 把输入的信息显示在聊天区
item = QListWidgetItem(QIcon("USER.png"), content, self.chatBox)
# 清空输入框
self.char_input.clear()
robot_reply = self.AI_robot.get_reply(content)
# 当询问天气等缺少地点元素的时候
global connect_flag, word_flag
if connect_flag:
content = content + connect_word
robot_reply = self.AI_robot.get_reply(content)
connect_flag = False
self.deal_message(robot_reply, content)
#
# 下面这个是处理重复输入多次函数
robot_reply = self.deal_reprtion(robot_reply)
# 改变背景
self.change_background()
self.reply(robot_reply)
# 消息回复
def reply(self, robot_reply):
if robot_reply is None:
return
if len(robot_reply) >= 16:
count = int(len(robot_reply)/16)
for i in range(0, count):
if i ==0:
res = robot_reply[16*i:(16*(i+1)-1)]
item = QListWidgetItem(QIcon("AI_robot.png"), res, self.chatBox)
else:
res = robot_reply[16*i-1:(16 * (i+1)-1)]
item = QListWidgetItem(res, self.chatBox)
if len(robot_reply)/16 > count:
res = robot_reply[16*count-1:]
item = QListWidgetItem(res, self.chatBox)
return
item = QListWidgetItem(QIcon("AI_robot.png"), robot_reply, self.chatBox)
def deal_message(self, robot_reply, content):
# 处理句子的连接
for index, item in enumerate(key_word):
if item in robot_reply:
global connect_flag
connect_flag = True
# 处理重复输入
global word_flag, connect_word
if word_flag == 0:
connect_word = content
if connect_word == content:
word_flag = word_flag + 1
else:
word_flag = 0
# 处理背景的改变
global bg_flag
for index, item in enumerate(weather):
if item in robot_reply:
bg_flag = index+1
break
else:
bg_flag = 0
def deal_reprtion(self, robot_reply):
global word_flag
if word_flag == 2:
robot_reply = '这个问题我已经回答过了'
elif word_flag == 3:
robot_reply = '你是憨憨嘛?都说了回答了你还问,再问就不理你了。'
elif word_flag == 4:
robot_reply = '不理你了'
elif word_flag >= 5:
robot_reply = None
return robot_reply
def change_background(self):
global bg_flag
if bg_flag == 1:
self.chatBox.setStyleSheet("background-image: url(sunny.png);border:2px solid #c4c4c4; font-size:30px")
elif bg_flag == 2:
self.chatBox.setStyleSheet("background-image: url(foggy.png);border:2px solid #c4c4c4; font-size:30px")
elif bg_flag == 3:
self.chatBox.setStyleSheet("background-image: url(rainy.png);border:2px solid #c4c4c4; font-size:30px")
elif bg_flag == 4:
self.chatBox.setStyleSheet("background-image: url(cloudy.png);border:2px solid #c4c4c4; font-size:30px")
else:
self.chatBox.setStyleSheet("background-image: url(background.png);border:2px solid #c4c4c4; font-size:30px")
class Chat_robot:
def __init__(self):
self.AK = AK
self.SK = SK
self.access_token = self.get_access_token()
def get_access_token(self):
host = 'https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=' +\
self.AK + '&client_secret=' + self.SK
response = requests.get(host).json()
return response['access_token']
def get_reply(self, user_input):
post_data = json.dumps({
"log_id": "UNITTEST_10000",
"version": "2.0",
"service_id": "S29968",
"session_id": "",
"request": {
"query": user_input,
"user_id": "8888",
},
"dialog_state": {
"contexts": {
"SYS_REMEMBERED_SKILLS": ["1028652"]
}
}
})
# json.dumps() 用于将dict类型的数据转成str,因为如果直接将dict类型的数据写入json文件中会发生报错,因此在将数据写入时需要用到该函数
# access_token = '24.f9fe0457a175ef9f5255cbad68d8814a.2592000.1592477609.282335-19952337'
url = 'https://aip.baidubce.com/rpc/2.0/unit/service/chat?access_token=' + self.access_token
headers = {'content-type': 'application/x-www-form-urlencoded'}
response = requests.post(url, data=post_data, headers=headers).json()
if response:
return response['result']['response_list'][0]['action_list'][0]['say']
# 控件 位置 样式
# 程序的主入口会有main函数
# python把每一个py脚本看成模块,可以单独运行
if __name__ == "__main__":
# 实例化一个应用对象
app = QtWidgets.QApplication(sys.argv)
# 实例化聊天窗
win = ChatBox()
win.show()
sys.exit(app.exec_())
这个项目还有很多待改进之处,下面就来说说:
通过这次的小项目,让我更加熟练的掌握了python的用法和基础语法以及利用类去封装函数,还学会了如何去读API文档,以及对API的调用。更加的我还认识到了PyQt5这个库。