微信公號DIY:一小時搭建微信聊天機器人

最近借用了女友的公號,感受若是隻是用來發文章,太浪費微信給提供的這些功能了。想了想,先從最簡單的開始,作一個聊天機器人吧。html

使用Python實現聊天機器人的方案有多種:AIML、chatterBot以及圖靈聊天機器人和微軟小冰等。python

考慮到之後可能會作一些定製化的需求,這裏我選擇了chatterBotgithub 項目地址:https://github.com/gunthercox/ChatterBot)。git

chatterbot是一款python接口的,基於一系列規則和機器學習算法完成的聊天機器人。具備結構清晰,可擴展性好,簡單實用的特色。程序員

chatterBot 的工做流程如圖:github

chatterBot 工做流程

  1. 輸入模塊(input adapter)從終端或者API等輸入源獲取數據web

  2. 輸入源會被指定的邏輯處理模塊(logic Adapter)分別處理,邏輯處理模塊會匹配訓練集中已知的最接近輸入數據句子A,而後根據句子A去找到相關度最高的結果B,若是有多個邏輯處理模塊返回了不一樣的結果,會返回一個相關度最高的結果。算法

  3. 輸出模塊(output adapter)將匹配到的結果返回給終端或者API。mongodb

值得一說的是chatterBot 是一個模塊化的項目,分爲 input Adapter、logic Adapter、storage Adapter、output Adapter以及Trainer 模塊。json

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 的方式和機器人對話,是時候接入微信公號了!

接入微信公衆號

前提

  1. 擁有一個可使用的微信公衆號(訂閱號服務號均可以,若是沒有,可使用微信提供的測試帳號)

  2. 擁有一個外網能夠訪問的服務器(vps 或公有云均可以 aws 新用戶無償使用一年,能夠試試)

  3. 服務器配置了python3 環境,(建議使用 virtualenvwrapper 配置虛擬環境)

微信設置

登陸微信公衆號: https://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,感興趣的能夠參考一下。

參考連接

預告

這裏,聊天機器人仍是比較簡單的只能回覆簡單的對話,下一篇將要結束如何在公號訓練機器人以及一個更實用的功能,如何讓公號變成一個博客寫做助手。


最後,感謝女友支持。

歡迎關注(April_Louisa) 請我喝芬達
歡迎關注 請我喝芬達
相關文章
相關標籤/搜索