Develop Japan Financial & Stock app with GCP Free Tier Part4
#JAはじめに
Always Freeプロダクト枠を使用し、決算書による財務分析&株価分析Webアプリを作ってみる
第四弾。
今回はキーワードを入力すると該当銘柄を教えてくれるチャットボット用のサーバを実装してみます。
設計
フロントのチャット部分はチャットライブラリ(https://github.com/riversun/chatux)を使用し、
バックエンドはここまでGCE,GAEと使ってきたのと、そこまで大きな処理を実装するわけでは
ないので、今回はコンテナをデプロイできるサーバレスサービスのCloud Runを使用します。
ローカルのコンテナ環境で動作テストをし、問題なければCloud Buildでコンテナをビルドし、
Cloud Runにデプロイします。
フローとしては、下記になります。
Docker
コンテナで実行するために、Dockerfileをしたためます。
適宜必要なモジュールをインストールする記述を記載します。
# Use the official lightweight Python image.
# https://hub.docker.com/_/python
FROM python:3.7-slim
# Copy local code to the container image.
ENV APP_HOME /app
WORKDIR $APP_HOME
COPY . ./
# Install production dependencies.
RUN pip install Flask gunicorn flask_cors google-cloud-storage pandas
# Run the web service on container startup. Here we use the gunicorn
# webserver, with one worker process and 8 threads.
# For environments with multiple CPU cores, increase the number of workers
# to be equal to the cores available.
CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 app:app
またCloud Buildに投げるときはDockerfileだけでも問題ないですが、
ローカルでテストしよすいようにdocker-composeも使用します。
ということでyamlファイルは以下。
version: '3'
services:
backend:
build: ./backend
image: backend-image
container_name: backend1
restart: always
ports:
- 8000:8000
environment:
PORT: 8000
フロントエンド
フロントのチャット部分はチャットライブラリ(https://github.com/riversun/chatux)を使用し、
htmlファイルに埋め込む形で記載します。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>銘柄検索ボット</title>
</head>
<body>
<script src="https://riversun.github.io/chatux/chatux.min.js"></script>
<script>
const chatux = new ChatUx();
const initParam =
{
renderMode: 'auto',
api: {
//echo chat server
endpoint: 'http://localhost:8080/chat',
method: 'GET',
dataType: 'json'
},
bot: {
botPhoto: 'https://1.bp.blogspot.com/-U-God4u2s7Q/VdLsEO8pS9I/AAAAAAAAw2w/CucJpIwog34/s400/vegetable_kyouyasai_syougoin_daikon.png',
humanPhoto: null,
widget: {
sendLabel: '検索',
placeHolder: 'キーワードを入力してください'
}
},
window: {
title: '銘柄検索ボット',
infoUrl: 'https://github.com/RuBisCO28/KabuChatServer'
}
};
chatux.init(initParam);
chatux.start(true);
</script>
</body>
</html>
起動すると下記のようなbotが画面右下に出現します。
検索対象のデータ
今回、検索対象のデータはedinetコードリストに 企業説明を加えた下記のようなデータとなります。
8801, 3月31日,三井不動産株式会社,不動産業,東証1部,三菱地所と並ぶ総合不動産の双璧。ビル賃貸主力。マンション分譲、非保有不動産事業を拡大
4519,12月31日,中外製薬株式会社,医薬品,東証1部,ロシュ傘下で成長続ける異色の医薬品大手。抗体・バイオで先行、抗がん剤、骨・関節領域に強い```
チャットサーバ
言語はPython、フレームワークはFlaskで構築してみます。
ローカルでサーバをたてて、csv形式のedinetデータを読み込み、dataframeに流します。
肝心のチャットUIとチャットサーバのやり取りはjson形式で行います。
/chat以降のパラメーターでキーワードとなるテキストを受け取り、
該当するテキストがないかをcsv形式のedinetデータの企業説明欄から検索して、
ヒットすればその企業のyahooファイナンスのページのリンクを記載したメッセージを
返すという流れになります。
コードは以下
from flask import Flask, jsonify, abort, make_response, request
from flask_cors import CORS
import os
from google.cloud import storage
from io import BytesIO
import pandas as pd
app = Flask(__name__)
app.config['JSON_AS_ASCII'] = False
CORS(app)
@app.route('/chat',methods=['GET'])
def answer():
text = request.args.get("text","")
# for local
df=pd.read_csv("all_edinetcodeinfo.csv",header=None)
# for GCP
#storage_client = storage.Client()
#bucket = storage_client.get_bucket('edinetcodeinfo')
#blob = bucket.get_blob('all_edinetcodeinfo')
#df = pd.read_csv(BytesIO(blob.download_as_string()),header=None)
dfc=df[df[5].str.contains(text)]
if(len(dfc)==0):
result={"output":[{"type":"text","value":"見つかりませんでした"}]}
return make_response(jsonify(result))
else:
ft=str(len(dfc))+"件見つかりました"
html=""
for i in range(len(dfc)):
code=dfc.iloc[i,0]
company=dfc.iloc[i,2]
value=str(code)+": "+"<a href=\"https://stocks.finance.yahoo.co.jp/stocks/chart/?code="+str(code)+"\" target=\"_blank\" >"+str(company)+"</a><br>"
html+=value
result={"output":[{"type":"text","value":ft,"delayMs": 300},{"type":"html","value":html}]}
return make_response(jsonify(result))
@app.errorhandler(404)
def not_found (error):
return make_response(jsonify({"error","Not found"}),404)
if __name__ == "__main__":
app.run(debug=True,host='0.0.0.0',port=int(os.environ.get('PORT', 8080)))
テスト
docker-compose.ymlを使用して、コンテナを起動してみます。
# cd <docker-compose.ymlが置かれているディレクトリ>
# docker-compose up -d
あとはindex.htmlを開き、単語を入力すると検索結果が返されます。
Cloud Buildでのビルド
さてコンテナでのテストが完了したので、Dockerfile を含むディレクトリから次のコマンドを実行し、
Cloud Build を使用してコンテナイメージをビルドします。
gcloud builds submit --tag gcr.io/PROJECT-ID/application_name
PROJECT-IDは、GCP のプロジェクトID です。gcloud config get-value project を実行すると取得できます。
またアプリ名はデプロイするapplication_nameです。
成功すると、イメージ名(gcr.io/PROJECT-ID/application_name)を含むSUCCESSメッセージが表示されます。
イメージはContainer Registryに保存され、必要に応じて再利用できます。
Cloud Run へのデプロイ
コンテナ イメージをデプロイするには次のコマンドを使用してデプロイします。
gcloud run deploy --image gcr.io/PROJECT-ID/application_name --platform managed
サービス名の入力を求められます。Enter キーを押してデフォルト名を受け入れることができます。
またリージョンを指定するよう求められます。選択したリージョン(us-central1 など)を選択します。
最後に未認証の呼び出しを許可する場合は、[y] と返信します。
デプロイが完了し成功すると、コマンドラインにサービスURL が表示されるので、
ブラウザでこのサービスURL を開き、デプロイしたコンテナにアクセスできます。
さきほどローカル環境でテストしたときのようにキーワードをパラメータで指定し、jsonが返ってくれば成功です。
https://URL/chat?text=キーワード
Code
恒例のGithubにサンプル公開。
https://github.com/RuBisCO28/KabuChatServer
最後に一言
今回のように大きな処理を伴わず、必要な時必要な分だけ使う場合はCloud Functionsなどでもよいですが、 コンテナで管理デプロイできるCloud Runはまた違った魅力があるように思いました。