如何用 Python 和 Flask 建立部署一個 Facebook Messenger 機器人

Konstantinos Tsaprailis發表於2016-08-03

這是我建立一個簡單的 Facebook Messenger 機器人的記錄。功能很簡單,它是一個回顯機器人,只是列印回使用者寫了什麼。

回顯伺服器類似於伺服器的“Hello World”例子。

這個專案的目的不是建立最好的 Messenger 機器人,而是讓你瞭解如何建立一個小型機器人和每個事物是如何整合起來的。

技術棧

使用到的技術棧:

  • Heroku 做後端主機。免費級足夠這個等級的教程。回顯機器人不需要任何種類的資料持久,所以不需要資料庫。
  • Python 是我們選擇的語言。版本選擇 2.7,雖然它移植到 Pyhton 3 很容易,只需要很少的改動。
  • Flask 作為網站開發框架。它是非常輕量的框架,用在小型工程或微服務是非常完美的。
  • 最後 Git 版本控制系統用來維護程式碼和部署到 Heroku。
  • 值得一提:Virtualenv。這個 python 工具是用來建立清潔的 python 庫“環境”的,這樣你可以只安裝必要的需求和最小化應用的大小。

機器人架構

Messenger 機器人是由一個響應兩種請求的伺服器組成的:

  • GET 請求被用來認證。他們與你註冊的 FaceBook 認證碼一同被 Messenger 發出。
  • POST 請求被用來實際的通訊。典型的工作流是,機器人將透過使用者傳送帶有訊息資料的 POST 請求而建立通訊,然後我們將處理這些資料,併發回我們的 POST 請求。如果這個請求完全成功(返回一個 200 OK 狀態碼),我們也將響應一個 200 OK 狀態碼給初始的 Messenger請求。

這個教程應用將託管到 Heroku,它提供了一個優雅而簡單的部署應用的介面。如前所述,免費級可以滿足這個教程。

在應用已經部署並且執行後,我們將建立一個 Facebook 應用然後連線它到我們的應用,以便 Messenger 知道傳送請求到哪,這就是我們的機器人。

機器人伺服器

基本的伺服器程式碼可以在 Github 使用者 hult(Magnus Hult)Chatbot 專案上獲取,做了一些只回顯訊息的程式碼修改和修正了一些我遇到的錯誤。最終版本的伺服器程式碼如下:

from flask import Flask, request
import json
import requests

app = Flask(__name__)

### 這需要填寫被授予的頁面通行令牌(PAT)
### 它由將要建立的 Facebook 應用提供。
PAT = ''

@app.route('/', methods=['GET'])
def handle_verification():
  print "Handling Verification."
  if request.args.get('hub.verify_token', '') == 'my_voice_is_my_password_verify_me':
    print "Verification successful!"
    return request.args.get('hub.challenge', '')
  else:
    print "Verification failed!"
    return 'Error, wrong validation token'

@app.route('/', methods=['POST'])
def handle_messages():
  print "Handling Messages"
  payload = request.get_data()
  print payload
  for sender, message in messaging_events(payload):
    print "Incoming from %s: %s" % (sender, message)
    send_message(PAT, sender, message)
  return "ok"

def messaging_events(payload):
  """Generate tuples of (sender_id, message_text) from the
  provided payload.
  """
  data = json.loads(payload)
  messaging_events = data["entry"][0]["messaging"]
  for event in messaging_events:
    if "message" in event and "text" in event["message"]:
      yield event["sender"]["id"], event["message"]["text"].encode('unicode_escape')
    else:
      yield event["sender"]["id"], "I can't echo this"


def send_message(token, recipient, text):
  """Send the message text to recipient with id recipient.
  """

  r = requests.post("https://graph.facebook.com/v2.6/me/messages",
    params={"access_token": token},
    data=json.dumps({
      "recipient": {"id": recipient},
      "message": {"text": text.decode('unicode_escape')}
    }),
    headers={'Content-type': 'application/json'})
  if r.status_code != requests.codes.ok:
    print r.text

if __name__ == '__main__':
  app.run()

讓我們分解程式碼。第一部分是引入所需的依賴:

from flask import Flask, request
import json
import requests

接下來我們定義兩個函式(使用 Flask 特定的 app.route 裝飾器),用來處理到我們的機器人的 GET 和 POST 請求。

@app.route('/', methods=['GET'])
def handle_verification():
  print "Handling Verification."
  if request.args.get('hub.verify_token', '') == 'my_voice_is_my_password_verify_me':
    print "Verification successful!"
    return request.args.get('hub.challenge', '')
  else:
    print "Verification failed!"
    return 'Error, wrong validation token'

當我們建立 Facebook 應用時,verify_token 物件將由我們宣告的 Messenger 傳送。我們必須自己來校驗它。最後我們返回“hub.challenge”給 Messenger。

處理 POST 請求的函式更有意思一些:

@app.route('/', methods=['POST'])
def handle_messages():
  print "Handling Messages"
  payload = request.get_data()
  print payload
  for sender, message in messaging_events(payload):
    print "Incoming from %s: %s" % (sender, message)
    send_message(PAT, sender, message)
  return "ok"

當被呼叫時,我們抓取訊息載荷,使用函式 messaging_events 來拆解它,並且提取發件人身份和實際傳送的訊息,生成一個可以迴圈處理的 python 迭代器。請注意 Messenger 傳送的每個請求有可能多於一個訊息。

def messaging_events(payload):
  """Generate tuples of (sender_id, message_text) from the
  provided payload.
  """
  data = json.loads(payload)
  messaging_events = data["entry"][0]["messaging"]
  for event in messaging_events:
    if "message" in event and "text" in event["message"]:
      yield event["sender"]["id"], event["message"]["text"].encode('unicode_escape')
    else:
      yield event["sender"]["id"], "I can't echo this"

對每個訊息迭代時,我們會呼叫 send_message 函式,然後我們使用 Facebook Graph messages API 對 Messenger 發回 POST 請求。在這期間我們一直沒有回應我們阻塞的原始 Messenger請求。這會導致超時和 5XX 錯誤。

上述情況是我在解決遇到錯誤時發現的,當使用者傳送表情時實際上是傳送的 unicode 識別符號,但是被 Python 錯誤的編碼了,最終我們發回了一些亂碼。

這個發回 Messenger 的 POST 請求將永遠不會完成,這會導致給初始請求返回 5xx 狀態碼,顯示服務不可用。

透過使用 encode('unicode_escape') 封裝訊息,然後在我們傳送回訊息前用 decode('unicode_escape') 解碼訊息就可以解決。

def send_message(token, recipient, text):
  """Send the message text to recipient with id recipient.
  """

  r = requests.post("https://graph.facebook.com/v2.6/me/messages",
    params={"access_token": token},
    data=json.dumps({
      "recipient": {"id": recipient},
      "message": {"text": text.decode('unicode_escape')}
    }),
    headers={'Content-type': 'application/json'})
  if r.status_code != requests.codes.ok:
    print r.text

部署到 Heroku

一旦程式碼已經建立成我想要的樣子時就可以進行下一步。部署應用。

那麼,該怎麼做?

我之前在 Heroku 上部署過應用(主要是 Rails),然而我總是遵循某種教程做的,所用的配置是建立好了的。而在本文的情況下,我就需要從頭開始。

幸運的是有官方 Heroku 文件來幫忙。這篇文件很好地說明了執行應用程式所需的最低限度。

長話短說,我們需要的除了我們的程式碼還有兩個檔案。第一個檔案是“requirements.txt”,它列出了執行應用所依賴的庫。

需要的第二個檔案是“Procfile”。這個檔案通知 Heroku 如何執行我們的服務。此外這個檔案只需要一點點內容:

web: gunicorn echoserver:app 

Heroku 對它的解讀是,我們的應用透過執行 echoserver.py 啟動,並且應用將使用 gunicorn 作為 Web 伺服器。我們使用一個額外的網站伺服器是因為與效能相關,在上面的 Heroku 文件裡對此解釋了:

Web 應用程式併發處理傳入的 HTTP 請求比一次只處理一個請求的 Web 應用程式會更有效利地用測試機的資源。由於這個原因,我們建議使用支援併發請求的 Web 伺服器來部署和執行產品級服務。

Django 和 Flask web 框架提供了一個方便的內建 Web 伺服器,但是這些阻塞式伺服器一個時刻只能處理一個請求。如果你部署這種服務到 Heroku 上,你的測試機就會資源利用率低下,應用會感覺反應遲鈍。

Gunicorn 是一個純 Python 的 HTTP 伺服器,用於 WSGI 應用。允許你在單獨一個測試機內透過執行多 Python 程式的方式來併發的執行各種 Python 應用。它在效能、靈活性和配置簡易性方面取得了完美的平衡。

回到我們之前提到過的“requirements.txt”檔案,讓我們看看它如何結合 Virtualenv 工具。

很多情況下,你的開發機器也許已經安裝了很多 python 庫。當部署應用時你不想全部載入那些庫,但是辨認出你實際使用哪些庫很困難。

Virtualenv 可以建立一個新的空白虛擬環境,以便你可以只安裝你應用所需要的庫。

你可以執行如下命令來檢查當前安裝了哪些庫:

kostis@KostisMBP ~ $ pip freeze
cycler==0.10.0
Flask==0.10.1
gunicorn==19.6.0
itsdangerous==0.24
Jinja2==2.8
MarkupSafe==0.23
matplotlib==1.5.1
numpy==1.10.4
pyparsing==2.1.0
python-dateutil==2.5.0
pytz==2015.7
requests==2.10.0
scipy==0.17.0
six==1.10.0
virtualenv==15.0.1
Werkzeug==0.11.10

注意:pip 工具應該已經與 Python 一起安裝在你的機器上。如果沒有,檢視官方網站如何安裝它。

現在讓我們使用 Virtualenv 來建立一個新的空白環境。首先我們給我們的專案建立一個新資料夾,然後進到目錄下:

kostis@KostisMBP projects $ mkdir echoserver
kostis@KostisMBP projects $ cd echoserver/
kostis@KostisMBP echoserver $

現在來建立一個叫做 echobot 的新環境。執行下面的 source 命令啟用它,然後使用 pip freeze 檢查,我們能看到現在是空的。

kostis@KostisMBP echoserver $ virtualenv echobot
kostis@KostisMBP echoserver $ source echobot/bin/activate
(echobot) kostis@KostisMBP echoserver $ pip freeze
(echobot) kostis@KostisMBP echoserver $

我們可以安裝需要的庫。我們需要是 flask、gunicorn 和 requests,它們被安裝後我們就建立 requirements.txt 檔案:

(echobot) kostis@KostisMBP echoserver $ pip install flask
(echobot) kostis@KostisMBP echoserver $ pip install gunicorn
(echobot) kostis@KostisMBP echoserver $ pip install requests
(echobot) kostis@KostisMBP echoserver $ pip freeze
click==6.6
Flask==0.11
gunicorn==19.6.0
itsdangerous==0.24
Jinja2==2.8
MarkupSafe==0.23
requests==2.10.0
Werkzeug==0.11.10
(echobot) kostis@KostisMBP echoserver $ pip freeze > requirements.txt

上述完成之後,我們用 python 程式碼建立 echoserver.py 檔案,然後用之前提到的命令建立 Procfile,我們最終的檔案/資料夾如下:

(echobot) kostis@KostisMBP echoserver $ ls
Procfile     echobot     echoserver.py   requirements.txt

我們現在準備上傳到 Heroku。我們需要做兩件事。第一是如果還沒有安裝 Heroku toolbet,就安裝它(詳見 Heroku)。第二是透過 Heroku 網頁介面建立一個新的 Heroku 應用。

點選右上的大加號然後選擇“Create new app”。

為你的應用選擇一個名字,然後點選“Create App”。

你將會重定向到你的應用的控制皮膚,在那裡你可以找到如何部署你的應用到 Heroku 的細節說明。

(echobot) kostis@KostisMBP echoserver $ heroku login
(echobot) kostis@KostisMBP echoserver $ git init
(echobot) kostis@KostisMBP echoserver $ heroku git:remote -a <myappname>
(echobot) kostis@KostisMBP echoserver $ git add .
(echobot) kostis@KostisMBP echoserver $ git commit -m "Initial commit"
(echobot) kostis@KostisMBP echoserver (master) $ git push heroku master
...
remote:        https://<myappname>.herokuapp.com/ deployed to Heroku
...
(echobot) kostis@KostisMBP echoserver (master) $ heroku config:set WEB_CONCURRENCY=3

如上,當你推送你的修改到 Heroku 之後,你會得到一個用於公開訪問你新建立的應用的 URL。儲存該 URL,下一步需要它。

建立這個 Facebook 應用

讓我們的機器人可以工作的最後一步是建立這個我們將連線到其上的 Facebook 應用。Facebook 通常要求每個應用都有一個相關頁面,所以我們來建立一個

接下來我們去 Facebook 開發者專頁,點選右上角的“My Apps”按鈕並選擇“Add a New App”。不要選擇建議的那個,而是點選“basic setup”。填入需要的資訊並點選“Create App Id”,然後你會重定向到新的應用頁面。

在 “Products” 選單之下,點選“+ Add Product” ,然後在“Messenger”下點選“Get Started”。跟隨這些步驟設定 Messenger,當完成後你就可以設定你的 webhooks 了。Webhooks 簡單的來說是你的服務所用的 URL 的名稱。點選 “Setup Webhooks” 按鈕,並新增該 Heroku 應用的 URL (你之前儲存的那個)。在校驗元組中寫入 ‘myvoiceismypasswordverifyme’。你可以寫入任何你要的內容,但是不管你在這裡寫入的是什麼內容,要確保同時修改程式碼中 handle_verification 函式。然後勾選 “messages” 選項。

點選“Verify and Save” 就完成了。Facebook 將訪問該 Heroku 應用並校驗它。如果不工作,可以試試執行:

(echobot) kostis@KostisMBP heroku logs -t

然後看看日誌中是否有錯誤。如果發現錯誤, Google 搜尋一下可能是最快的解決方法。

最後一步是取得頁面訪問元組(PAT),它可以將該 Facebook 應用於你建立好的頁面連線起來。

從下拉選單中選擇你建立好的頁面。這會在“Page Access Token”(PAT)下面生成一個字串。點選複製它,然後編輯 echoserver.py 檔案,將其貼入 PAT 變數中。然後在 Git 中新增、提交併推送該修改。

(echobot) kostis@KostisMBP echoserver (master) $ git add .
(echobot) kostis@KostisMBP echoserver (master) $ git commit -m "Initial commit"
(echobot) kostis@KostisMBP echoserver (master) $ git push heroku master

最後,在 Webhooks 選單下再次選擇你的頁面並點選“Subscribe”。

現在去訪問你的頁面並建立會話:

成功了,機器人回顯了!

注意:除非你要將這個機器人用在 Messenger 上測試,否則你就是機器人唯一響應的那個人。如果你想讓其他人也試試它,到 Facebook 開發者專頁中,選擇你的應用、角色,然後新增你要新增的測試者。

總結

這對於我來說是一個非常有用的專案,希望它可以指引你找到開始的正確方向。官方的 Facebook 指南有更多的資料可以幫你學到更多。

你可以在 Github 上找到該專案的程式碼。

如果你有任何評論、勘誤和建議,請隨時聯絡我。


via: http://tsaprailis.com/2016/06/02/How-to-build-and-deploy-a-Facebook-Messenger-bot-with-Python-and-Flask-a-tutorial/

作者:Konstantinos Tsaprailis 譯者:wyangsun 校對:wxy

本文由 LCTT 原創翻譯,Linux中國 榮譽推出

相關文章