感情分析でサボテンは踊るのをやめてしまうか

# やりたいこと

COTOHA　APIの感情分析で文章を読み込んでいって、ネガティブな値が一定値を超えたらサボテンが踊るのをやめる
~~流行りのネタに乗るなとか言わないで~~

# 元ネタ

突如としてツイッターにて流行り始めたこのサボテン
「みんながインターネットで争ってばかりいる」のくだりと[前使ったCOTOHA　API](https://zine.qiita.com/event/collaboration-cotoha-api/?utm_source=qiita&utm_medium=top_banner)でちょっと思いついたため記事にした。

https://twitter.com/_rotaren_/status/1229235440441417728?s=20
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ㅤㅤ <br><br> ₍₍⁽⁽🌵₎₎⁾⁾<br><br>見て！サボテンが踊っているよ<br>かわいいね<br><br>ㅤ<br><br> 🌵<br><br>みんながインターネットで争ってばかりいるので、サボテンは踊るのをやめてしまいました<br>お前のせいです<br>あ〜あ</p>&mdash; 川崎 (@_rotaren_) <a href="https://twitter.com/_rotaren_/status/1229235440441417728?ref_src=twsrc%5Etfw">February 17, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


# イメージ

イメージとしてはターミナルで一文ずつ（ツイッターの140文字程度が目安）入力したら踊っているサボテンが返ってくる
感情値が一定ラインを越えたらサボテンは踊るのをやめる
本気で実装しようとしたら都度DBに入れてカウントすることになるが今回はそこまでしない

# コード

とりあえず感情値がネガティブで0.5を超えたらサボテンが止まるようにした

環境：Python 3.8.1

 <details><summary>コードおりたたみ</summary><div>

```
top/
　├ file/
　│　├ infile.txt   (分析する文章置き場）
　│　└ outfile.json (出力結果）
　├ config.ini
  └ cotoha_saboten.py
```

```python:cotoha_saboten.py
# -*- coding:utf-8 -*-

import os
import urllib.request
import json
import configparser
import codecs

class CotohaApi:
    def __init__(self, client_id, client_secret, developer_api_base_url, access_token_publish_url):
        self.client_id = client_id
        self.client_secret = client_secret
        self.developer_api_base_url = developer_api_base_url
        self.access_token_publish_url = access_token_publish_url
        self.getAccessToken()

    def getAccessToken(self):
        url = self.access_token_publish_url
        headers={
            "Content-Type": "application/json;charset=UTF-8"
        }

        data = {
            "grantType": "client_credentials",
            "clientId": self.client_id,
            "clientSecret": self.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)
        self.access_token = res_body["access_token"]

    # 感情分析API
    def sentiment(self, sentence):
        url = self.developer_api_base_url + "v1/sentiment"
        headers={
            "Authorization": "Bearer " + self.access_token,
            "Content-Type": "application/json;charset=UTF-8",
        }
        data = {
            "sentence": sentence
        }
        data = json.dumps(data).encode()
        req = urllib.request.Request(url, data, headers)
        try:
            res = urllib.request.urlopen(req)
        except urllib.request.HTTPError as e:
            print ("<Error> " + e.reason)

        res_body = res.read()
        res_body = json.loads(res_body)
        return res_body

if __name__ == '__main__':
    APP_ROOT = os.path.dirname(os.path.abspath( __file__)) + "/"

    # config.iniの値を取得
    config = configparser.ConfigParser()
    config.read(APP_ROOT + "config.ini")
    CLIENT_ID = config.get("COTOHA API", "Developer Client id")
    CLIENT_SECRET = config.get("COTOHA API", "Developer Client secret")
    DEVELOPER_API_BASE_URL = config.get("COTOHA API", "Developer API Base URL")
    ACCESS_TOKEN_PUBLISH_URL = config.get("COTOHA API", "Access Token Publish URL")

    cotoha_api = CotohaApi(CLIENT_ID, CLIENT_SECRET, DEVELOPER_API_BASE_URL, ACCESS_TOKEN_PUBLISH_URL)

    # 踊れサボテン
    saboten_dancing = 1
    print ("見て！サボテンが踊っているよ")
    print ("かわいいね")
    print ("何かつぶやいてみよう")
    while saboten_dancing == 1:
        saboten_listen = input(">> ")

        # ファイルに入力
        new_dir_path = 'file'
        new_filename = 'infile.txt'
        os.makedirs(new_dir_path, exist_ok=True)
        with open(os.path.join(new_dir_path, new_filename), mode='a', encoding='utf-8') as f:
            f.write(saboten_listen + "\n" )

        # file/infile.txtから解析対象文取得
        checkpath = 'file/infile.txt'
        test_data = open(checkpath, "r", encoding='utf-8')
        sentence = test_data.read()
        test_data.close()

        # API実行
        result = cotoha_api.sentiment(sentence)

        # 確認用に出力結果をファイル出力
        new_dir_path = 'file'
        new_filename = 'outfile.json'
        result_formated = json.dumps(result, indent=4, separators=(',', ': '))
        new_file_content = codecs.decode(result_formated, 'unicode-escape')
        os.makedirs(new_dir_path, exist_ok=True)
        with open(os.path.join(new_dir_path, new_filename), 'w', encoding='utf-8') as f:
            f.write(new_file_content)

        # 踊るのをやめる
        if result['result']['sentiment'] == "Negative" and result['result']['score'] > 0.5:
            print ("みんながインターネットで争ってばかりいるので、サボテンは踊るのをやめてしまいました")
            print ("お前のせいです")
            print ("あ～あ")
            saboten_dancing = 0
        else:
            print ("サボテンは踊り続けています")


```

</div></details>

# やってみた

* とりあえず無難に

```

$ py .\cotoha_saboten.py
見て！サボテンが踊っているよ
かわいいね
何かつぶやいてみよう
>> こんにちは。
サボテンは踊り続けています
```
（感情値：Neutral、0.3162340660855847）

* 続けて攻撃的に

```

>> 使えないやつだな。
みんながインターネットで争ってばかりいるので、サボテンは踊るのをやめてしまいました
お前のせいです
あ～あ
```
（感情値：Negative、0.6537253556009858）

* 再度起動して少し優しく

```

>> 嘘だよ、いい子だよ
サボテンは踊り続けています
```
（感情値：Neutral、0.28391280932941443）

最初から「嘘だよ、いい子だよ」と入力すると感情値：Positive、0.5477461521779882だったのでとりあえず前の結果が影響するようにできてる。
ひとまずネガティブな話をしていると踊るのをやめるサボテンになった。

# 争ってみた

沸点低いなこのサボテン。
文章が少ないとすぐ閾値を超えそう。

```
見て！サボテンが踊っているよ
かわいいね
何かつぶやいてみよう

>> 何々って小説面白いね。
サボテンは踊り続けています
>> 何々はつまらん。読んでいる奴はバカ。
みんながインターネットで争ってばかりいるので、サボテンは踊るのをやめてしまいました
お前のせいです
あ～あ
```

# 争ってみた（長文）

適当に作った例文

>A: 何々って漫画面白いね
B: わかる、特に誰誰が子猫をかばって倒れるシーンがいい
A: は？一番は強敵誰誰との死闘だろ？わかってないな、にわかか？
B: そんなの人それぞれだろ、うるさいな
A: ちゃんと読んでないだろ、クズが。
B: お前みたいなつまらない奴に読まれた漫画がかわいそうだな！
A: 黙れバカの分際で
B: お前こそ黙れ
A: お前と話してると不幸になる


* 意外とサボテンが踊り続けている
* 「バカ」「クズ」などの罵倒や煽りを検知できないから思ったよりネガティブになっていない
* 閾値を0.5にしていたが、ネガティブになった段階で止めてもよさそう。

```
見て！サボテンが踊っているよ
かわいいね
何かつぶやいてみよう

>> 何々って漫画面白いね
サボテンは踊り続けています
>> わかる、特に誰誰が子猫をかばって倒れるシーンがいい
サボテンは踊り続けています
>> は？一番は強敵誰誰との死闘だろ？わかってないな、にわかか？
サボテンは踊り続けています
>> そんなの人それぞれだろ、うるさいな
サボテンは踊り続けています
>> ちゃんと読んでないだろ、クズが。
サボテンは踊り続けています
>> お前みたいなつまらない奴に読まれた漫画がかわいそうだな！
サボテンは踊り続けています
>> 黙れバカの分際で
サボテンは踊り続けています
>> お前こそ黙れ
サボテンは踊り続けています
>> お前と話してると不幸になる
サボテンは踊り続けています
```

 <details><summary>API側からの出力</summary><div>

```
{
    "result": {
        "sentiment": "Negative",
        "score": 0.11156484777896825,
        "emotional_phrase": [
            {
                "form": "うるさいな",
                "emotion": "N"
            },
            {
                "form": "面白いね",
                "emotion": "P"
            },
            {
                "form": "かわいそうだな",
                "emotion": "N"
            },
            {
                "form": "つまらない",
                "emotion": "N"
            },
            {
                "form": "不幸",
                "emotion": "N"
            },
            {
                "form": "わかってないな",
                "emotion": "N"
            },
            {
                "form": "いい",
                "emotion": "P"
            }
        ]
    },
    "status": 0,
    "message": "OK"
}
```

</div></details>


# 改良版

サボテンが止まる条件をネガティブのみに

```
見て！サボテンが踊っているよ
かわいいね
何かつぶやいてみよう

>> 何々って漫画面白いね
サボテンは踊り続けています
>> わかる、特に誰誰が子猫をかばって倒れるシーンがいい
サボテンは踊り続けています
>> は？一番は強敵誰誰との死闘だろ？わかってないな、にわかか？
サボテンは踊り続けています
>> そんなの人それぞれだろ、うるさいな
サボテンは踊り続けています
>> ちゃんと読んでないだろ、クズが。
サボテンは踊り続けています
>> お前みたいなつまらない奴に読まれた漫画がかわいそうだな！
みんながインターネットで争ってばかりいるので、サボテンは踊るのをやめてしまいました
お前のせいです
あ～あ
```

これで争いが始まったら踊りをやめるサボテンが爆誕しました。
~~5chのスレに常駐させたい~~
めでたしめでたし。

# 追記(2020/02/21)

方々から意見貰えて嬉しいです。
ネガティブな発言をすると「幸福は義務ですよ？」と警告されるディストピアSNSだったり、ポジネガ逆転させて争いを好む悪魔みたいなのも作れるとのお話があって面白いなって思いました。
結構応用ききそうです。


# 参考
* [自然言語処理を簡単に扱えると噂のCOTOHA APIをPythonで使ってみた](https://qiita.com/gossy5454/items/83072418fb0c5f3e269f)
* [COTOHA API Portal](https://api.ce-cotoha.com/)