千葉県Go To EAT向けに店舗検索BOT（AI LINE BOT）を作った話(5)【配信サーバ構築】

<a href="https://qiita.com/Haradasn/items/3a0714f19974ef2b96c9">千葉県Go To EAT向けに店舗検索BOT（AI LINE BOT）を作った話(4)【AIモデル作成】</a>の続きです。

この記事にて、LINE Messaging APIの登録、配信サーバの構築を行います。

##LINE Messaging APIの利用
まずは、LINE Developersに登録します。
既存のLINEアカウントを持っていれば、無料で登録できます。

◆<a href"https://developers.line.biz/ja/">LINE Developers</a>

登録完了後、プロバイダを作成します。作成ボタンを押下します。
![Devhome.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/896062/6d4be7ec-ee88-f6ac-512e-694d3d2f1ae8.png)

![devpro作成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/896062/a7c90d76-b597-78d8-9a16-3825cce658d3.png)

チャネル設定画面から、Messaging APIを選択します。

![チャネル作成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/896062/0d6f2273-85fd-346d-b95d-74fe6a19ec40.png)

各チャネルの項目に任意の値を入力し、設定を保存します。

![チャネル設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/896062/82f9b5ad-0855-8dc7-1ba4-0225e01252e0.png)

これでチャネル作成は完了です。
![チャネル画面.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/896062/9b9232e4-4473-3b68-2f3f-cc6f039e1f5b.png)


##AWSサーバの構築
pythonで作ったAIモデルを動作させるためのサーバをAWS上に確保します。
まずは、AWSのユーザを登録します。
ユーザはあらかじめ以下を参考に作成して下さい。

◆参考<a href="https://aws.amazon.com/jp/register-flow/">AWSアカウント作成手順</a>

今回は、「無料」がコンセプトなので、無料枠が適用されるt.micro(Amazon linux2)を使います。

インスタンス作成画面からインスタンスを作成します。
![インスタンス作成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/896062/108ad2f9-913b-ee5a-02a1-660bce96e78f.png)
OSにAamazon Linux2 AMIを選択します。
![AMZLIN.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/896062/ecfbd087-0076-e7cf-4fdf-b7c1587054c7.png)
インスタンスタイプにt2.microを選択します。
![t2micro.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/896062/1102c418-505b-f423-0878-c8d983ded8b4.png)
確認画面からインスタンス作成ボタンを押下します。
![kakunin.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/896062/204687aa-cf24-2c9a-28b7-51bb8e109f38.png)

数分経つと、インスタンスが作成されます。
LINE Messageing APIとサーバはhttps通信する必要なため、このタイミングでセキュリティグループを設定しておきます。左ペイン「セキュリティグループ」を選択し、443のインバウンドアクセスを可能としておきます。
![SC.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/896062/70be73a5-e767-dcf8-8555-b2738401f559.png)

##ドメインの取得
https通信を可能とするためには、秘密鍵と証明書を発行する必要があります。
そのために、無料のドメインプロバイダに登録して、フリードメインを取得します。
以下のページを参考にしました。

◆参考：<a href="https://qiita.com/hanbaga324/items/da0c4f6fa8d365db10c9">freenomでドメインの取得</a>
##Let's Encryptによる秘密鍵/証明書の発行
以下のサイトを参考に秘密鍵/証明書を無料で発行します。

参考：<a href"https://qiita.com/daiki_44/items/a3616390f277722b97e0">無料でHTTPS化できる「Let's Encrypt」をやってみた ※install.sh付き</a>
##Flask＆https通信化
以下のページを参考にサーバ側でflask&https通信するプログラムを作成しました。
プログラムには、スマホから受け取った検索ワードを作成済のdoc2vecモデルに読み込ませ、結果を返却しています。
◆参考：<a href ="https://python.ms/flask-via-tls/">Flask を HTTPS 化する。<a/>

プログラム格納先
<a href="https://github.com/Haradasn/linebot">https://github.com/Haradasn/linebot</a>

```python:python_deamon.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import MeCab
import gensim.models.doc2vec as doc2vec
from gensim import models
from gensim.models.doc2vec import TaggedDocument
from flask import Flask, request, abort
import ssl
import pandas as pd
from linebot import (
   LineBotApi, WebhookHandler
)
from linebot.exceptions import (
   InvalidSignatureError
)
from linebot.models import (
   MessageEvent, TextMessage, TextSendMessage,
)
model = models.Doc2Vec.load('/opt/doc2vec.model')
df = pd.read_csv('/opt/database.csv', header=None)
def trim_doc(doc):
    lines = doc.splitlines()
    valid_lines = []
    is_valid = False
    horizontal_rule_cnt = 0
    break_cnt = 0
    for line in lines:
        if horizontal_rule_cnt < 2 and '-----' in line: 
            is_valid = horizontal_rule_cnt == 2
            continue
            break_cnt += 1
            is_valid = break_cnt != 3
            continue
        break_cnt = 0
        valid_lines.append(line)
    return ''.join(valid_lines)

def split_into_words(doc):
    mecab = MeCab.Tagger("-Ochasen")
    valid_doc = doc
    lines = mecab.parse(valid_doc).splitlines()
    words = []
    #print(lines)
    for line in lines:
        chunks = line.split('\t')
        if len(chunks) > 3 and (chunks[3].startswith('形容詞') or (chunks[3].startswith('名詞') and not chunks[3].startswith('名詞-数'))):
            words.append(chunks[0])
    return words

# 似た文章を探す
def search_similar_texts(words,df):
    x = model.infer_vector(words)
    most_similar_texts = model.docvecs.most_similar([x],topn=3)
    text=[[0 for i in range(4)] for j in range(3)]
    for idx,row in enumerate(df.iterrows()):
        for i,similar_text in enumerate(most_similar_texts):    
            if df[df.columns[4]][idx] == similar_text[0]:
                text[i][0] = similar_text[0]
                text[i][1] = df[df.columns[7]][idx]
                if df[df.columns[2]][idx] == "〇":
                    text[i][2] = "◆紙クーポン使用　　：可"
                else:
                    text[i][2] = "◆紙クーポン使用　　：不可"
                if df[df.columns[3]][idx] == "〇":
                    text[i][3] = "◆電子クーポン使用　：可"
                else:
                    text[i][3] = "◆電子クーポン使用　：不可"
                break
    return text
    #for similar_text in most_similar_texts:
    #    print()

# 似た単語を探す
def search_similar_word(words):
    #print(words)
    for word in words:
        #print(word)
        #print(word + ':')
        for result in model.most_similar(positive=word, topn=10):
            print(result[0])
app = Flask(__name__)
context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
context.load_cert_chain('/etc/letsencrypt/live/tsurushi.tk/fullchain.pem', '/etc/letsencrypt/live/tsurushi.tk/privkey.pem')
line_bot_api = LineBotApi('#LINE APIトークンを記載する')
handler = WebhookHandler('#LINE Developersに書いてあるシークレットを記載する。')



@app.route("/callback", methods=['POST'])
def callback():
   # get X-Line-Signature header value
   signature = request.headers['X-Line-Signature']

   # get request body as text
   body = request.get_data(as_text=True)
   app.logger.info("Request body: " + body)

   # handle webhook body
   try:
       handler.handle(body, signature)
   except InvalidSignatureError:
       print("Invalid signature. Please check your channel access token/channel secret.")
       abort(400)

   return 'OK'
@app.route('/')
def hello():
    return 'Hello World!'

@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
    #print("event.message.text=",event.message.text)
    words = split_into_words(event.message.text)
    text = []
    text = search_similar_texts(words,df)
    #print("text",text[0])
    line_bot_api.reply_message(
       event.reply_token,
       [
       TextSendMessage(text="1.こちらはいかがでしょうか\uDBC0\uDC78\n"+text[0][1]+"\n"+text[0][2]+"\n"+text[0][3]),
       TextSendMessage("2.こちらはいかがでしょうか\uDBC0\uDC9D\n"+text[1][1]+"\n"+text[1][2]+"\n"+text[1][3]),
       TextSendMessage("3.こちらはいかがでしょうか\uDBC0\uDC90\n"+text[2][1]+"\n"+text[2][2]+"\n"+text[2][3])
       ]
    )
if __name__ == "__main__":
   app.run(host='0.0.0.0', port=443,ssl_context=context, threaded=True)
#    app.run(host='0.0.0.0',port=80)
```

##LINE DevelopersへのURLの設定
最後に、先に取得していたドメインのURLを、LINE Developersの「Webhook URL」に登録します。
![hol.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/896062/7a17ad54-5dfd-ed99-8efe-bd1c160da944.png)

検証ボタンを押して、「成功」の文字が出たら、LINE Message APIとサーバの疎通は完了です！