スポーツ記事から選手と技名を抽出してみた

# Motive
[【Qiita x COTOHA APIプレゼント企画】](https://zine.qiita.com/event/collaboration-cotoha-api/?utm_source=qiita&utm_medium=article_banner)関連の投稿です。

[COTOHA API で照応解析してもギブミーチョコレート出来ていない問題](https://qiita.com/satsukiya/items/a44979bb2e5bbb6f907e)とは別のAPIを使ってみました。

今回は
固有表現抽出(/nlp/v1/ne)
APIです。

MeCabだったら固有名詞を学習をして辞書に登録しないと人物名が抽出できなかった気がします。あと、KNPは精度は良さそうなのですがパッケージ自体重いです。:scream:
また、形態素解析すると機械学習された分配器が優秀なのか品詞までは正確に出力されるのですが、文章の中で多く出て来る名詞を分類できていたかと言うとそうでもない気がします。
COTOHAではAPIだけで名詞の細かい分類をしてくれます。

どこまで固有名詞を抽出できるかを簡易的にトライするために、スポーツ記事から人物名・技名を出力してみました。


# Environment
- Python  3.6.8
- COTOHA API (https://api.ce-cotoha.com/)

# Dataset
[東京スポーツ](https://www.tokyo-sports.co.jp/)
選定基準としては住んでいる地域ではこのスポーツ新聞が発売されていないという高尚な理由です。:camera_with_flash:

# Method
前述の通り、
COTOHA API 固有抽出
https://api.ce-cotoha.com/contents/reference/apireference.html#parsing_io_part
を使っています。

選手(人物)は`x["class"] == "PSN" and x["extended_class"] == ""`、技名は`x["class"] == "ART" and x["extended_class"] in [
        "Doctrine_Method_Other"]`
で抽出しています。`Doctrine_Method_Other` は(主義方式名_その他）という意味です。

| 名称 | 説明 |
|:--|:--|
| ORG | 組織名 |
| PSN | 人名 |
| LOC | 場所 |
| ART | 固有物名 |
| DAT | 日付表現 |
| TIM | 時刻表現 |
| NUM | 数値表現 |
| MNY | 金額表現 |
| PCT | 割合表現 |
| OTH | その他 |

# Development

#### Script

<details > <summary> <font color="Brown">**スクリプトコード** (クリックするとコードが表示されます。) </font> </summary><div>

```Python
import argparse
import requests
from bs4 import BeautifulSoup
import json

#--- この4つパラメータはPortalで取得 ---
PUBLISH_URL = "--- get your parameter ---"
CLIENT_ID = "--- get your parameter ---" 
CLIENT_SECRET = "--- get your parameter ---" 
BASE_URL = "--- get your parameter ---"


class COTOHA:

    def __init__(self):
        self._token = self._getAccessToken()

    def _getAccessToken(self):
        header = {"Content-Type": "application/json"}
        contents = {
            "grantType": "client_credentials",
            "clientId": CLIENT_ID,
            "clientSecret": CLIENT_SECRET
        }
        raw_res = requests.post(PUBLISH_URL, headers=header, json=contents)
        response = raw_res.json()
        return response["access_token"]

    def compose(self, sentence):
        header = {
            "Authorization": "Bearer {}".format(self._token),
            "Content-Type": "application/json"
        }
        contents = {
            "sentence": sentence
        }
        raw_res = requests.post(
            BASE_URL +
            "nlp/v1/parse",
            headers=header,
            json=contents)
        response = raw_res.json()
        return response

    def properNoun(self, sentence):
        header = {
            "Authorization": "Bearer {}".format(self._token),
            "Content-Type": "application/json"
        }
        contents = {
            "sentence": sentence
        }
        raw_res = requests.post(
            BASE_URL +
            "nlp/v1/ne",
            headers=header,
            json=contents)
        response = raw_res.json()
        return response

    def keyword(self, sentence):
        header = {
            "Authorization": "Bearer {}".format(self._token),
            "Content-Type": "application/json"
        }
        contents = {
            "document": sentence
        }
        raw_res = requests.post(
            BASE_URL +
            "nlp/v1/keyword",
            headers=header,
            json=contents)
        response = raw_res.json()
        return response

    def coreference(self, sentence):
        header = {
            "Authorization": "Bearer {}".format(self._token),
            "Content-Type": "application/json"
        }
        contents = {
            "document": sentence
        }
        raw_res = requests.post(
            BASE_URL +
            "nlp/v1/coreference",
            headers=header,
            json=contents)
        response = raw_res.json()
        return response


def extract_norn_list(_apiobj, contents, condition):
    dst = []
    for p in contents:
        items = _apiobj.properNoun(p.text)["result"]
        _raw = list(filter(condition, items))

        # print(_raw)
        # 略称は除く
        for _p in _raw:
            name = _p["form"]
            _exist = False
            for pname in dst:
                if name in pname:
                    _exist = True
            if not _exist:
                dst.append(name)
    return dst


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--url")
    args = parser.parse_args()

    #APIオブジェクトを作成
    cotoha = COTOHA()

    #URLから記事を取得(東京スポーツ仕様)
    res = requests.get(args.url)
    soup = BeautifulSoup(res.text, 'html.parser')
    title_text = soup.find('title').get_text()
    contents = soup.find('div', {"class": "detail-content"}).find_all("p")

    #抽出条件
    def is_person(x): return x["class"] == "PSN" and x["extended_class"] == ""
    def is_attack(x): return x["class"] == "ART" and x["extended_class"] in [
        "Doctrine_Method_Other"]

    #選手を出力
    print(extract_norn_list(cotoha, contents, is_person))
    #技名を出力
    print(extract_norn_list(cotoha, contents, is_attack))

if __name__ == "__main__":
    main()

```

</div></details>

#### Command

```bash
python main.py --url https://www.tokyo-sports.co.jp/prores/ddt/1754700/
```

# Consequence

2つの記事を使って実行します。

### 【新日１・５東京ドーム】みのる　ＵＳ王座防衛のモクスリー襲撃「誰にケンカ売ってんだ！」
https://www.tokyo-sports.co.jp/prores/njpw/1682622/

#### dataset

```text
新日本プロレスの年間最大興行「レッスルキングダム１４」（５日、東京ドーム）で行われたＩＷＧＰ・ＵＳヘビー級王座戦は、王者ジョン・モクスリー（３４）がＩＷＧＰタッグ王者のジュース・ロビンソン（３０）の挑戦を退け、初防衛に成功した。
　前夜（４日）の東京ドーム大会では、モクスリーがランス・アーチャー（３２）から王座を奪回。ジュースはデビッド・フィンレー（２６）とのコンビでタッグ王座を獲得した。その翌日に新王者同士の決戦となったが、モクスリーは昨年６月にジュースから同王座を奪っており、前夜のリング上で決着をつけることを宣言していた。
　序盤はジュースが快調に先制したものの、モクスリーは場外でイスを持ち出して背中に一撃。さらにはジュースの額にかみついた。ＷＷＥ時代に“狂犬”として暴れ回った荒くれ者が、強引にペースを奪い返した。
　ジュースは豪快なハイアングルパワーボムで反撃したが、王者は足４の字固めから鉄柱を使った４の字と意表を突いた攻撃を連発した。挑戦者は雪崩式ブレーンバスターからジャックハマーにジャーマン。モクスリーのデスライダー（ダブルアーム式ＤＤＴ）をかわして、ラリアートでぶち抜いた。
だが、王者は乱打戦から強烈なランニングニーを一閃。ジュースのパルプフリクションを切り返して、ＤＤＴから必殺のデスライダーを炸裂させて１２分４８秒、３カウントを奪った。
　試合後には入場テーマ曲が流れ、いきなり鈴木みのる（５１）が登場。昨年１２月８日の広島大会でモスクリーからデスライダーを見舞われており、険しい表情で怒りを隠せない。花道でジャージーを脱いで戦闘態勢に入ると、リング上で王者とエルボーを打ち合った。ド迫力のみのるは裸絞めからゴッチ式パイルドライバーでモクスリーをＫＯした。
　みのるはマイクを握ると「誰にケンカ売ってんだ、このヤロー！　オレはプロレスラーの鈴木みのるだ。こいつのケンカ、オレが買ってやる！」と宣戦布告。ＵＳ王座を巡る“狂犬”対“性悪男”の抗争勃発で、危険な香りが漂ってきた。
　みのるの話「誰にケンカ売ってんだ、おい。俺は、お前が俺の前に来るのを待ってたんだよ。ジョン・モクスリー…いや、ジョン・ボーイ、心してかかってこい。ぶち殺す」
　ジュースの話「すべてはここで終わりだ。ジョン・モクスリーは今日も俺より強かった。また超えられなかった。今日のことを考えたのは昨日の試合が終わってから。それまでまったく今日の試合のことなんて考えていなかった」
```

#### output

```
['ジョン・モクスリー', 'ランス・アーチャー', 'デビッド・フィンレー', '鈴木みのる', 'ジョン・ボーイ']
['足4の字固め', '雪崩式', 'ジャックハマー', 'ラリアート', '裸絞め']
```

### 【新日１・４東京ドーム】内藤　逆転でＩＣ王座奪回も「目的はこのベルトじゃない」
https://www.tokyo-sports.co.jp/prores/njpw/1681815/

#### dataset

```
新日本プロレスの年間最大興行「レッスルキングダム１４」（４日、東京ドーム）で行われたＩＷＧＰインターコンチネンタル（ＩＣ）選手権は、内藤哲也（３７）が王者のジェイ・ホワイト（２７）を撃破。王座を奪回するとともに、５日東京ドーム大会でＩＷＧＰヘビー級王者（オカダ・カズチカＶＳ飯伏幸太の勝者）とのダブルタイトルマッチに駒を進めた。
昨年９月の神戸大会でジェイに敗れ、昨年２度目となるＩＣ王座からの陥落。東京スポーツ新聞社制定「プロレス大賞」ではノミネート「０」の屈辱も味わった。しかし、大観衆は“制御不能男”の復活を待ち望んでいる。序盤から大・内藤コールで背中を押すと、ジェイには容赦ないブーイングを浴びせた。
 内藤は場外でエプロンを使ったネックブリーカーを放って先制。ところが、ジェイのセコンドの外道が場外から内藤の足を引っ張りペースを乱す。王者は内藤の左ヒザに狙いを絞って攻め込んだ。内藤はコーナーからの飛びつきフランケンシュタイナーで反撃。相手に顔につばを吐きかけてから、串刺しの低空ドロップキックだ。
これでペースを握るかとみられたが、ジェイのＤＤＴを食らって悶絶し、再び左ヒザを攻められた。場外にはバックドロップで投げ捨てられ、劣勢は変わらない。さらに裏足４の字固めでヒザを締め上げられた。
　大ピンチの内藤は顔をゆがませながらロープブレーク。何とか脱出すると、逆襲の浴びせ蹴りだ。さらにスパインバスター、回転式ＤＤＴ、雪崩式フランケンシュタイナー、グロリアの猛攻。レフェリーがアクシデントでダウンする隙に外道が乱入したが、急所打ちで撃退した。
　勝負をかけた内藤は、コリエンド式デスティーノを連発。ジェイの必殺ブレードランナー（変型顔面砕き）を完全に防ぎ切ると、最後は渾身のデスティーノで３カウントを奪った。
　３３分５４秒の大激闘に逆転勝利。昨年１月からＩＷＧＰとＩＣ、２冠奪取の野望を掲げてきた“制御不能男”は、完全復活へ一世一代の大舞台に挑む。
【内藤の話】「今回の２連戦の目的はこのベルトを取ることじゃないから。お客様は『内藤おめでとう』って声かけてくれてうれしいよ。でも、トランキーロ。今回の目的ではないから、そこは。さあて、明日の対戦相手はどっちかな。俺の予定はオカダ。理想も、オカダ。さあ、どうかな」
【ジェイ・ホワイトの話】「アイツ（内藤）はどこに行った…。俺は残念ながらみんなが作り上げたストーリーの脇役の一人だったということだったんだな。みんな、ジェイ・ホワイトが負けるのを望んでいたはずだ。お前らが好きな内藤が勝ったんだぞ。なぜ笑わないんだ。新しい俺のデスティーノ…運命が明日から始まる」
```

#### output

```
['内藤哲也', 'ジェイ・ホワイト', 'オカダ・カズチカ', '飯伏幸太', 'デスティーノ...運命']
['ネックブリーカー', 'バックドロップ', '足4の字固め', 'スパインバスター']
```

# Consideration
- 選手の名称は「デスティーノ...運命」以外主要メンバーは抽出ができている。一般的な人物名は問題なく分類できてそうです。
- 技名ですが、残念ながらCOTOHA APIの分類には出てこないです。何回かAPIからの出力で一番抽出できてそうな組み合わせだったのが`class:ART, extended_class:Doctrine_Method_Other`だったので出力してみたのですが、「ハイアングルパワーボム」「コリエンド式デスティーノ」あたりが対象外になってしまいます。２番目の条件として`class:ART, extended_class:Product`も追加すると技名以外も抽出されるので100%は厳しかったです:tired_face:
- スポーツ記事ではなく専門書であれば効力を発揮できるかもしれません。APIに下記のtypeのパラメータを付与することができるからです。(for Enterpriseユーザのみ、、、なので有料であれば利用可能です。)

|param|name|
|:-:|:-:|
|IT|コンピュータ・情報・通信|
|automobile|自動車|
|chemistry|化学・石油工業|
|company|企業|
|construction|土木建築|
|economy|経済・法令|
|energy|電力・エネルギー|
|institution|機関・団体|
|machinery|機械|
|medical|医学|
|metal|非鉄・金属|

# PostScript
人物名抽出の精度はいいと言ったのですが、なぜか最近引退した「獣神サンダー・ライガー」が正しく抽出されなかったです。「ART:固有物名」の分類になっていました。:japanese_ogre:
これはタレント名鑑のスタッフに出動応援した方が良さそうでしょうか:thinking:。
:sushi:としてです。