寫一個自動回覆的聊天機器人

Simon_Zhou發表於2019-04-26
要寫一個自動聊天的機器人,底層必定離不開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實現了可多使用者聊天,並使用了守護主執行緒,防止了在主執行緒接受資料阻塞引起伺服器崩潰的情況。


相關文章