COTOHAを利用して、物語の舞台を抽出・図示してみた

#概要
物語の舞台が地元だったりすると、それだけで読んでしまうタイプなので
[COTOHA](https://api.ce-cotoha.com/contents/index.html)の固有表現抽出を、利用して物語の舞台を、抽出・図示するものを作成してみた。

#処理の流れ
1.青空文庫より任意の小説を取得
2.COTOHA API（固有表現抽出）で地名(LOC)のみ取得
3.取得した地名を利用して、WordCroudで図示する。

#環境
Google Colaboratory

#ライブラリ
・個別にインストールが必要なものはwordcloudのみ。
　（Google Colaboratoryは事前にある程度のライブラリがインストール済みなので、便利です）
　
　以下のコマンドを実行すれば、事前準備は完了です。

```python:wordcloudのインストール＆フォントのダウンロード
!pip install wordcloud
!apt-get -y install fonts-ipafont-gothic
!rm /root/.cache/matplotlib/fontlist-v300.json
```

```python:青空文庫をクローン
!git clone --branch master --depth 1 https://github.com/aozorabunko/aozorabunko.git
```

#コード
####
<details><summary>1.青空文庫より任意の小説を取得する部分</summary><div>

```python:青空文庫より任意の小説を取得する部分
from bs4 import BeautifulSoup

def get_word():

  #クローンしたhtmlからパスを指定(サンプルは太宰治のグッド・バイ)
  path_to_html='aozorabunko/cards/000035/files/258_20179.html'
  
  #BeautifulSoupでhtml解析
  with open(path_to_html, 'rb') as html:
    soup = BeautifulSoup(html, 'lxml')
  main_text = soup.find("div", class_='main_text')
  for yomigana in main_text.find_all(["rp","h4","rt"]):
    yomigana.decompose()
  sentences = [line.strip() for line in main_text.text.strip().splitlines()]
  aozora_text=','.join(sentences)

  #cotoha apiコール用に文字数で分割(1800文字ごと)
  aozora_text_list = [aozora_text[i: i+1800] for i in range(0, len(aozora_text), 1800)]
  return aozora_text_list
```
</div></details>
Gitからクローンした青空文庫より、任意の小説のパス(path_to_html)を指定して全文取得し
BeautifulSoupでパースしてます。
（ちなみに、サンプルは太宰治のグッド・バイ）

また、COTOHAが実行できるように、文字列を1800文字ごとに分割して配列化してます。
（ちゃんと調べてないですが2000文字でダメで、1800文字で実行したらイケたので。。~~ちゃんと調べろよ~~）

---

<details><summary>2.COTOHA_APIを呼び出しする部分</summary><div>

```python:COTOHA_APIをコールする部分
import os
import urllib.request
import json
import configparser
import codecs
import sys
import time


client_id = "各自のクライアントID"
client_secret = "各自のシークレットキー"

developer_api_base_url = "https://api.ce-cotoha.com/api/dev/nlp/"
access_token_publish_url = "https://api.ce-cotoha.com/v1/oauth/accesstokens"

def cotoha_call(sentence):
    # アクセストークン取得
    def getAccessToken():     
        url = access_token_publish_url
        headers={
            "Content-Type": "application/json;charset=UTF-8"
        }
        data = {
            "grantType": "client_credentials",
            "clientId": client_id,
            "clientSecret": client_secret
        }
        data = json.dumps(data).encode()
        req = urllib.request.Request(url, data, headers)
        res = urllib.request.urlopen(req)
        res_body = res.read()
        res_body = json.loads(res_body)
        access_token = res_body["access_token"]
        return access_token

    # API URL指定(固有表現抽出)
    base_url_footer = "v1/ne" 
    url = developer_api_base_url + base_url_footer
    headers={
        "Authorization": "Bearer " + getAccessToken(), #access_token,
        "Content-Type": "application/json;charset=UTF-8",
    }
    data = {
        "sentence": sentence
    }
    data = json.dumps(data).encode()
    time.sleep(0.5)
    req = urllib.request.Request(url, data, headers)
        
    try:
        res = urllib.request.urlopen(req)
    # リクエストでエラーが発生した場合の処理
    except urllib.request.HTTPError as e:
        # ステータスコードが401 Unauthorized or 500 Internal Server Errorならアクセストークンを取得し直して再リクエスト
        if e.code == 401 or 500:
            access_token = getAccessToken()
            headers["Authorization"] = "Bearer " + access_token
            time.sleep(0.5)
            req = urllib.request.Request(url, data, headers)
            res = urllib.request.urlopen(req)
        # 401 or 500 以外のエラーなら原因を表示
        else:
            print ("<Error> " + e.reason)
            #sys.exit()

    res_body = res.read()
    res_body = json.loads(res_body)
    return res_body
```
</div></details>


COTOHA（固有表現抽出）を呼び出す部分、エラー401と500の場合のみリトライするようにしてます。

---

<details><summary>3.WordCloudで図示する部分</summary><div>

```python:WordCloudで図示する部分
from wordcloud import WordCloud
import matplotlib.pyplot as plt
from PIL import Image

def get_wordcrowd_mask(text):
  
  #日本語フォント指定
  f_path = '/usr/share/fonts/opentype/ipafont-gothic/ipagp.ttf'

  #wcパラメタ指定
  wc = WordCloud(background_color="white",
                  width=500,
                  height=500,
                  font_path=f_path,
                  collocations=False,
                  ).generate( text )

  #画面描写
  plt.figure(figsize=(5,5), dpi=200)
  plt.imshow(wc, interpolation="bilinear")
  plt.axis("off")
  plt.show()

```
</div></details>

WordCloudを利用して、テキストを図示する部分です。


---

<details><summary>4.メイン</summary><div>

```python:処理を実行する部分
aozora_text_list = get_word()
json_list = []
loc_str = ''
cnt = 0
for i in aozora_text_list:
  cnt+=1
  print( str(cnt) + '/' + str(len(aozora_text_list)) )
  json_list.append(cotoha_call(i))


for i in json_list:
  for j in i['result']:
    if(j['class'] == 'LOC'):
      loc_str = loc_str + j['form'] + ","

get_wordcrowd_mask(loc_str)
```

</div></details>

1～3を単純に実行している部分
また、APIコール時の進捗を、以下出力結果のように表出してます。（n/1でテキスト分割時の配列内の個数）

```python:
1/9
2/9
3/9
4/9
5/9
6/9
7/9
8/9
9/9
```

---

#出力結果

##・グッド・バイ（太宰治）
![goodbye.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/586438/60b91943-c60e-ac18-e2d2-24c623a89bc5.png)

すこし、地名と関係なさそうな文字が混じってはいますが、概ね抽出できてそうですね。
以下、他にも試してみた結果。



##・斜陽（太宰治）
![syayou.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/586438/f81a6aa4-d359-4467-4924-b98ea106207b.png)


##・銀河鉄道の夜（宮沢賢治）
![gingatetudou.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/586438/ef6a0444-96e1-e8ec-ad22-df2410041764.png)

##・檸檬（梶井基次郎）
![REMON.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/586438/90bfc4dc-7966-595c-94f4-070fccaea0f1.png)


なんか楽しいな。。。

#まとめ
COTOHAもColabも無償で使用できて、手軽に言語処理に触れる
環境なのですごいいいですね！

以上です、ご一読ありがとうございました！
　
　

　
　
　
　
　



最後に、以下画像は何の作品でしょう！（うざいからやめろ。。）
![gongitune.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/586438/d25e03ce-8573-dd63-ee87-f07c17d9ff29.png)