COTOHA APIのユーザ属性推定APIを用いてVTuberの本質を暴いてみた（？）

## 注意
* ネタです。広い心で見てやってください

# 動機 

* 自然処理言語に興味があったから
* [なんか面白そうな企画](https://zine.qiita.com/event/collaboration-cotoha-api/?utm_source=qiita&utm_medium=top_banner)があったから
* ベータ版の「ユーザ属性推定API」が人の年齢、性別、習慣、趣味などを文章から分析するというのでバーチャルな存在にも適用できるのか気になったため。

# 流れ
* 有名なVTuberさんの文章を[COTOHA API Portal](https://api.ce-cotoha.com/)の「ユーザ属性推定API」で分析、属性が公称の属性とどのくらい一致しているか調べる。
* ~~数億歳とか2歳とか整数でない年齢とかでも突っ込みは入れずに進める。~~

# コード

設定ファイル書いて分析したいテキストファイルを格納したらターミナルから起動できるようになってます。
環境：Python 3.8.1

 <details><summary>おりたたみ</summary><div>

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

```python:cotoha.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 userAttribute(self, document):
        url = self.developer_api_base_url + "beta/user_attribute"
        headers={
            "Authorization": "Bearer " + self.access_token,
            "Content-Type": "application/json;charset=UTF-8",
        }
        data = {
            "document": document,
            "type": "kuzure"
        }
        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)

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

    # API実行
    print ("ユーザ属性解析を行います。")
    result = cotoha_api.userAttribute(sentence)

    # 出力結果をjson形式に
    result_formated = json.dumps(result, indent=4, separators=(',', ': '))

    # ファイル出力
    new_dir_path = 'file'
    new_filename = 'outfile.json'
    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)

    # ターミナル出力
    num = int(input("結果をターミナルに出力しますか？ するなら1を入力してください。"))
    if num == 1:
        print (codecs.decode(result_formated, 'unicode-escape'))
    else:
        print ("ターミナルに出力しません。")
```

```ini:config.ini
[COTOHA API]
Developer API Base URL: https://api.ce-cotoha.com/api/dev/nlp/
Developer Client id: (write me)
Developer Client secret: (write me)
Access Token Publish URL: (write me)

```
</div></details>


# 調査結果

## ・ケース1：電脳少女シロ氏

* 2~3歳女性（本人公称）
    * ~~鳴き声がイルカだけどイルカじゃないよ~~

* 参考文
   * 配信で放った、俗に言われる「浅漬けコピペ」

> 関係ないんですけど浅漬けってあるじゃないですかぁ。
> あのーお野菜を塩とか糠で、漬け込むもの？ですね。
> あのｗこの世で、一番浅漬けが集結してる場所ってどこかなって考えてみたんですけどｗｗふふっふっｗそれってきっと海水浴場なんじゃないかなって。
>ほらあのー塩分濃度が高い海に自ら浸かりに行くって、それはもう、人間の、浅漬け…ですよね。
>ちなみにシロは浅漬けは好んで食したりはしないです。

* 出力
 <details><summary>出力データ</summary><div>

```json-doc
{
    "result": {
        "age": "40-49歳",
        "gender": "男性",
        "habit": [
            "SMOKING"
        ],
        "hobby": [
            "COLLECTION",
            "COOKING",
            "FISHING",
            "FORTUNE",
            "GAMBLE",
            "SPORT",
            "TRAVEL"
        ],
        "location": "関東"
    },
    "status": 0,
    "message": "OK"
}
```
</div></details>

こいつぁひでえや！
一致してそうなの料理くらいですかね…
50間近のおっさんの文章には見えないけど私のやり方が悪いのか…
なお、最初文章に句読点がところどころ無い状態だとうまく認識してくれなかったので、「w」(笑いを示すスラング）も認識されてない可能性もありそう。
（原文を変えずに笑いのニュアンスを認識させるのが大変だったのでそのままにしております。）

| 項目       | 出力内容 |
|:-----------------|------------------|
| 年齢             |40-49歳|
| 性別           |男性|
| 習慣             |喫煙習慣あり|
| 趣味               |収集家、料理、釣り、占い、ギャンブル、スポーツ、旅行|
|出身地             | 関東 |



## ・ケース2：キズナアイ氏

* 10代女性？（公式データ見つからなかった）
   * 言わずと知れた先駆者。

* 参考文
    * ツイッターより~~（浅漬けコピペ並みの名言が見つからなかったため）~~

> 深夜だからつぶやきます。
> 正直こういうのはあんまり言わない方がいいかなって思ったりもするんだけど、この時間ならいいかなって。
> 多分わかってくれる人は多いんじゃないかな。
> わかってくれたらうれしいです。
> 私の今の、素直な気持ちです。
> 私、やっぱり超かわいい。間違いない。

* 出力
 <details><summary>出力データ</summary><div>

```json-doc
{
    "result": {
        "age": "20-29歳",
        "civilstatus": "既婚",
        "earnings": "-1M",
        "hobby": [
            "COOKING",
            "IDOL",
            "TVDRAMA"
        ],
        "location": "関東"
    },
    "status": 0,
    "message": "OK"
}
```
</div></details>

ケース１と比べて性別の項目が消えてしまった…
[リファレンス](https://api.ce-cotoha.com/contents/reference/apireference.html)見ても消える原因がわからない…不明でも不明と出力されるみたいなのだが…
そして給与にマイナスが付くのか…


| 項目       | 出力内容 |
|:-----------------|------------------|
| 年齢             |20-29歳|
| 既婚/未婚           |既婚|
| 給与             |-1M|
| 趣味               |料理、アイドル、テレビドラマ|
|出身地             | 関東 |

## ・ケース3：ヤマトイオリ氏

* 17.001歳
    * 二時間ぶっ通しで雑談配信できるほどの逸材。話題や言い回しが独特のため採用。エッセイの出版経験あり。

* 参考文
    * 動画概要欄より
    * https://www.youtube.com/watch?v=hqdncOUuBjQ

> 新しく買ったコップみたいなやつの底が傷ついていました。
イオリはん？なんで新しく買ったばっかりなのにこんなに
傷がついてるんだろう？って思いました。
しばらく考えたけど、答えが出なかったです。
でも出たんですよね。
それからしばらくして、新しい飲み物を入れてかき混ぜるときに
底に当てながら回してるってことに気づいて、
「はっっっっっっっっ！」って声が出ました！！
そうなんですよね！イオリは、気が付かないうちに
コップの底をがしがし傷つけていたんですよね。
これが自覚がないうちに相手を傷つけているってことか
ってなりました。。。ちょっと悲しくなりました！
全然イオリはそのことに気が付けなかったから。
でも絶対そうとも限らないということもありますよね！
例えば買ったときから傷ついていた可能性もありますし、
イオリじゃない家族が使ってしめしめ底に傷を入れようとか
いやそんないじわるな家族はいないですが、
真相がわからないものを1つの理由で決めつけるのも
良くないなぁとも思いました！
まあ結局なんでも、人を思いやる気持ちってすごく大事だなあと。
コップから大切なことを改めて学びました、
イオリちゃんだよ〜〜|･ω･`)だれぇえ

* 出力
 <details><summary>出力データ</summary><div>

```json-doc
{
    "result": {
        "age": "40-49歳",
        "civilstatus": "未婚",
        "hobby": [
            "CAMERA",
            "FORTUNE",
            "GAMBLE",
            "GOURMET",
            "SPORT",
            "TVDRAMA"
        ],
        "moving": [
            "CAR",
            "RAILWAY",
            "WALKING"
        ]
    },
    "status": 0,
    "message": "OK"
}
```
</div></details>

こちらは結構文章量あるから期待していたが年齢はかなりかけ離れたものに。
ただし、趣味はギャンブル以外はそこそこいい線ついていそう。
リファレンスにもあったが「移動手段」の項目の判断基準が気になる。


| 項目       | 出力内容 |
|:-----------------|------------------|
| 年齢             |40-49歳|
| 既婚/未婚           |未婚|
| 趣味               |カメラ、占い（?）、ギャンブル、グルメ、スポーツ、TVドラマ|
|移動手段             | 車、鉄道、徒歩 |



せっかくなのでもう一つ解析してみる。

## ・ケース4：ヤマトイオリ氏2

* 参考文その2
    * 動画概要欄より
    * https://www.youtube.com/watch?v=5xLfIZdLA30

> はじめまして、いつもありがとうございますっ！
.LIVE所属 アイドル部 ヤマトイオリです🎀
んっ！？いったいここは。。。！？！？！？！
ふぉっふぉっふぉっふぉっ
ここは2018年の次の世界、2019年じゃよ。
なんとーー！？
みんなさん、新年開けましておめでとうございますっ！
ああああああおめでとうございます(っ´ω`)っ🍄
さて、もう年越してから4日経っちゃったよっ！
たしかに、そうですよね、ごめんなさいっ！
年越しの瞬間、なにしていましたか？
ジャンプしたしぃ〜！ワタシ年越しの瞬間地面にいなかったしぃ〜！
とか、しましたか？イオリもそれ去年とか、やったなあ。
二度はやりますよね、ジャンプ年越し。
どこかのアイドル部のハイディーちゃんは、壁倒立しながららしいね🐮
今年はシロちゃんやアイドル部の子と年越せた方が多くて
幸せな年の幕開けだなって思いますた(๑•̀ㅂ•́)و✧
（ぬっ）
今年の抱負や意気込み、気持ちは配信で伝えたいと思います。
今年はしいたけを食べすぎて本当に日焼けとかじゃなく
顔が茶色くなるんじゃないかと怖くなりました。
でもしいたけ食べるお箸が止まらないんだもん🍄( ›ω‹ )🍄
なんであんなに美味しいんだろう。。
おいしいたけー！って思わず叫んでしまいますよね。
そうですよね。そんな幕開けです。

* 出力
 <details><summary>出力データ</summary><div>

```json-doc
{
    "result": {
        "age": "40-49歳",
        "civilstatus": "未婚",
        "gender": "男性",
        "hobby": [
            "COOKING",
            "FORTUNE",
            "GOURMET",
            "MOVIE",
            "SHOPPING",
            "TRAVEL"
        ],
        "moving": [
            "WALKING"
        ],
        "occupation": "会社員"
    },
    "status": 0,
    "message": "OK"
}

```
</div></details>

別の文章でも40-49歳扱いになっている。
趣味も近しいものが並んでいるので、正しいかはともかく近しい人物の文章として認識されている模様。


| 項目       | 出力内容 |
|:-----------------|------------------|
| 年齢             |40-49歳|
| 既婚/未婚           |未婚|
| 性別           |男性|
| 趣味               |料理、占い（?）、グルメ、ムービー、ショッピング、旅行|
|移動手段             | 徒歩 |
|職業             | 会社員 |


# 結論

バーチャルな存在には属性推定は難しい
~~そもそも二歳児だったり数億歳だったりする連中の属性推定は無理~~
気が向いたら別のVTuberさんでもやってみたい
ケースによって出ない項目やおかしな値も余裕があったら続編として調べる
むしろイオリンの概要欄の文章を全て解析した方が面白いデータが取れそう

# 参考
* [自然言語処理を簡単に扱えると噂のCOTOHA APIをPythonで使ってみた](https://qiita.com/gossy5454/items/83072418fb0c5f3e269f)
* [COTOHA API Portal](https://api.ce-cotoha.com/)
* [電脳少女シロ](https://www.youtube.com/channel/UCLhUvJ_wO9hOvv_yYENu4fQ)
* [キズナアイ](https://www.youtube.com/channel/UC4YaOt1yT-ZeyB0OmxHgolA)
* [ヤマトイオリ](https://www.youtube.com/channel/UCyb-cllCkMREr9de-hoiDrg)