--- title: Flaskで自然言語処理を使ったWebアプリを作りherokuでデプロイした tags: Flask Python3 HTML CSS Heroku author: mk668a slide: false --- # 説明 Flaskで英文を入れると時制を解析するWebアプリを作りました。 - 今回作ったもの: https://english-tense-analyzer.herokuapp.com/ - GitHub: https://github.com/mk668a/flask_nlp_app # 環境 Python3がインストールされていることとGitHubで作成していることを前提に進めます。 # Flaskアプリケーションの作成 ## ディレクトリ構成 ``` . ├── static │ ├── js │ │ └── main.js │ └── css │ └── style.css ├── templates │ └── index.html ├── main.py └── analyzer.py ``` ## htmlを作成 ``` html:templates/index.html TENSE ANALYZER

TENSE ANALYZER

``` ## Flaskのインストール ```pip install Flask``` ## main.pyを作成 ```python3:main.py from flask import * app = Flask(__name__) @app.route("/") def init(): return render_template('index.html') if __name__ == "__main__": # debugはデプロイ時にFalseにする app.run(host='127.0.0.1', port=5000, debug=True) ``` ## アプリケーションを立ち上げる ``` python main.py ``` http://127.0.0.1:5000/ を開いて"TENSE ANALYZER"の文字が表示されていれば成功。 ## フォームを作る ### htmlにフォームを追加 ``` html:templates/index.html ...

TENSE ANALYZER

英文を入力してEnterキーを押してください

結果

{{value_str}}

... ``` formタグに```action="/input"```、 inputタグに```value="{{value_str}}"```, ```name="str"``` の属性を追加。 ### フォームから送信した値を受け取る処理をmain.pyに追加 flask_socketioをインストール ``` pip install flask_socketio``` ```python3:main.py from flask import * from flask_socketio import SocketIO app = Flask(__name__) socketio = SocketIO(app, async_mode=None) # [変更] socketioを追加 value_str = "" # [変更] value_strを定義 @app.route("/") def init(): # value_strをhtml側で使えるようにする return render_template('index.html', value_str=value_str) # action="/input" @app.route("/input", methods=["GET", "POST"]) def get_form(): global value_str # フォームの値を受け取る try: value_str = request.form['str'] # name="str"のinputタグの値を取得 # ページ読み込み時 except: value_str = "" # index.html内にて{{ value_str }}で挿入できる return render_template('index.html', value_str=value_str) if __name__ == "__main__": # debugはデプロイ時にFalseにする socketio.run(app, host='127.0.0.1', port=5000, debug=True) # [変更] ``` index.htmlの```

{{value_str}}

```の部分にフォームから送信した値が表示されれば成功。 ## NLPで解析部分を実装 今回は[NLTK(Natural Language Toolkit)](https://www.nltk.org/)というライブラリを使用しました。 ### NLTKをインストール ```pip install nltk``` ### analyzer.pyを作成
analyzer.py
```python:analyzer.py import re import nltk nltk.download('punkt') nltk.download('averaged_perceptron_tagger') class Analyzer: def tense_analyze(self, value_str): if(len(value_str) > 0 and "." != value_str[-1]): value_str = value_str+"." s = value_str morph = nltk.word_tokenize(value_str) result = nltk.pos_tag(morph) tense = '' q = 0 will = re.search('will', value_str) for i in range(len(s.split())): b = int(i) if result[b][1] != 'VBP' and result[b][1] != 'VBZ' and result[b][1] != 'VBG' and result[b][1] != 'VB' and result[b][1] != 'VBD' and result[b][1] != 'VBN': q = q+1 elif (result[b][1] == 'VBP' or result[b][1] == 'VBZ' or result[b][1] == 'VBD' or result[b][1] == 'VB') and (result[b][0] != 'am' and result[b][0] != 'are' and result[b][0] != 'was' and result[b][0] != 'were' and result[b][0] != "be" and result[b][0] != 'is'): if result[b][0] == 'have' or result[b][0] == 'has' or result[b][0] == 'had': if result[b+1][1] == 'VBN' or result[b+2][1] == 'VBN' or result[b+1][1] == 'VBD' or result[b+2][1] == 'VBD': if result[b+1][0] == 'been': if result[b+2][1] == 'VBG' or result[b+3][1] == 'VBG': tense = 'PerfectContiuous' break else: tense = 'Perfect' break else: tense = 'Perfect' break else: tense = 'Simple' break else: tense = 'Simple' elif result[b][1] == "VBP" or result[b][0] == "was" or result[b][0] == "were" or result[b][1] == "VBZ" or result[b][0] == "be" or result[b][0] == "is" or result[b][0] == "VBD": if result[b+1][1] == 'VBG': tense = 'Continuous' break else: tense = 'Simple' break for j in range(len(s.split())): c = int(j) if tense == 'Simple': if result[c][1] == 'VBD': tense = 'PastSimple' break elif result[c][1] == 'VBD' and (result[c-1][1] == 'VBP' or result[c-1][1] == 'VBD' or result[c-1][1] == 'VBZ'): tense = 'PastSimple' break elif (not will) and result[c][1] == "VBP" or result[c][1] == "VBZ": tense = "PresentSimple" elif will and (result[c-1][1] != 'VBP' or result[c-1][1] != 'VBD' or result[c-1][1] != 'VBZ'): tense = "FutureSimple" else: q = q+1 elif tense == "Perfect": if result[c][0] == 'had': tense = 'PastPerfectSimple' break elif not will and (result[c][0] == "have" or result[c][0] == "has"): tense = "PresentPerfectSimple" elif will and result[c][0] == "have": tense = "FuturePerfectSimple" elif tense == "Continuous": if result[c][0] == 'was' or result[c][0] == 'were': tense = 'PastContinuous' break elif (not will) and result[c][1] == "VBP" or result[c][1] == "VBZ": tense = "PresentContinuous" elif will: tense = "FutureContinuous" else: q = q+1 elif tense == "PerfectContiuous": if result[c][0] == 'had': tense = "PastPerfectContinuous" elif not will and (result[c][0] == 'have' or result[c][0] == "has"): tense = 'PresentPerfectContinuous' elif will: tense = "FuturePerfectContinuous" else: q = q+1 value_str.strip() return value_str, tense ```
```Analyzerクラス```に文章の時制の手がかりとなる部分と時制を返す関数```def tense_analyze(self, value_str):```を作成。 アルゴリズムの説明等は省略します。 ### main.py内でanalyzer.pyをimportして使う ```python:main.py from flask import * from flask_socketio import SocketIO # analyzer.pyをimport import analyzer app = Flask(__name__) socketio = SocketIO(app, async_mode=None) # [削除]value_str = "" result = [] # [変更] 結果を格納する配列 @app.route("/") def init(): # value_strをhtml側で使えるようにする # [変更] value_str -> result return render_template('index.html', result=result) # action="/input" @app.route("/input", methods=["GET", "POST"]) def get_form(): global value_str # フォームの値を受け取る try: value_str = request.form['str'] # name="str"のinputタグの値を取得 # ページ読み込み時 except: value_str = "" # tense_analyze関数の実行 Analyzer = analyzer.Analyzer() value_str, tense = Analyzer.tense_analyze(value_str) # 結果をresultに追加 appendList = [] if(value_str): appendList.append(value_str) if(tense): appendList.append(tense) result.append(appendList) # [変更] value_str -> result # index.html内にて{{ result }}で挿入できる return render_template('index.html', result=result) if __name__ == "__main__": # debugはデプロイ時にFalseにする socketio.run(app, host='127.0.0.1', port=5000, debug=True) # 変更 ``` ### index.htmlでresultを表示する。 Python用のテンプレートエンジンJinja2をインストール ``` pip install Jinja2 ``` Jinja2をインストールすることでhtml内でif文やfor文が使えます。 [Jinja2のドキュメント](https://jinja.palletsprojects.com/en/2.10.x/) ``` html:templates/index.html ...

結果

{% if result|length > 0 %}
{% for r in result|reverse %} {% if r[0] %}

{{ r[0] }}

{% if r[1] %}

{{ r[1] }}

{% endif %}
{% endif %} {% endfor %}
{% endif %}
... ``` ## リセットボタンを作成 ### index.html ```html:templates/index.html ...
... ``` formに```action="/reset"```属性を追加 ### main.py ``` python:main.py ... @app.route("/") def init(): # value_strをhtml側で使えるようにする return render_template('index.html', result=result) # action="/reset" @app.route("/reset", methods=["GET", "POST"]) def reset_result(): global result result = [] return render_template('index.html', result=result) # action="/input" @app.route("/input", methods=["GET", "POST"]) ... ``` ## CSSとJavaScript ### CSS
style.css
```css:static/css/style.css /* css reset */ body { font-family: 'Inconsolata', monospace; background-color: #fdfeff; display: flex; padding: 0 1rem; } h1, h2, h3 { color: rgb(70, 70, 70); margin-bottom: 0; } h1 { font-size: 4vw; } h2 { font-size: 3vw; } h3 { font-size: 2vw; } p { margin: 0; color: dimgrey; font-size: 1.6vw; font-weight: bold; } ul, li { padding: 0; } li { list-style-type: none; font-size: 1.6vw; font-weight: bold; color: dimgrey; } li::before { content: "・"; } input { background: white; } input:-webkit-autofill, input:-webkit-autofill:hover, input:-webkit-autofill:focus, input:-webkit-autofill:active { -webkit-text-fill-color: rgb(70, 70, 70); -webkit-box-shadow: 0 0 0px 1000px white inset; box-shadow: 0 0 0px 1000px white inset; } input:focus { outline: 0; } button { color: dimgrey; } button:hover { cursor: pointer; } button:focus { outline: 0; } /* */ /* container */ #container { margin: auto; width: 80vw; } @media screen and (max-width:768px) { #container { width: 90vw; } } #container>div { margin-bottom: 2rem; } /* */ /* nlp */ #nlp { /* border-radius: 5px; */ /* background: #34456314; */ } #form { margin: 0.5rem 0; } #form .input { height: 25px; width: calc(100% - 15px); padding: 5px; margin-left: 1px; background: transparent; font-size: 1.8vw; border: 2px solid rgb(70, 70, 70); } #form .submit { display: none; } .result-content { margin: 0.5rem 0; padding: 5px 10px; background-color: #f1f1f1; border-radius: 5px; } .result-content #reset { padding: 5px 0; } .result-content #reset button { margin: auto 0 0 0; padding: 5px 10px; display: block; border: 1px solid #fdfeff; background: #fdfeff; border: 2px solid dimgray; border-radius: 5px; font-size: 1.6vw; font-weight: bold; transition: all 0.3s ease; } .result-content #reset button:hover { background: dimgrey; color: #fdfeff; } .result-content .items { overflow: scroll; max-height: 50vh; } .result-content .items .item { display: flex; flex-wrap: wrap; padding-bottom: 0.5rem; border-bottom: 1px solid #34456314; margin: 1rem 0; } .result-content p { padding: 5px 0; font-size: 1.8vw; } .result-content .tense { background: #fdfeff; border-radius: 15px; padding: 5px 10px; } /* */ #task li { color: aliceblue; padding: 5px; display: inline-block; border-radius: 5px; background: #344563; } ```
### JavaScript ```javascript:static/js/main.js // ウィンドウ読み込み時にフォームにfocusする window.onload = function () { document.formStr.str.focus() } ``` ### html内で読み込む html内でcssとJavaScriptを読み込むのを忘れずに ```html:templates/index.html ... ``` # Herokuにデプロイ ## ディレクトリ構成 ``` . ├── static │ ├── js │ │ └── main.js │ └── css │ └── style.css ├── templates │ └── index.html ├── main.py ├── analyzer.py ├── Procfile └── requirements.txt ``` ##Procfileを作成 ### gunicornをインストール ```pip install gunicorn``` ### Procfile ```:Procfile web: gunicorn [ファイル名]:app --log-file=- ``` mainの部分は自分で作ったファイル名に合わせる(今回はmain.pyだからmain) ```:Procfile web: gunicorn main:app --log-file=- ``` ## requirements.txtを作成 以下のコマンドを実行すると、requirements.txt何に必要なライブラリ等が自動的に書き込まれる。 ```$ pip freeze > requirements.txt``` ```:requirements.txt autopep8==1.4.4 Click==7.0 Flask==1.1.1 Flask-SocketIO==4.2.1 gunicorn==20.0.4 itsdangerous==1.1.0 Jinja2==2.10.3 MarkupSafe==1.1.1 nltk==3.4.5 pycodestyle==2.5.0 python-engineio==3.10.0 python-socketio==4.4.0 six==1.13.0 Werkzeug==0.16.0 ``` ## Herokuに登録する [このページ](https://www.heroku.com/)でアカウントを作成する。 ## Herokuをインストールする ``` brew tap heroku/brew && brew install heroku``` ## Herokuにログインする ``` heroku login ``` ## Herokuアプリを作成 ``` heroku create [アプリ名]``` 今回は、tenseにします。 ``` heroku create tense``` ```terminal $ heroku create tense Creating ⬢ tense... ! ▸ Name tense is already taken ``` 既に使用されてたため変更 ``` heroku create english-tense-analyzer``` ```terminal $ heroku create english-tense-analyzer Creating ⬢ english-tense-analyzer... done https://english-tense-analyzer.herokuapp.com/ | https://git.heroku.com/english-tense-analyzer.git ``` ## デプロイ 以下のコマンドを実行するだけで完了です ```git push heroku master``` ```heroku open```を実行するとブラウザでWebアプリケーションが開きます。 ## 終わりに - 今回作ったもの: https://english-tense-analyzer.herokuapp.com/ - GitHub: https://github.com/mk668a/flask_nlp_app #### 感想 使用してみて、とても軽く、簡単に手軽に作れるという点で学習コストが低く、使いやすいフレームワークだと思いました。 機械学習を使ってWebアプリケーションを作りたい方や少し試したいという方には丁度いいかと思います。 Djangoなどの他のPythonのWebフレームワークはまだ使用したことがないので試してみたいです。 ## 参考 - Python Flaskでサクッと簡単に画面を作成する - Flaskにおける基本的なフォームの使い方 - HerokuにFlaskアプリ(hello world)をデプロイする方法まとめ - Python+Flask+Herokuで簡単webアプリ