pyknp(JUMAN,KNP)をwindowsで使えるようにする

# 目次
1.開発環境
2.各種ソフトをダウンロード
3.コマンドプロンプトで確認
4.pyknpインストール
5.使用テスト
6.プログラム書き換え
7.参考・余談

少しでも参考になると嬉しいです、セットアップ頑張ってください！
# 1.開発環境
**環境**
*Windows10
*Python-3.7.5 64bit
**ターミナル？**
*Command prompt
*Visual studio codeのターミナル

# 2.各種ソフトをダウンロード
まずJUMANとKNPをダウンロードします
### [**1.JUMAN**](http://nlp.ist.i.kyoto-u.ac.jp/index.php?JUMAN)
>ダウンロードの欄の上から3番目くらいに、
**JUMAN Ver.7.0 (Windows 64bit版) (インストーラ付; 8,330,604 bytes)**
ってのがあります。それをダウンロードして、開いたらインストールの作業に移ります
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647496/20f2cbdf-e86f-f13a-39ef-7005062158c1.png)
設定はそのままで大丈夫のはず…

### [**1.KNP**](http://nlp.ist.i.kyoto-u.ac.jp/index.php?KNP)
>こちらもダウンロードの欄から下のやつ探してダウンロードします
KNP Ver.4.11 (Windows 64bit版) (インストーラ付; 979,363,446 bytes)
なんかサイズが＼でけぇ!/のでまあまあ時間かかります
こちらもダウンロードして開いたら、インストールが始まるのでそれに従ってください

### PATHを設定する
>pathは自分で設定しないといけないです
[**こちら**](https://www.atmarkit.co.jp/ait/articles/1805/11/news035.html)を参考にさせていただきました。システム側をいじるだけでいけます
1.検索かなんかでシステムのプロパティを開きます
2.詳細設定
3.下の方にある[環境設定]
4.システム環境設定にある[path]
5.[編集]
6.[C:\Program Files\juman]を追加
7.[C: \Program Files\knp]を追加
8.[OK]
人によっては再起動したほうがいいかもしれません。僕は、再起動しないと反映されませんでした

余談：pyknpではJUMAN++とKNPを使うのですが、JUMANをwindowsで使用するにはいろいろ面倒だったので旧バージョンであるJUMANを使用します。これの対応は下の方に書いてあります
# 3.コマンドプロンプトで確認
まずコマンドプロンプトを開きます(すべてctrl+Cで終了できます)
**確認方法1: jumanと入力**

```
C:~\> juman
なんか文章を入力
(成功例)
なんか なんか なんか 副詞 8 * 0 * 0 * 0 "代表表記:なんか/なんか 標準:何/なに+か/か"
文章 ぶんしょう 文章 名詞 6 普通名詞 1 * 0 * 0 "代表表記:文章/ぶんしょう カテゴリ:抽象物"
を を を 助詞 9 格助詞 1 * 0 * 0 NIL
入力 にゅうりょく 入力 名詞 6 サ変名詞 2 * 0 * 0 "代表表記:入力/にゅうりょく カテゴリ:抽象物 ドメイン:科学・技術 反義:名詞-サ変名詞:出力/しゅつりょく"
EOS
```

**確認方法2: echo なんか文章 | juman と入力**

```
C:~\> echo なんか文章を入力 | juman
なんか なんか なんか 副詞 8 * 0 * 0 * 0 "代表表記:なんか/なんか 標準:何/なに+か/か"
文章 ぶんしょう 文章 名詞 6 普通名詞 1 * 0 * 0 "代表表記:文章/ぶんしょう カテゴリ:抽象物"
を を を 助詞 9 格助詞 1 * 0 * 0 NIL
入力 にゅうりょく 入力 名詞 6 サ変名詞 2 * 0 * 0 "代表表記:入力/にゅうりょく カテゴリ:抽象物 ドメイン:科学・技術 反義:名詞-サ変名詞:出力/しゅつりょく"
  \  \  特殊 1 空白 6 * 0 * 0 NIL
EOS
```
**確認方法3: juman | knp と入力**

```
C:\~> juman | knp
なんか文章を入力
# S-ID:1 KNP:4.11-CF1.1 DATE:2020/11/23 SCORE:-27.41598
なんか──┐　
文章を──┤　
      入力
EOS
```
**(失敗例)**

```
'juman' は、内部コマンドまたは外部コマンド、
操作可能なプログラムまたはバッチ ファイルとして認識されていません。
```
# 4.pyknpインストール
VisualStudioCodeのターミナルで作業
pip install pyknpでインストール

```:ターミナル
C:~\>pip install pyknp
```
出来なかったら、（僕の場合は文字コードのエラーだったっぽいので）
https://qiita.com/Nidhog-tm/items/c7e9d759ce1a0f5c85c6
に従ってUTF-8を使用
**その後おそらくどこかで文字化けが起こるので、チェック外す > 英語にする > 日本語にする、で直す**
# 5.使用テスト
多分ってか絶対エラー吐きます。ここからこのスクリプトを使ってエラーの確認をしていきます
(参考:[https://pyknp.readthedocs.io/en/latest/](https://pyknp.readthedocs.io/en/latest/))

```python:test.py
# coding: utf-8
from __future__ import unicode_literals # It is not necessary when you use python3.
from pyknp import Juman
jumanpp = Juman()   # default is JUMAN++: Juman(jumanpp=True). if you use JUMAN, use Juman(jumanpp=False)
result = jumanpp.analysis("下鴨神社の参道は暗かった。")
for mrph in result.mrph_list(): # 各形態素にアクセス
    print("見出し:%s, 読み:%s, 原形:%s, 品詞:%s, 品詞細分類:%s, 活用型:%s, 活用形:%s, 意味情報:%s, 代表表記:%s" \
            % (mrph.midasi, mrph.yomi, mrph.genkei, mrph.hinsi, mrph.bunrui, mrph.katuyou1, mrph.katuyou2, mrph.imis, mrph.repname))

### (成功例) ###
見出し:下鴨, 読み:しもがも, 原形:下鴨, 品詞:名詞, 品詞細分類:地名, 活用型:*, 活用形:*, 意味情報:自動獲得:Wikipedia Wikipedia地名, 代表表記:
見出し:神社, 読み:じんじゃ, 原形:神社, 品詞:名詞, 品詞細分類:普通名詞, 活用型:*, 活用形:*, 意味情報:代表表記:神社/じんじゃ ドメイン:文化・芸術 カテゴリ:場所-施設 地名末尾, 代表表記:神社/じんじゃ
見出し:の, 読み:の, 原形:の, 品詞:助詞, 品詞細分類:接続助詞, 活用型:*, 活用形:*, 意味情報:NIL, 代表表記:
見出し:参道, 読み:さんどう, 原形:参道, 品詞:名詞, 品詞細分類:普通名詞, 活用型:*, 活用形:*, 意味情報:代表表記:参道/さんどう ドメイン:文化・芸術 カテゴリ:場所-施設, 代表表記:参道/さんどう
見出し:は, 読み:は, 原形:は, 品詞:助詞, 品詞細分類:副助詞, 活用型:*, 活用形:*, 意味情報:NIL, 代表表記:
見出し:暗かった, 読み:くらかった, 原形:暗い, 品詞:形容詞, 品詞細分類:*, 活用型:イ形容詞アウオ段, 活用形:タ形, 意味情報:代表表記:暗い/くらい, 代表表記:暗い/くらい
見出し:。, 読み:。, 原形:。, 品詞:特殊, 品詞細分類:句点, 活用型:*, 活用形:*, 意味情報:NIL, 代表表記:
```
# 6.プログラム書き換え
ここがマジ一日かかった。けど原因わかったら数分で行けます…（時間返してもろて）
書き換えるところは、すべてpyknpのファイル内の.pyファイルです。

**1.knp.py**
---
```knp.py
# (29行目)
# 書き換え内容
jumancommand='jumanpp'を'juman' にする
jumanpp     = True    を False  にする

# 書き換え前
def __init__(self, command='knp', server=None, port=31000, timeout=60,
                 option='-tab', rcfile='', pattern=r'EOS',
                 jumancommand='jumanpp', jumanrcfile='',
                 jumanoption='', jumanpp=True):
# 書き換え後
def __init__(self, command='knp', server=None, port=31000, timeout=60,
                 option='-tab', rcfile='', pattern=r'EOS',
                 jumancommand='juman', jumanrcfile='',
                 jumanoption='', jumanpp=False):
```
**2.juman.py**
---
```knp.py
# (27行目)
# 書き換え内容
command = 'jumanpp'を'juman' にする
jumanpp =  True    を False  にする

# 書き換え前
    def __init__(self, command='jumanpp', server=None, port=32000, timeout=30,
                 option='', rcfile='', ignorepattern='',
                 pattern=r'^EOS$', jumanpp=True):
# 書き換え後
    def __init__(self, command='juman', server=None, port=32000, timeout=30,
                 option='', rcfile='', ignorepattern='',
                 pattern=r'^EOS$', jumanpp=False):
```
**3.process.py**
---
```process.py
# (72行目)
# 書き換え内容1
signal.signal(signal.SIGALRM, alarm_handler)
signal.alarm(self.process_timeout)
#　↓
alarm = threading.Timer(self.process_timeout, alarm_handler)
alarm.start()

# 書き換え内容2
self.process.stdin.write(sentence.encode('utf-8') + six.b('\n'))
# ↓ (utf-8をcp932にする)
self.process.stdin.write(sentence.encode('cp932') + six.b('\n'))

# 書き換え内容3
line = self.process.stdout.readline().rstrip().decode('utf-8')
# ↓ (utf-8をcp932にする)
line = self.process.stdout.readline().rstrip().decode('cp932')

# 書き換え内容4
signal.alarm(0)
#　↓
alarm.cancel()


# 書き換え後
    def query(self, sentence, pattern):
        assert(isinstance(sentence, six.text_type))

        def alarm_handler(signum, frame):
            raise subprocess.TimeoutExpired(self.process_command, self.process_timeout)
# 書き換えポイント1
        # signal.signal(signal.SIGALRM, alarm_handler)
        # signal.alarm(self.process_timeout)
        alarm = threading.Timer(self.process_timeout, alarm_handler)
        alarm.start()
        result = ""
        try:
#書き換えポイント2
            # self.process.stdin.write(sentence.encode('utf-8') + six.b('\n'))
            self.process.stdin.write(sentence.encode('cp932') + six.b('\n'))
            self.process.stdin.flush()
            while True:
#書き換えポイント3
                # line = self.process.stdout.readline().rstrip().decode('utf-8')
                line = self.process.stdout.readline().rstrip().decode('cp932')
                if re.search(pattern, line):
                    break
                result = "%s%s\n" % (result, line)
        finally:
#書き換えポイント4
            # signal.alarm(0)
            alarm.cancel()
        return result
```
ついでに参照の仕組みを解説すると、
**knp    は juman   を参照**
（この時使用するコマンド名はjumanpp(JUMAN++)ではなくjumanじゃないとエラー起きる）
**juman  は process を参照**
（この時使用するコマンドもjumanじゃないといけない）
**processは subprocess(ターミナルのコマンド実行したりするやつ)を参照**
（ここで参照するときに、大半のwindowsは'cp932'という文字コードを使っているせいで、'utf-8'を使うと不具合が起きるからそれを直す。ついでにalarmもwindows用に書き換えてる。subprocess系は、windowsのターミナルであるpowershellを使う用には設計されていないっぽい。面倒ですね。）
という関係になっています
# 7.参考・余談
参考:
[**JUMAN >>> http://nlp.ist.i.kyoto-u.ac.jp/index.php?JUMAN**](http://nlp.ist.i.kyoto-u.ac.jp/index.php?JUMAN)
[**KNP   >>> http://nlp.ist.i.kyoto-u.ac.jp/?KNP**](http://nlp.ist.i.kyoto-u.ac.jp/?KNP)
[**pyKNP >>> http://nlp.ist.i.kyoto-u.ac.jp/?PyKNP**](http://nlp.ist.i.kyoto-u.ac.jp/?PyKNP)
参考サイト様:
[**pyKNPについて >>> https://pyknp.readthedocs.io/en/latest/**](https://pyknp.readthedocs.io/en/latest/)
[**subprocessについて >>> https://docs.python.org/ja/3.5/library/asyncio-subprocess.html**](https://docs.python.org/ja/3.5/library/asyncio-subprocess.html)
[**記事書いてるときに見つけた良さそうなサイト >>> http://chuckischarles.hatenablog.com/entry/2019/09/12/150505**](http://chuckischarles.hatenablog.com/entry/2019/09/12/150505)

ここまでに試したこと
---
最終的に、係り受け解析がしたかったので今回のものに取り組みました

1. [**CaboCha**](https://taku910.github.io/cabocha/)という係り受け解析のものがあるのですが、どうやら**64bitに対応していない**らしく、32bitにするのが面倒なので諦めました。なので今回のKNPを使わせていただいています。（もう一つ必要になる、形態素解析のMeCabってやつはpipとかでおそらく簡単にインストールできます）

2. pyknpは本来[**juman++**](http://nlp.ist.i.kyoto-u.ac.jp/?JUMAN%2B%2B)に対応しているが、**インストーラーが無く**、Linuxでやらないと面倒なことになる（windowsだとpath設定とか環境設定,utf-8などの設定が超つらい。できませんでした。）

3. python初期搭載の**subprocess**というモジュールがあるのですが、これがlinux用に作られていることが大変だったポイントの一つです。例えば、**Shell='True'**にしないと、windowsのshellが使えないらしいのですが、この設定は危険で**推奨されていない**っぽいので断念しました。

4. utf-8で変換すると、上記の仕様のせいで文字列をバイト(？)に変換するときに文字数が減らされるようです。そのため今回は、変換方法をutf-8を使ったものではなく、cp932を使ったものにして対応しました



初投稿なのでミスがあればその都度直していきたいので声をかけてくれるとありがたいです。
この記事が誰かのためになることを祈ります!!!

juman,knp,pyknpを開発した研究室の方々マジでありがとうございます。
この場をお借りして感謝申し上げます。