Python (Flask) で形態素解析をするAPIを作ってGlitchで公開する

# 概要
- Python(Falsk)で形態素解析で文章内の単語数を数えるAPIを作ります
- APIだけではなくJinja2を使用して取得結果を画面に表示します
- GlitchというNode.jsのアプリを公開するためのサービスで公開します

# 成果物
- 文章を入力すると文章内の単語の数を表示します
- FlaskとJinja2のみでウェブとして動作させています
- 実際に動作する[公開先のリンク](https://flask-nagisa.glitch.me/)
- プロジェクト（ソースコードなど）を[確認できるリンク] (https://glitch.com/edit/#!/flask-nagisa)

## スクリーンショット

### 文章入力画面

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412322/e05b9881-567c-9e38-f960-13a2265ff128.png)

### 結果画面

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412322/e548979c-d254-3ee8-fc25-bf53acc3c51e.png)
 
※単語の数が1件のモノが下に続いています

### 形態素解析するAPI

#### APIのURL

以下のようにAPIとしても利用できます。
こちらの[【リンク】](https://flask-nagisa.glitch.me/morpho_api?text=%E8%BF%91%E5%B9%B4%E3%80%81IT%E3%81%A8%E3%81%A8%E3%82%82%E3%81%ABICT%EF%BC%88Information%20and%20Communication%20Technology%EF%BC%9D%E6%83%85%E5%A0%B1%E9%80%9A%E4%BF%A1%E7%A7%91%E5%AD%A6%EF%BC%89%E3%81%A8%E3%81%84%E3%81%86%E8%A8%80%E8%91%89%E3%82%82%E4%BD%BF%E3%82%8F%E3%82%8C%E3%82%8B%E3%82%88%E3%81%86%E3%81%AB%E3%81%AA%E3%82%8A%E3%81%BE%E3%81%97%E3%81%9F%E3%80%82%E6%96%87%E9%83%A8%E7%A7%91%E5%AD%A6%E7%9C%81%E3%81%AE%E8%B3%87%E6%96%99%E3%81%AB%E3%82%82%E3%80%8C%E5%AD%A6%E6%A0%A1%E7%8F%BE%E5%A0%B4%E3%81%AEICT%E5%8C%96%E3%80%8D%E3%81%A8%E3%81%84%E3%81%A3%E3%81%9F%E5%BD%A2%E3%81%A7%E7%99%BB%E5%A0%B4%E3%81%99%E3%82%8B%E8%A8%80%E8%91%89%E3%81%A7%E3%81%99%E3%80%82)で以下と同じ内容を実行できます。

```
https://flask-nagisa.glitch.me/morpho_api?text=近年、ITとともにICT（Information and Communication Technology＝情報通信科学）という言葉も使われるようになりました。文部科学省の資料にも「学校現場のICT化」といった形で登場する言葉です。
```

#### APIの結果

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412322/e5f06fc2-87b6-87cd-02d1-b6e97a7f6ba5.png)



# 使用するフレームワークや用語の説明

## 形態素解析とは

- 文章内の品詞や単語などに判別することです
- 形態素解析するライブラリはいろいろあります
- 今回はnagisaという軽量のライブラリを使用しています
- Wikipediaでは以下ように記載されています

> 形態素解析（けいたいそかいせき、Morphological Analysis）とは、文法的な情報の注記の無い自然言語のテキストデータ（文）から、対象言語の文法や、辞書と呼ばれる単語の品詞等の情報にもとづき、形態素（Morpheme, おおま かにいえば、言語で意味を持つ最小単位）の列に分割し、それぞれの形態素の品詞等を判別する作業である。

## Flaskとは

- Pythonの軽量のフレームワークです
- 今回はバックエンドのAPIの作成がメインです
- フロントエンドの表示にはJinja2を使用します
- Wikipediaでは以下ように記載されています。

> Flask（フラスク）は、プログラミング言語Python用の、軽量なウェブアプリケーションフレームワークである。 標準で提供する機能を最小限に保っているため、自身を「マイクロフレームワーク」と呼んでいる。 Werkzeug WSGIツールキットとJinja2テンプレートエンジンを基に作られている。

# 準備

- まずは[Glitchのアカウントを作成](https://glitch.com/signup)します
- GlitchでFlaskが動作するプロジェクトが公開されています
- そのプロジェクトをベースに新しいAPIや画面の表示などの機能を追加します
- https://glitch.com/edit/#!/flask-python3
- Flask以外にも[Rails](https://glitch.com/edit/#!/rails-5-1-example)のプロジェクトもあります
- 上記のプロジェクトはベータであるため本番稼働というよりお試しのような形での利用が推奨です

## flask-python3をベースに自分のプロジェクトを作成する

- 上記のflask-python3のプロジェクトから`Remix Project`を選択します
- 左上のプロジェクト名の下矢印から選択できます
- 自分のプロジェクトとして作成できたら機能を追加していきます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412322/e1d458b8-3bc8-2983-6560-81b2c2d4358c.png)



# ディレクトリ構成とソース

- ディレクトリ構成はGlitch上にあるファイル・フォルダ全てを記載しています

## ディレクトリ構成

```
.
├── templates
│       ├── header.html
│       ├── index.html
│       └── morpho.html
├── .env
├── README.md
├── glitch.json
├── requirements.txt
└── server.py
```

## ソースコード

### server.py

#### ルーティングの設定

- ルーティングでアクセスされたときのURLを設定します
- API単体としても使えるよう`/morpho_api`でJsonの結果を取得することできます
- URLのルート`/`ではリクエストの`GET`と`POST`で表示する画面を変えています

#### GETとPOSTによる処理

- `GET`の場合は文章入力画面となる`index.html`を表示します
- `POST`の場合は結果画面となる`morpho.html`を表示します

#### 単語を数える処理

- 単語数を数える処理は`collections.Counter`で簡単に実装できます
- その後に`sorted`の処理で並び替えています
- 最終的に結果を返す部分では内包表記で記載しています
- 並び替えをするために違うオブジェクトに格納しているので何とかしたいところ・・・

```python
from flask import Flask, request, render_template
import collections
import nagisa

app = Flask(__name__)

@app.route("/morpho_api")
def morphological_analysis(document='nagisaを使った形態素解析をして単語を数えるツールです。解析後に単語数を視覚的に表示します。'):
  if request.args.get('text'):
    document = request.args.get('text')
  nagisa_result = nagisa.tagging(document)
  
  words_list= []
  for index, tag in enumerate(nagisa_result.postags):
    nagisa_word = nagisa_result.words[index]
    if tag == '名詞' and not nagisa_word.isnumeric():
      words_list.append(nagisa_word)
  
  count_dict = dict(collections.Counter(words_list))
  sorted_result = sorted(
    count_dict.items(),
    key =  lambda kv:(kv[1], kv[0]),
    reverse=True
  )
  max_count = max(count_dict.values())
  
  return {
    'input': document,
    'max_count': max_count,
    'result': [{'word': result[0], 'count': result[1]} for result in sorted_result]
  }

@app.route("/", methods=['GET', 'POST'])
def index():
  if request.method == 'GET':
    return render_template('index.html')
  
  if request.method == 'POST':
    if request.form['document']:
      morpho_list = morphological_analysis(document=request.form['document'])
      input_text = morpho_list['input']
      morpho_result = morpho_list['result']
      max_count = morpho_list['max_count']
      return render_template(
                            'morpho.html',
                             input_text=input_text,
                             morpho_result=morpho_result,
                             max_count=max_count
                            )
    return render_template('morpho.html')

@app.route('/test')
def test():
  return 'test'

if __name__ == "__main__":
  app.run()
  # app.run(debug=True)
  
```

### templates/header.html

- ヘッダー部分は共通なので別のファイルに分けています
- Jinja2でもvue.jsのようなコンポネント的な使い方できます

```html
  <head>
    <title>【flask + jinja2】形態素解析による単語を数えるツール</title>
    <meta charset="utf-8" />
    <link
      rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/bulma@0.9.1/css/bulma.min.css"
    />
  </head>
```

### templates/index.html

- URLのトップにアクセスしたときに表示されるページです
- このページから`POST`で単語の数を数えたい文章を送ります

```html
<!DOCTYPE html>
<html lang="ja">
  {% include "header.html" %}
  <body>
    <section class="hero is-primary is-fullheight">
      <!-- Hero content: will be in the middle -->
      <div class="hero-body">
        <div class="container has-text-centered">
          <p class="title">
            形態素解析で単語カウント
          </p>
          <form action="/">
            <textarea
              name="document"
              class="textarea"
              placeholder="単語を数えたい文章を入力して「実行」ボタンをタップ"
              rows="10"
            ></textarea>
            <button
              type="submit"
              formmethod="post"
              class="button mt-4 is-fullwidth"
            >
              実行
            </button>
          </form>

        </div>
      </div>
    </section>
  </body>
</html>

```

### templates/morpho.html

- 上記の`POST`後の表示されるページです
- 文章内の単語をキーに単語の数のリストを元に結果を表示します
- 用途的には微妙ですがグラフっぽいのはプログレスバーです

```html
<!DOCTYPE html>
<html lang="ja">
  {% include "header.html" %}
  <body>
    <section class="hero is-primary is-fullheight">
      <!-- Hero content: will be in the middle -->
      <div class="hero-body">
        <div class="container has-text-centered">
          <p class="title">
            形態素解析して単語を数えた結果
          </p>
          {% if input_text %}
          <div class="has-text-left mt-5 mb-5">
            <label class="label is-size-5">入力されたテキスト</label>
            <p class="subtitle has-text-black">
              {{input_text}}
            </p>
          </div>
          {% else %}
          <div class="has-text-left mt-5 mb-5">
            <label class="label is-size-5">テキスト未入力</label>
            <p class="subtitle has-text-black">
              テキストを入力して実行してください。
            </p>
            <p class="subtitle has-text-black">
              なお、誠に申し訳ありませんがJavaScriptは書いていないので事前の入力チェックはありません。
            </p>
          </div>
          {% endif %} {% for result in morpho_result %}
          <div class="has-text-left mt-3">
            <label class="label is-size-5"
              >{{ result['word']|e }}: {{ result['count']|e }}</label
            >
            <progress
              class="progress"
              value="{{ result['count']|e }}"
              max="{{ max_count|e }}"
            ></progress>
          </div>
          {% endfor %}
        </div>
      </div>
    </section>
  </body>
</html>
```
### requirements.txt

- `nagisa`を追加してPythonで使用できるようにします
- 追加すると裏で`pip install`でインストールされます

```
Flask
nagisa
```

### glitch.json

※特に編集はせず元のプロジェクトのままの内容です

```json
{
  "install": "pip3 install --user -r requirements.txt",
  "start": "PYTHONUNBUFFERED=true python3 server.py",
  "watch": {
    "ignore": [
      "\\.pyc$"
    ],
    "install": {
      "include": [
        "^requirements\\.txt$"
      ]
    },
    "restart": {
      "include": [
        "\\.py$",
        "^start\\.sh"
      ]
    },
    "throttle": 1000
  }
}

```

# 懸念点

## ディスク容量がギリギリ

- glitchで作成した環境の容量が200MBまでです
- nagisaのインストール後は180MBとギリギリです
- 常にwarningの状態になりますがFlaskなどは稼働します
- 本番稼働的な形では若干リスクがあるので注意が必要です

## 本来はNode.jsがメイン

- 基本的にGlitchはNode.jsがメインです
- 裏技のようにPythonやRubyなども動作させることができます
- 若干非公式感も否めないのでお試し程度の利用がよさそうです

## 無料枠ではいろいろと制限がある

- しばらく使っていないと環境がスリープ状態になります
- スリープ状態で公開しているURLにアクセスすると稼働状態まで時間が掛かります
- チャットボットのような即座に返すようなAPIには不向きです

# ふりかえり

- Glitchでサクっと作って公開できるのは楽しい
- 無料でPythonを手軽に動作させる環境ってGlitchくらい？
- 形態素解析の処理とかはPythonは速い（Kuromoji.jsが遅い？）
- フロントエンドの表現はPythonだけだと厳しい