写一个自动回复的聊天机器人

4,129 阅读8分钟
要写一个自动聊天的机器人,底层必定离不开socket, TCP 是一个稳定、可靠的传输协议,常用于对数据进行准确无误的传输,socket里面有对它的封装。


TCP 的概念

TCP 的英文全拼(Transmission Control Protocol)简称传输控制协议,它是一种面向连接的、可靠的、基于字节流的传输层通信协议

socket 的概念

socket (简称 套接字) 是进程之间通信一个工具进程之间想要进行网络通信需要基于这个 socket。它负责进程之间的网络数据传输,好比数据的搬运工。不夸张的说,只要跟网络相关的应用程序或者软件都使用到了 socket

客户端

下面是一个开发TCP应用程序客户端的一般流程,后面见具体代码:

  1. 创建客户端套接字对象

  2. 和服务端套接字建立连接

  3. 发送数据

  4. 接收数据

  5. 关闭客户端套接字

from socket import *
class Cilent_Socket:
    def __init__(self):
        self.tcp_client_socket = socket(AF_INET, SOCK_STREAM) # AF_INET指ipv4地址,SOCK_STREAM指TCP协议
        self.tcp_client_socket.connect(('192.168.137.1',8989))  #连接服务端,指定服务器ip和端口def run(self):
        while True:
            # 用户输入数据
            send_data = input("我:")
            if len(send_data)==0:
                 print('已断开连接!')
                 break
            if send_data == "quit" or send_data == "exit" or send_data =='Bye'or send_data =='bye':
                self.tcp_client_socket.send(send_data.encode("gbk"))
                recv_data = self.tcp_client_socket.recv(4096).decode('gbk')
                print('小美:', recv_data)
                self.tcp_client_socket.close()
                break
            self.tcp_client_socket.send(send_data.encode("gbk"))​            # 接收对方发送过来的数据,最大接收4096个字节
            recv_data = self.tcp_client_socket.recv(4096).decode('gbk')
            print('小美:', recv_data)
        # 关闭套接字
        self.tcp_client_socket.close()
def main():
    client = Cilent_Socket()
    client.run()
​if __name__ == '__main__':
    main()

上面代码中的__init__方法初始化了一个客户端套接字,并与服务器建立一个长连接,run()方法中用于和后台机器人发送消息和接收机器人给你返回的消息。

服务端

创建一个服务端程序的基本步骤是:

  1. 创建服务端端套接字对象

  2. 绑定端口号

  3. 设置监听

  4. 等待接受客户端的连接请求

  5. 接收数据

  6. 发送数据

  7. 关闭套接字

要创建一个能自动回复的机器人,只要循环接收用户输入的信息,将其输入的关键词进行判断,可以后台预先给定它对应的关键词对应给用户回复的信息即可,或者调用已知已经做好的API接口。下面两种情况会分别进行介绍。

1.自定义消息关键词回复

from socket import *
import time
import random​
class Server_Socket:
    def __init__(self):
        tcp_server_socket = socket(AF_INET, SOCK_STREAM)
        tcp_server_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, True) #设置端口复用
        tcp_server_socket.bind(('', 8989))  # 绑定服务器端口
        tcp_server_socket.listen(128)
        self.tcp_server_socket = tcp_server_socket
        self.chat_list = ['今天天气真好,和朋友出去玩一下吧','今天你学习了吗','又不知道吃什么','蓝瘦香菇','好嗨哟','去看电影吧','去吃好吃的'] # 定义初始化消息回复列表​
    def start(self):​
        client_socket, client_addr = self.tcp_server_socket.accept()
        while True:
            # 接收对方发送过来的数据
            recv_data = client_socket.recv(4096).decode('gbk')  # 接收4096个字节if len(recv_data) == 0:
                print("程序结束")
                break            # 下面一串是对用户的输入逻辑进行判断
            elif recv_data =="quit" or recv_data =="exit" or recv_data =='Bye' or recv_data =='bye' or recv_data =='再见':
                client_socket.send('再见'.encode('gbk'))
                break
            elif "你好" in recv_data or "hello" in recv_data:
                client_socket.send("你好".encode('gbk'))
            elif "sb" in recv_data or "SB" in recv_data or "傻" in recv_data or "二货" in recv_data :
                client_socket.send("你才傻,你全家都傻!!!".encode('gbk'))
            elif "贱" in recv_data or "蠢" in recv_data :
                client_socket.send("你个蠢货!".encode('gbk'))
            elif "吃" in recv_data or "hello" in recv_data:
                client_socket.send("红烧肉、东坡肘子...".encode('gbk'))
            elif "玩" in recv_data or "hello" in recv_data:
                client_socket.send("云南丽江不错!".encode('gbk'))
            elif "名字" in recv_data or "name" in recv_data:
                client_socket.send("我叫小美,编号9527,哈哈...".encode('gbk'))
            elif "时间" in recv_data or "time" in recv_data:
                client_socket.send(('现在时间是:'+time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))).encode('gbk')) # 返回当前时间
            else:
                self.chat_list.append(recv_data)  # 收集用户输入信息,丰富词汇
                rand_idx = random.randint(0, len(self.chat_list) - 1)                # 通过随机下标获取一条信息
                send_data = self.chat_list[rand_idx]                 # 将信息发送给客户端
                client_socket.send(send_data.encode('gbk'))        # 关闭为这个客户端服务的套接字,只要关闭了,就意味着为不能再为这个客户端服务了,如果还需要服务,只能再次重新连接
        client_socket.close()​
def main():
    server = Server_Socket()
    server.start()
​if __name__ == '__main__':
    main()

上面的代码是聊天机器人服务端代码,可和用户进行一般的闲聊,返回当前时间等,代码逻辑不复杂,优点是可以自行定制。

2.调用图灵机器人API实现自动回复

图灵机器人的接口可以实现的功能有:中文聊天,情感引擎等。要使用图灵的API,首先要去它官网进行注册,然后创建机器人,获取一个APIkey,然后才能使用它的API接口。下面是网址入口:

www.turingapi.com/

下面对它的API文档有用的一部分进行摘抄:

编码方式

UTF-8(调用图灵API的各个环节的编码方式均为UTF-8)

接口地址

openapi.tuling123.com/openapi/api…

请求方式

HTTP POST

请求参数

请求参数格式为 json 请求示例:

{    "reqType":0,    "perception": {        "inputText": {            "text": "附近的酒店"        },        "inputImage": {            "url": "imageUrl"        },        "selfInfo": {            "location": {                "city": "北京",                "province": "北京",                "street": "信息路"            }        }    },    "userInfo": {        "apiKey": "",        "userId": ""    }}

参数说明

参数类型是否必须取值范围说明
reqTypeintN-输入类型:0-文本(默认)、1-图片、2-音频
perception-Y-输入信息
userInfo-Y-用户参数

perception

参数类型是否必须取值范围说明
inputText-N-文本信息
inputImage-N-图片信息
inputMedia-N-音频信息
selfInfo-N-客户端属性

注意:输入参数必须包含inputText或inputImage或inputMedia!

inputText

参数类型是否必须取值范围说明
textStringY1-128字符直接输入文本

inputImage

参数类型是否必须取值范围说明
urlStringY图片地址

inputMedia

参数类型是否必须取值范围说明
urlStringY音频地址

selfInfo

参数类型是否必须取值范围说明
location-N-地理位置信息

location

参数类型是否必须取值范围说明
cityStringY-所在城市
provinceStringN-省份
streetStringN-街道

userInfo

参数类型是否必须取值范围说明
apiKeyStringY32位机器人标识
userIdStringY长度小于等于32位用户唯一标识
groupIdStringN长度小于等于64位群聊唯一标识
userIdNameStringN长度小于等于64位群内用户昵称

输出参数

输出示例:

  {    "intent": {        "code": 10005,        "intentName": "",        "actionName": "",        "parameters": {            "nearby_place": "酒店"        }    },    "results": [        {            "groupType": 1,            "resultType": "url",            "values": {                "url": "http://m.elong.com/hotel/0101/nlist/#indate=2016-12-10&outdate=2016-12-11&keywords=%E4%BF%A1%E6%81%AF%E8%B7%AF"            }        },        {            "groupType": 1,            "resultType": "text",            "values": {                "text": "亲,已帮你找到相关酒店信息"            }        }    ]}

参数说明

参数类型是否必须取值范围说明
intent-Y-请求意图
results-N-输出结果集

intent

参数类型是否包含取值范围说明
codeintY-输出功能code
intentNameStringN-意图名称
actionNameStringN-意图动作名称
parametersMapN-功能相关参数

results

参数类型是否包含取值范围说明
resultTypeStringY文本(text);连接(url);音频(voice);视频(video);图片(image);图文(news)输出类型
values-Y-输出值
groupTypeintY-‘组’编号:0为独立输出,大于0时可能包含同组相关内容 (如:音频与文本为一组时说明内容一致)

下面是针对文档来封装实现输入关键词来返回用户输入信息的函数代码:

import requests
import json
def get_response(msg):
    api = 'http://openapi.tuling123.com/openapi/api/v2' # 接口地址
    data = {        "perception": {            "inputText": {                "text": msg            },            "inputImage": {                "url": "imageUrl"            },            "selfInfo": {                "location": {                    "city": "成都",  # 参数必须指定地点                    "province": "四川", # 参数必须                    "street": "蜀西路"                }            }        },        "userInfo": {            "apiKey": '',  # 参数必须此处填入网站申请的key            "userId": ""        }    }
    data = json.dumps(data)  # 将字典格式转化为json格式,另外loads函数是将json转化为python中的字典
    print(data)
    print('=================================================================================')
    r = requests.post(api, data=data).json()  # 将post请求的结果转为json
    print(r)
    return r['results'][0]['values']['text'] # 返回的数据
​mes = get_response('天气') # 输入关键词

上面用到了python内置的request和json库,调用了几次发现有时返回的结果不太满意,不知道是不是没有买它套餐的原因。上一个版本的机器人服务端只实现了单用户,下面实现可以多用户聊天的版本:

import socket
import threading
import requests
import json​​
# 创建web服务器的类
class HttpWebServer:
    """初始化套接字对象"""def __init__(self, port):
        tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
        tcp_server_socket.bind(('', port))
        tcp_server_socket.listen(128)
        self.tcp_server_socket = tcp_server_socket
​    @staticmethod
    def get_response(msg):
        # 调用图灵机器人API
        api = 'http://openapi.tuling123.com/openapi/api/v2'
        data = {            "perception": {                "inputText": {                    "text": msg                },                "inputImage": {                    "url": "imageUrl"                },                "selfInfo": {                    "location": {                        "city": "成都",                        "province": "四川",                        "street": ""                    }                }            },            "userInfo": {                "apiKey": '',# 填入申请的key                "userId": ""            }        }
        data = json.dumps(data)  # 将字典格式转化为json格式,另外loads函数是将json转化为python中的字典
        print(data)
        # print('=================================================================================')
        r = requests.post(api, data=data).json()  # 将post请求的结果转为json
        print(r)
        return r['results'][0]['values']['text']  # 返回的数据
​    @staticmethod
    def client(new_socket):
        """新套接-请求-响应"""
        # 接受客户端消息
        while True:
            recv_data = (new_socket.recv(4096))
            recv_decode = recv_data.decode('utf-8')
            # 判断请求内容长度,若为0,则浏览器断开连接
            if len(recv_data) == 0:
                print('offline')
                new_socket.close()
                return​
            print('帅哥:' + recv_decode)
            response = HttpWebServer.get_response(recv_decode)
            new_socket.send(response.encode('utf-8'))
​​    def start(self):
        """开启服务器的方法"""
        while True:
            # 循环接受请求,并创建相应的套接字
            new_socket, ip_port = self.tcp_server_socket.accept()
            # 运用多线程实现多个客户端访问,并设置主线程守护
            sub_threading = threading.Thread(target=self.client, args=(new_socket,), daemon=True)
​            # 子线程开启
            sub_threading.start()​​
def main():
    """程序入口"""
    web_server = HttpWebServer(8989)
    web_server.start()
​if __name__ == '__main__':
    main()

上面采用了threading实现了可多用户聊天,并使用了守护主线程,防止了在主线程接受数据阻塞引起服务器崩溃的情况。