mecab-ipadic-neologd対応のRESTful MeCabサーバ用Dockerfile

以前mecab-python周りは導入が面倒くさかったのでDockerfileにまとめておこう。

REST APIの部分は[こちら](https://mohadana.herokuapp.com/api)を参考にFlaskで実装しました。
ソースは[github](https://github.com/matsulib/mecab-service)にあります。
ついでにdocker-composeも使ってみたらこれは便利。今回みたいにコンテナ1つだとあまり旨味はないけど。

:memo:[2016-10-07 追記] 辞書ファイルの更新について追記。
:memo:[2018-03-11 追記] フロントエンドを追加。

## Dockerfile
今回の成果物です。

```
FROM ubuntu:16.04

RUN apt-get update \
  && apt-get install python3 python3-pip curl git sudo cron -y \
  && apt-get clean \
  && rm -rf /var/lib/apt/lists/*

WORKDIR /opt
RUN git clone https://github.com/taku910/mecab.git
WORKDIR /opt/mecab/mecab
RUN ./configure  --enable-utf8-only \
  && make \
  && make check \
  && make install \
  && ldconfig

WORKDIR /opt/mecab/mecab-ipadic
RUN ./configure --with-charset=utf8 \
  && make \
  &&make install

WORKDIR /opt
RUN git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git
WORKDIR /opt/mecab-ipadic-neologd
RUN ./bin/install-mecab-ipadic-neologd -n -y

COPY . /opt/api
WORKDIR /opt/api
RUN pip3 install -r requirements.txt

CMD ["python3", "server.py"]
```

:memo:[2016-10-07 追記]

* 辞書ファイルを自動更新するためにcronをインストール。設定方法は下記を参照。
* ついでにmecab-ipadic-neologdをgit cloneするディレクトリを変更

## その他
### ディレクトリ構造
```
.
├── README.md
├── docker-compose.yml
└── flask-mecab
    ├── Dockerfile
    ├── requirements.txt
    └── server.py
```

### docker-compose.yml
```
api:
  build: ./flask-mecab
  volumes:
    - "./flask-mecab:/opt/api"
  ports:
    - "5000:5000"
  restart: always
```

### MeCabの出力結果をJSON形式に変換
MeCabの出力フォーマットはカスタマイズできますが、デフォルトでは、

```
表層形\t品詞,品詞細分類1,品詞細分類2,品詞細分類3,活用形,活用型,原形,読み,発音
```
となっているので、出力された文字列の各行を'\t'と','で分割してzip()を使って各要素に対応させています。

```python3:server.py
def mecab_parse(sentence, dic='ipadic'):
    dic_dir = "/usr/local/lib/mecab/dic/"
    if dic == 'neologd':
        dic_name = 'mecab-ipadic-neologd'
    else:
        dic_name = dic

    m = MeCab.Tagger('-d ' + dic_dir + dic_name)

    # 出力フォーマット（デフォルト）
    format = ['表層形', '品詞','品詞細分類1', '品詞細分類2', '品詞細分類3', '活用形', '活用型','原型','読み','発音']

    return [dict(zip(format, (lambda x: [x[0]]+x[1].split(','))(p.split('\t')))) for p in m.parse(sentence).split('\n')[:-2]]
```
後述する実行例も参照してください。

## 起動方法
docker-compose.ymlにポートフォワーディングの設定などを書けるので起動はこれだけです。

```shell-session 
$ git clone https://github.com/matsulib/mecab-service.git
$ cd mecab-service/
$ sudo docker-compose up -d   
```


## 実行方法
HTTPリクエスト

```
POST /mecab/v1/parse-ipadic
POST /mecab/v1/parse-neologd
```

リクエストヘッダ

```
Content-Type: application/json
```

リクエストボディ

```json
{
  "sentence": 文字列
}
```

## 実行例 ipadic
```shell-session
$ curl -X POST http://localhost:5000/mecab/v1/parse-ipadic \
       -H "Content-type: application/json" \
       -d '{"sentence": "関数型プログラミング"}'  | jq .
```

```json
{
  "dict": "ipadic",
  "message": "Success",
  "results": [
    {
      "原型": "関数",
      "品詞": "名詞",
      "品詞細分類1": "一般",
      "品詞細分類2": "*",
      "品詞細分類3": "*",
      "活用型": "*",
      "活用形": "*",
      "発音": "カンスー",
      "表層形": "関数",
      "読み": "カンスウ"
    },
    {
      "原型": "型",
      "品詞": "名詞",
      "品詞細分類1": "接尾",
      "品詞細分類2": "一般",
      "品詞細分類3": "*",
      "活用型": "*",
      "活用形": "*",
      "発音": "ガタ",
      "表層形": "型",
      "読み": "ガタ"
    },
    {
      "原型": "プログラミング",
      "品詞": "名詞",
      "品詞細分類1": "サ変接続",
      "品詞細分類2": "*",
      "品詞細分類3": "*",
      "活用型": "*",
      "活用形": "*",
      "発音": "プログラミング",
      "表層形": "プログラミング",
      "読み": "プログラミング"
    }
  ],
  "status": 200
}
 
``` 

## 実行例 mecab-ipadic-neologd
mecab-ipadic-neologdは固有名詞に強い辞書です。

```shell-session
$ curl -X POST http://localhost:5000/mecab/v1/parse-neologd \
       -H "Content-type: application/json" \
       -d '{"sentence": "関数型プログラミング"}'  | jq .
```

```json
{
  "dict": "neologd",
  "message": "Success",
  "results": [
    {
      "原型": "関数型プログラミング",
      "品詞": "名詞",
      "品詞細分類1": "固有名詞",
      "品詞細分類2": "一般",
      "品詞細分類3": "*",
      "活用型": "*",
      "活用形": "*",
      "発音": "カンスーガタプログラミング",
      "表層形": "関数型プログラミング",
      "読み": "カンスウガタプログラミング"
    }
  ],
  "status": 200
}
```

## イメージサイズについて

今回のDockerfileから作ったイメージが次のmecabservice_apiなんですが、元のubuntuと比べるととてつもなく巨大になりました。

```
$ sudo docker images
REPOSITORY                TAG                 IMAGE ID            CREATED             SIZE
mecabservice_api          latest              785bc1295e46        About an hour ago   3.375 GB
ubuntu                    16.04               c73a085dc378        4 days ago          127.1 MB
```
どこで肥大化したのかhistoryサブコマンドで見てみる。
やはり辞書ファイルがすごく大きいみたいだけど、これは仕方ないか。

```
$ sudo docker history mecabservice_api
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
785bc1295e46        About an hour ago   /bin/sh -c #(nop) CMD ["python3" "server.py"]   0 B
00339a58f77e        About an hour ago   /bin/sh -c pip3 install -r requirements.txt     5.852 MB
a397235a8e30        About an hour ago   /bin/sh -c #(nop) WORKDIR /opt/api              0 B
436ef40f928d        About an hour ago   /bin/sh -c #(nop) COPY dir:a4cb3a57cc1f117b07   2.445 kB
12456a11160d        3 hours ago         /bin/sh -c ./bin/install-mecab-ipadic-neologd   2.307 GB
18478e2f8d71        3 hours ago         /bin/sh -c #(nop) WORKDIR /opt/mecab/mecab-ip   0 B
74e6a0bffa98        3 hours ago         /bin/sh -c git clone --depth 1 https://github   114.2 MB
db6a7c716f21        3 hours ago         /bin/sh -c #(nop) WORKDIR /opt/mecab            0 B
6980af0a5afd        3 hours ago         /bin/sh -c ./configure --with-charset=utf8      106 MB
db32f32c58e6        3 hours ago         /bin/sh -c #(nop) WORKDIR /opt/mecab/mecab-ip   0 B
5745385f2342        3 hours ago         /bin/sh -c ./configure  --enable-utf8-only      11.16 MB
8c601b1fac00        3 hours ago         /bin/sh -c #(nop) WORKDIR /opt/mecab/mecab      0 B
d730397e47eb        3 hours ago         /bin/sh -c git clone https://github.com/taku9   378.8 MB
2abd825af064        3 hours ago         /bin/sh -c apt-get update   && apt-get instal   325 MB
d5abec2370fb        3 hours ago         /bin/sh -c #(nop) WORKDIR /opt                  0 B
c73a085dc378        4 days ago          /bin/sh -c #(nop)  CMD ["/bin/bash"]            0 B
```

## :memo: [2016/10/07 追記] mecab-ipadic-neologdの辞書を自動更新する

[mecab-ipadic-neologd](https://github.com/neologd/mecab-ipadic-neologd/blob/master/README.ja.md)の辞書は定期的にサーバー上で更新されていることに気づきました。

>- この辞書の更新は開発サーバ上で自動的におこなわれます
>    - 少なくとも毎週 2 回更新される予定です
>        - 月曜日と木曜日


さらに、インストール後の辞書に更新を取り込む方法も[公式](https://github.com/neologd/mecab-ipadic-neologd/blob/master/README.ja.md)に提示されています。

>cron(説明は省略します)などで自動的に更新したい場合に便利なオプションは既にあります。
>例えば下記の様な 2 行を crontab などに書いた場合は、火・金曜日の午前 3 時に解析結果を確認せずに(-y)ユーザ権限(-u)で指定した>ディレクリトリに(-p [/path/to/user/directory])ある辞書ファイルが更新されます。
> 
```
    00 03 * * 2 ./bin/install-mecab-ipadic-neologd -n -y -u -p /path/to/user/directory > /path/to/log/file
    00 03 * * 5 ./bin/install-mecab-ipadic-neologd -n -y -u -p /path/to/user/directory > /path/to/log/file
```


稼働中のコンテナをmecabservice_api_1とし、更新ログを/opt/log/mecabに出力する場合は、
以下のように、コンテナにbashで接続して設定できます。

```
$ sudo docker exec -it mecabservice_api_1 /bin/bash
# mkdir -p /opt/log/mecab
# (crontab -l 2>/dev/null; echo "PATH=$PATH") | crontab -
# (crontab -l 2>/dev/null; echo '00 18 * * 1 /opt/mecab-ipadic-neologd/bin/install-mecab-ipadic-neologd -n -y >/opt/log/mecab/neologd_`date -I`.log 2>/opt/log/mecab/neologd_`date -I`.err') | crontab -
# (crontab -l 2>/dev/null; echo '00 18 * * 4 /opt/mecab-ipadic-neologd/bin/install-mecab-ipadic-neologd -n -y >/opt/log/mecab/neologd_`date -I`.log 2>/opt/log/mecab/neologd_`date -I`.err') | crontab -
# /etc/init.d/cron restart
# exit
```

ここではJST時間の火・金曜日の午前3時に更新されるようにUTC時間で設定しています。
デフォルトではcrontabのPATHにmecabが含まれないところで一瞬詰まりました。
また、最初はシェルのちょっとしたバグで上記の設定でうまく動かなかったのですが、[IssueとPull Request](https://github.com/neologd/mecab-ipadic-neologd/issues/23)を投げたらすぐにマージして頂き動くようになりました(*^^)v

## :memo: [2018/03/11 追記] フロントエンドを追加する
せっかくdocker-composeを使ってるのに１サービスだけでは寂しいので、上記REST APIをフロントエンドから呼び出すWebアプリをdocker-composeに追加しました。フロントのソースは[github](https://github.com/matsulib/mecab-service)を参照してください。コンテナ間の通信やCORSの設定が詰まりどころでした。

### docker-compose.yml
```
api:
  build: ./flask-mecab
  volumes:
    - "./flask-mecab:/opt/api"
  ports:
    - "5000:5000"
  restart: always
front:
  build: ./flask-mecab-front
  volumes:
    - "./flask-mecab-front:/opt/front"
  ports:
    - "5001:5001"
  restart: always
  links:
    - api
  environment:
    FLASK_MECAB_URI: "http://api:5000/mecab/v1"
```

起動後、ブラウザで`http://localhost:5001/`にアクセスします。

### スクリーンショット
![mecab.PNG](https://qiita-image-store.s3.amazonaws.com/0/141719/cdf400f7-9c95-9989-25e2-f731572feb37.png)