Flask+WordCloud+MeCabで入力テキストのワードクラウドを表示するWebアプリケーションの作成

# はじめに
はじめまして、はとぽっぽといいます。今回は深夜テンションでアドベントカレンダーを作ってしまったがために、ぜーぜーいいながら記事を書いています。初めてこのようなもの書く初心者なのでお手柔らかにお願いします...。

今回は[Flask](https://flask.palletsprojects.com/en/1.1.x/)と[wordcloud](https://github.com/amueller/word_cloud)と[MeCab](https://taku910.github.io/mecab/)を使ってテキストBOXにテキストを入れるとそのワードクラウドを表示してくれるWEBアプリケーションを作ってみました。はじめはGoogleAppEngineにでもdeployしようかと思ったのですが、余裕がなかったので、ローカル環境での実装にしました。
ワードクラウドとはこんな感じの出現頻度の高い言葉ほど大きく表示される画像のことです。(画像は「羅生門」を入力とした時の出力結果)
![wordcloud.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272204/d76c9d62-ecdc-2a23-11a0-d06161d9e1f0.png)

# 実行環境
- WSL Ubuntu 18.04
- Python 3.7.2
- Flask 1.1.1
- wordcloud 1.6.0
- mecab-python3 0.996.2

# 実装
## 1.wordcloudについて
ワードクラウドを作成するにあたって、wordcloudというパッケージをpipを使ってインストールします。

```
pip install wordcloud
```
基本的な使い方はこのような形になります。入力としてスペース区切りになった単語の文章を渡してあげます。(分かち書きのテキスト。"hello world ever you me"といった感じのstr)
フォントの指定をしてあげないと文字が表示されない場合があるので注意です。collocationsは複合語についてのパラメータでdefaultではTrueになっています。また、ストップワードの指定などもできるそうです。

```python:word.py
output_image = WordCloud(
    font_path='/usr/share/fonts/truetype/fonts-japanese-gothic.ttf',
    background_color="white",
    width=900, height=900,
    max_words=500,
    min_font_size=4,
    collocations=False
).generate(input_texts)
```

ワードクラウド作成には先述の通り分かち書きの文章が必要になります。ここで、日本語では文章から単語ごとに区切る必要があるので、それを実現するためにMeCabを使います。
入力が日本語だった場合は名詞の分かち書きを作成してワードクラウドを作成するメソッドを作成しました。作成された画像は/static/image/wordcloud.pngの形で保存しました。

```python:word.py
import MeCab
from wordcloud import WordCloud


def create_cloud(texts, language):
    word_list = []
    if language == 'Japanese':
        tagger = MeCab.Tagger('-Ochasen')
        texts_line = texts.split('\n')
        for text in texts_line:
            node = tagger.parseToNode(text)
            while node:
                if node.feature.split(',')[0] == '名詞':
                    word_list.append(node.surface)
                node = node.next
    else:
        word_list.extend(texts.split(' '))
    input_texts = ' '.join(word_list)
    output_image = WordCloud(
        font_path='/usr/share/fonts/truetype/fonts-japanese-gothic.ttf',
        background_color="white",
        width=900, height=900,
        max_words=500,
        min_font_size=4,
        collocations=False
    ).generate(input_texts)
    output_image.to_file('./static/image/wordcloud.png')

```


## 2.入力フォームの作成(HTML)
HTMLを書いて、入力フォームを作成します。デザインに関しては[Bootstrap](https://getbootstrap.jp/)を使ってそれっぽくしました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272204/90ca9149-d73d-06f9-87be-e3f7f777a322.png)
一部コード抜粋

```html:index.html
<form action="/result/" method="POST">
    <h3 class="p-3 mb-2 bg-info text-white">Input Text</h3>
    <fieldset class="form-group">
        <div class="row">
            <legend class="col-form-label col-sm-3 pt-0">言語選択</legend>
            <div class="col-sm-10">
                <div class="form-check">
                    <input class="form-check-input" type="radio" name="language" id="English"
                        value="English" checked>
                    <label class="form-check-label" for="English">
                        英語
                    </label>
                </div>
                <div class="form-check">
                    <input class="form-check-input" type="radio" name="language" id="Japanese"
                        value="Japanese">
                    <label class="form-check-label" for="Japanese">
                        日本語
                    </label>
                </div>
            </div>
        </div>
    </fieldset>
    <div class="form-group">
        <label for="texts">入力文章</label>
        <textarea class="form-control" name="texts" id="texts" required></textarea>
    </div>
    <input class="btn btn-primary" type="submit" value="Submit">
    <input class="btn btn-danger" type="reset" value="Reset">
</form>
```
POSTメソッドを利用してフォームを作成しました。言語とテキストを入力としています。

## 3.入力処理を記述(flask)
入力を受け取ってその入力を先述のメソッドに渡してあげる処理を書きました。
result.htmlには/static/image/wordcloud.pngを出力する処理があり、これによって出力の結果が見える形になっています。

```python:main.py
from flask import Flask, render_template, request
from word import create_cloud

app = Flask(__name__)


@app.route('/')
def index():
    return render_template('index.html')


@app.route('/result/', methods=['GET', 'POST'])
def get_result():
    texts = str(request.form['texts'])
    language = str(request.form['language'])
    create_cloud(texts, language)
    return render_template('result.html')


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

# 実装結果
このような形で入力をしてあげます。テキストは青空文庫からコピペしただけなので細かいところはご愛敬。([まだらの紐](https://www.aozora.gr.jp/cards/000009/files/50717_36994.html))
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272204/ecdda77d-42d5-38e5-a245-a5d8ebab3750.png)
出力結果はこのようになりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272204/26a34901-da0c-f9c5-bf4e-3344d60733ef.png)
ストップワードを指定していないので「もの」とか「それ」などの言葉が入ってしまっているのは少し汚いですね。

# まとめ
最初に目標にしていた入力を受け取ってそのワードクラウドを出力することはできたので良かったです。ただ、入力単語数が少ない場合などのエラーが発生したときの処理を全く書いていないので、それはこれからの改善点かもしれません。
ストップワードも指定できるようにしてあげるようにするもの面白いかなと思います。
このような形でそんなにこちらがたくさんコードを書くことなくそれっぽいものが作成できるので、よかったら作成してみるのもよいのではないでしょうか。

[Github](https://github.com/hatopoppoK3/NaturalLanguageProcessing/tree/master/wordcloud/flask)

# 参考サイト
アプリケーションを作成するにあたって参考にさせていただいたサイト様です。

- [Word Cloudで文章の単語出現頻度を可視化する。[Python]](https://qiita.com/kenmatsu4/items/9b6ac74f831443d29074)
- [Word Cloudで同じ単語が2回出てきて困る](https://qiita.com/kbs/items/33b3dd6dae15f7b20b9e)
- [Python - Word Cloud を作成する方法について](https://www.pynote.info/entry/python-wordcloud)