最近借用了女朋友的公號,感覺如果只是用來發文章,太浪費微信給提供的這些功能了。想了想,先從最簡單的開始,做一個聊天機器人吧。
使用Python實現聊天機器人的方案有多種:AIML、chatterBot以及圖靈聊天機器人和微軟小冰等。
考慮到以後可能會做一些定製化的需求,這裡我選擇了chatterBot
(github 專案地址:https://github.com/gunthercox/ChatterBot)。
chatterbot是一款python介面的,基於一系列規則和機器學習演算法完成的聊天機器人。具有結構清晰,可擴充套件性好,簡單實用的特點。
chatterBot 的工作流程如圖:
- 輸入模組(input adapter)從終端或者API等輸入源獲取資料
- 輸入源會被指定的邏輯處理模組(logic Adapter)分別處理,邏輯處理模組會匹配訓練集中已知的最接近輸入資料句子A,然後根據句子A去找到相關度最高的結果B,如果有多個邏輯處理模組返回了不同的結果,會返回一個相關度最高的結果。
- 輸出模組(output adapter)將匹配到的結果返回給終端或者API。
值得一說的是chatterBot 是一個模組化的專案,分為 input Adapter、logic Adapter、storage Adapter、output Adapter以及Trainer 模組。
logic Adapter是一個外掛式設計,主程式在啟動時會將使用者定義的所有邏輯處理外掛新增到logic context中,然後交MultiLogicAdapter 進行處理,MultiLogicAdapter 依次呼叫每個 logic Adapter,logic Adapter 被呼叫時先執行can_process 方式判斷輸入是否可以命中這個邏輯處理外掛。比如”今天天氣怎麼樣“這樣的問題顯然需要命中天氣邏輯處理外掛,這時時間邏輯處理外掛的can_process 則會返回False。在命中後logic Adapter 負責計算出對應的回答(Statement物件)以及可信度(confidence),MultiLogicAdapter會取可信度最高的回答,並進入下一步。
下面我們來看下 chatterBot 如何使用
chatterBot 安裝&使用
安裝
chatterBot 是使用Python編寫的,可以使用 pip 安裝:
pip install chatterbot複製程式碼
chatterBot 的中文對話要求Python3 以上版本,建議在Python3.x 環境下開發
測試
開啟iPython,輸入測試一下
In [1]: from chatterbot import ChatBot # import ChatBot
In [2]: momo = ChatBot('Momo', trainer='chatterbot.trainers.ChatterBotCorpusTrainer')
/Users/gs/.virtualenvs/py3/lib/python3.6/site-packages/chatterbot/storage/jsonfile.py:26: UnsuitableForProductionWarning: The JsonFileStorageAdapter is not recommended for production environments.
self.UnsuitableForProductionWarning # 這裡storage adapter 預設使用的是 json 格式儲存資料的,如果想在服務端部署,應該避免使用這種格式,因為實在是太慢了
In [3]: momo.train("chatterbot.corpus.chinese") # 指定訓練集,這裡我們使用中文
# 下邊是對話結果
In [4]: momo.get_response('你好')
Out[4]: <Statement text:你好>
In [5]: momo.get_response('怎麼了')
Out[5]: <Statement text:沒什麼.>
In [6]: momo.get_response('你知道它的所有內容嗎?')
Out[6]: <Statement text:優美勝於醜陋.>
In [7]: momo.get_response('你是一個程式設計師嗎?')
Out[7]: <Statement text:我是個程式設計師>
In [8]: momo.get_response('你使用什麼語言呢?')
Out[8]: <Statement text:我經常使用 Python, Java 和 C++ .>複製程式碼
這時你已經可以和機器人對話了,不過現在由於訓練資料太少,機器人只能返回簡單的對話。
這裡是預設的中文對話訓練資料 中文訓練資料地址:https://github.com/gunthercox/chatterbot-corpus/tree/master/chatterbot_corpus/data/chinese。
那麼我們怎麼新增訓練資料呢?
訓練機器人
chatterBot 內建了training class,自帶的方法有兩種,一種是使用通過輸入list 來訓練,比如 ["你好", "我不好"],後者是前者的回答,另一種是通過匯入Corpus 格式的檔案來訓練。也支援自定義的訓練模組,不過最終都是轉為上述兩種型別。
chatterBot 通過呼叫 train() 函式訓練,不過在這之前要先用 set_trainer() 來進行設定。例如:
In [12]: from chatterbot.trainers import ListTrainer # 匯入訓練模組的 ListTrainer 類
In [13]: momo.get_response('你叫什麼?') # 現在是答非所問,因為在這之前我們並沒有訓練過
Out[13]: <Statement text:我在烤蛋糕.>
In [14]: momo.set_trainer(ListTrainer) # 指定訓練方式
In [15]: momo.train(['你叫什麼?', '我叫魔魔!']) # 訓練
In [16]: momo.get_response('你叫什麼?') # 現在機器人已經可以回答了
Out[16]: <Statement text:我叫魔魔!>複製程式碼
訓練好的資料預設存在 ./database.db,這裡使用的是 jsondb。
對 chatterBot 的介紹先到這裡,具體用法可以參考文件:ChatterBot Tutorial:http://chatterbot.readthedocs.io/en/stable/tutorial.html
接下來,介紹如何在專案中使用 chatterBot。
使用 Sanic 建立專案
Sanic 是一個和類Flask 的基於Python3.5+的web框架,它編寫的程式碼速度特別快。
除了像Flask 以外,Sanic 還支援以非同步請求的方式處理請求。這意味著你可以使用新的 async/await 語法,編寫非阻塞的快速的程式碼。
對 Sanic 不瞭解的可以參考我之前的一篇文章: python web 框架 Sanci 快速入門,可以在公號輸入 【sanic】獲取文章地址。
這裡之所以使用 Sanic 是因為他和Flask 非常像,之前我一直使用Flask,並且它也是專門為Python3.5 寫的,使用到了協程。
首先建個專案,這裡專案我已經建好了,專案結構如下:
.
├── LICENSE
├── README.md
├── manage.py # 執行檔案 啟動專案 使用 python manage.py 命令
├── momo
│ ├── __init__.py
│ ├── app.py # 建立app 模組
│ ├── helper.py
│ ├── settings.py # 應用配置
│ └── views
│ ├── __init__.py
│ ├── hello.py # 測試模組
│ └── mweixin.py # 微信訊息處理模組
├── requirements.txt
└── supervisord.conf複製程式碼
原始碼我已經上傳到github,有興趣的可以看一下,也可以直接拉下來測試。
專案程式碼地址
我們先重點看下 hello.py
檔案 和 helper.py
。
# hello.py
# -*- coding: utf-8 -*-
from sanic import Sanic, Blueprint
from sanic.views import HTTPMethodView
from sanic.response import text
from momo.helper import get_momo_answer # 匯入獲取機器人回答獲取函式
blueprint = Blueprint('index', url_prefix='/')
class ChatBot(HTTPMethodView):
# 聊天機器人 http 請求處理邏輯
async def get(self, request):
ask = request.args.get('ask')
# 先獲取url 引數值 如果沒有值,返回 '你說啥'
if ask:
answer = get_momo_answer(ask)
return text(answer)
return text('你說啥?')
blueprint.add_route(ChatBot.as_view(), '/momo')複製程式碼
# helper.py
from chatterbot import ChatBot
momo_chat = ChatBot(
'Momo',
# 指定儲存方式 使用mongodb 儲存資料
storage_adapter='chatterbot.storage.MongoDatabaseAdapter',
# 指定 logic adpater 這裡我們指定三個
logic_adapters=[
"chatterbot.logic.BestMatch",
"chatterbot.logic.MathematicalEvaluation", # 數學模組
"chatterbot.logic.TimeLogicAdapter", # 時間模組
],
input_adapter='chatterbot.input.VariableInputTypeAdapter',
output_adapter='chatterbot.output.OutputAdapter',
database='chatterbot',
read_only=True
)
def get_momo_answer(content):
# 獲取機器人返回結果函式
response = momo_chat.get_response(content)
if isinstance(response, str):
return response
return response.text複製程式碼
執行命令 python manage.py
啟動專案。
在瀏覽器訪問url: http://0.0.0.0:8000/momo?ask=你是程式設計師嗎
到這裡,我們已經啟動了一個web 專案,可以通過訪問url 的方式和機器人對話,是時候接入微信公號了!
接入微信公眾號
前提
- 擁有一個可以使用的微信公眾號(訂閱號服務號都可以,如果沒有,可以使用微信提供的測試賬號)
- 擁有一個外網可以訪問的伺服器(vps 或公有云都可以 aws 新使用者免費使用一年,可以試試)
- 伺服器配置了python3 環境,(建議使用 virtualenvwrapper 配置虛擬環境)
微信設定
登入微信公眾號: mp.weixin.qq.com
開啟:開發>基本配置
檢視公號開發資訊:
開啟伺服器配置:
設定請求url,這裡是你配置的url(需要外網可訪問,只能是80或443埠)
填寫token和EncodingAESKey,這裡我選擇的是相容模式,既有明文方便除錯,又有資訊加密。
詳細配置可以參考官方文件:接入指南
如果你的 伺服器地址
已經配置完成,現在點選提交應該就成功了。如果沒有成功我們接下來看怎麼配置伺服器地址。
程式碼示例
先看下 微信請求的檢視程式碼:
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from six import StringIO
import re
import xmltodict
from chatterbot.trainers import ListTrainer
from sanic import Blueprint
from sanic.views import HTTPMethodView
from sanic.response import text
from sanic.exceptions import ServerError
from weixin import WeixinMpAPI
from weixin.lib.WXBizMsgCrypt import WXBizMsgCrypt
from momo.settings import Config
blueprint = Blueprint('weixin', url_prefix='/weixin')
class WXRequestView(HTTPMethodView):
def _get_args(self, request):
# 獲取微信請求引數,加上token 拼接為完整的請求引數
params = request.raw_args
if not params:
raise ServerError("invalid params", status_code=400)
args = {
'mp_token': Config.WEIXINMP_TOKEN,
'signature': params.get('signature'),
'timestamp': params.get('timestamp'),
'echostr': params.get('echostr'),
'nonce': params.get('nonce'),
}
return args
def get(self, request):
# 微信驗證伺服器這一步是get 請求,引數可以使用 request.raw_args 獲取
args = self._get_args(request)
weixin = WeixinMpAPI(**args) # 這裡我使用了 第三方包 python-weixin 可以直接例項化一個WeixinMpAPI物件
if weixin.validate_signature(): # 驗證引數合法性
# 如果引數爭取,我們將微信發過來的echostr引數再返回給微信,否則返回 fail
return text(args.get('echostr') or 'fail')
return text('fail')
blueprint.add_route(WXRequestView.as_view(), '/request')複製程式碼
這裡處理微信請求我使用的是 我用python 寫的 微信SDK python-weixin,可以使用 pip 安裝:
pip install python-weixin複製程式碼
這個包最新版本對Python3 加密解密有點問題,可以直接從github 安裝:
pip install git+https://github.com/zongxiao/python-weixin.git@py3複製程式碼
然後更新 app.py 檔案:
# -*- coding: utf-8 -*-
from sanic import Sanic
from momo.settings import Config
def create_app(register_bp=True, test=False):
# 建立app
app = Sanic(__name__)
if test:
app.config['TESTING'] = True
# 從object 匯入配置
app.config.from_object(Config)
register_blueprints(app)
return app
def register_blueprints(app):
from momo.views.hello import blueprint as hello_bp
from momo.views.mweixin import blueprint as wx_bp
app.register_blueprint(hello_bp)
# 註冊 wx_bp
app.register_blueprint(wx_bp)複製程式碼
詳細程式碼參考github: 微信聊天機器人 momo
接入聊天機器人
現在我們公號已經接入了自己的服務,是時候接入微信聊天機器人。
微信聊天機器人的工作流程如下:
看我們訊息邏輯處理程式碼:
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from six import StringIO
import re
import xmltodict
from chatterbot.trainers import ListTrainer
from sanic import Blueprint
from sanic.views import HTTPMethodView
from sanic.response import text
from sanic.exceptions import ServerError
from weixin import WeixinMpAPI
from weixin.reply import TextReply
from weixin.response import WXResponse as _WXResponse
from weixin.lib.WXBizMsgCrypt import WXBizMsgCrypt
from momo.settings import Config
from momo.helper import validate_xml, smart_str, get_momo_answer
from momo.media import media_fetch
blueprint = Blueprint('weixin', url_prefix='/weixin')
appid = smart_str(Config.WEIXINMP_APPID)
token = smart_str(Config.WEIXINMP_TOKEN)
encoding_aeskey = smart_str(Config.WEIXINMP_ENCODINGAESKEY)
# 關注後自動返回的文案
AUTO_REPLY_CONTENT = """
Hi,朋友!
這是我媽四月的公號,我是魔魔,我可以陪你聊天呦!
我還能"記賬",輸入"記賬"會有驚喜呦!
<a href="https://mp.weixin.qq.com/mp/profile_ext?action=home&__biz=MzAwNjI5MjAzNw==&scene=124#wechat_redirect">歷史記錄</a>
"""
class ReplyContent(object):
_source = 'value'
def __init__(self, event, keyword, content=None, momo=True):
self.momo = momo
self.event = event
self.content = content
self.keyword = keyword
if self.event == 'scan':
pass
@property
def value(self):
if self.momo:
answer = get_momo_answer(self.content)
return answer
return ''
class WXResponse(_WXResponse):
auto_reply_content = AUTO_REPLY_CONTENT
def _subscribe_event_handler(self):
# 關注公號後的處理邏輯
self.reply_params['content'] = self.auto_reply_content
self.reply = TextReply(**self.reply_params).render()
def _unsubscribe_event_handler(self):
# 取關後的處理邏輯,取關我估計會哭吧
pass
def _text_msg_handler(self):
# 文字訊息處理邏輯 聊天機器人的主要邏輯
event_key = 'text'
content = self.data.get('Content')
reply_content = ReplyContent('text', event_key, content)
self.reply_params['content'] = reply_content.value
self.reply = TextReply(**self.reply_params).render()
class WXRequestView(HTTPMethodView):
def _get_args(self, request):
params = request.raw_args
if not params:
raise ServerError("invalid params", status_code=400)
args = {
'mp_token': Config.WEIXINMP_TOKEN,
'signature': params.get('signature'),
'timestamp': params.get('timestamp'),
'echostr': params.get('echostr'),
'nonce': params.get('nonce'),
}
return args
def get(self, request):
args = self._get_args(request)
weixin = WeixinMpAPI(**args)
if weixin.validate_signature():
return text(args.get('echostr') or 'fail')
return text('fail')
def _get_xml(self, data):
post_str = smart_str(data)
# 驗證xml 格式是否正確
validate_xml(StringIO(post_str))
return post_str
def _decrypt_xml(self, params, crypt, xml_str):
# 解密訊息
nonce = params.get('nonce')
msg_sign = params.get('msg_signature')
timestamp = params.get('timestamp')
ret, decryp_xml = crypt.DecryptMsg(xml_str, msg_sign,
timestamp, nonce)
return decryp_xml, nonce
def _encryp_xml(self, crypt, to_xml, nonce):
# 加密訊息
to_xml = smart_str(to_xml)
ret, encrypt_xml = crypt.EncryptMsg(to_xml, nonce)
return encrypt_xml
def post(self, request):
# 獲取微信伺服器傳送的請求引數
args = self._get_args(request)
weixin = WeixinMpAPI(**args)
if not weixin.validate_signature(): # 驗證引數合法性
raise AttributeError("Invalid weixin signature")
xml_str = self._get_xml(request.body) # 獲取form data
crypt = WXBizMsgCrypt(token, encoding_aeskey, appid)
decryp_xml, nonce = self._decrypt_xml(request.raw_args, crypt, xml_str) # 解密
xml_dict = xmltodict.parse(decryp_xml)
xml = WXResponse(xml_dict)() or 'success' # 使用WXResponse 根據訊息獲取機器人返回值
encryp_xml = self._encryp_xml(crypt, xml, nonce) # 加密訊息
return text(encryp_xml or xml) # 回應微信請求
blueprint.add_route(WXRequestView.as_view(), '/request')複製程式碼
可以看到,我處理微信請求返回結果比較簡單,也是使用的 python-weixin 包封裝的介面,
主要的處理邏輯是 WXResponse。
這裡需要注意的是,如果伺服器在5秒內沒有響應微信伺服器會重試。為了加快響應速度,不要在伺服器 將 chatterBot 的 storage adapter 設定為使用 jsondb。
上邊這些就是,微信聊天機器人的主要處理邏輯,我們執行服務,示例如下:
可以看到這裡聊天機器人也可以做簡單的數學運算和報時,是因為我在上邊指定處理邏輯的時候新增了數學模組和時間模組:
momo_chat = ChatBot(
'Momo',
# 指定儲存方式 使用mongodb 儲存資料
storage_adapter='chatterbot.storage.MongoDatabaseAdapter',
# 指定 logic adpater 這裡我們指定三個
logic_adapters=[
"chatterbot.logic.BestMatch",
"chatterbot.logic.MathematicalEvaluation", # 數學模組
"chatterbot.logic.TimeLogicAdapter", # 時間模組
],
input_adapter='chatterbot.input.VariableInputTypeAdapter',
output_adapter='chatterbot.output.OutputAdapter',
database='chatterbot',
read_only=True
)複製程式碼
到這裡,微信機器人的搭建就完成了,詳細程式碼已經長傳到了 github: https://github.com/gusibi/momo/tree/chatterbot,感興趣的可以參考一下。
參考連結
- ChatterBot 專案地址:https://github.com/gunthercox/ChatterBot
- ChatterBot Tutorial:http://chatterbot.readthedocs.io/en/stable/tutorial.html
- 用Python快速實現一個聊天機器人:http://www.jianshu.com/p/d1333fde266f
- 基於Python-ChatterBot搭建不同adapter的聊天機器人:https://ask.hellobi.com/blog/guodongwei1991/7626
- 擁有自動學習的 Python 機器人 - ChatterBot:https://kantai235.github.io/2017/03/16/ChatterBotTeaching/
- 使用 ChatterBot構建聊天機器人:https://www.biaodianfu.com/chatterbot.html
- python-weixin sdk: https://github.com/gusibi/python-weixin
預告
這裡,聊天機器人還是比較簡單的只能回覆簡單的對話,下一篇將要結束如何在公號訓練機器人以及一個更實用的功能,如何讓公號變成一個部落格寫作助手。
最後,感謝女朋友支援。
歡迎關注(April_Louisa) | 請我喝芬達 |
---|---|