Perfumeの歌詞で一番使われている単語は何か？

# はじめに
僕はPerfumeが好きなんですが、ふと一番使われている単語が何か調べてみようと思った次第です。  
開始前の僕の予想は「恋」です。

## やること
1. 歌詞をスクレイピングしてくる
2. スクレイピングしてきた歌詞を形態素解析する

たったこれだけ

## 一番使われている単語を調べる
notebook環境で実施しましたが、見やすくするためにソースコードの拡張子は`.py`を使います。  
細かく操作が区切れていますが、notebookのセルだと思ってください（これでも一応ギュッとした方）。

### 初期設定
```やってみる.py
from collections import defaultdict
import os
import random
import requests

from bs4 import BeautifulSoup
import ipadic
import MeCab
from tqdm.notebook import tqdm
```
```やってみる.py
# 定数定義
TARGET_URL = 'https://www.uta-net.com/artist/5555/6/'
```

---

### スクレイピングする
```やってみる.py
response = requests.get(TARGET_URL)
soup = BeautifulSoup(response.text, 'html.parser')
song_titles = soup.find_all('span', 'fw-bold songlist-title pb-1 pb-lg-0')
song_lyrics = soup.find_all('span', 'd-block d-lg-none utaidashi text-truncate')
```
```やってみる.py
len(song_titles) == len(song_lyrics)

>>> True
```

---

### 形態素解析する
```やってみる.py
tagger = MeCab.Tagger(ipadic.MECAB_ARGS)
```
形態素解析をやる上で、超簡単に前処理をしています。
```やってみる.py
total = len(song_titles)
word_counts = defaultdict(lambda: defaultdict(int))
exclude_parts = ['BOS/EOS', '記号', '助詞']

for title, lyric in tqdm(zip(song_titles, song_lyrics), total=total):
    # 不要なタグ等を除去
    title_text = title.text
    lyric_text = lyric.text

    # 形態素解析
    node = tagger.parseToNode(lyric_text)
    while node:
        word = node.surface
        part = node.feature.split(',')[0]  # 品詞

        if part in exclude_parts:
            node = node.next
            continue
        if word == ' ':
            node = node.next
            continue
        
        word_counts['all'][word] += 1
        word_counts[title_text][word] += 1
        
        node = node.next
```

---

結果を確認してみます。果たして、予想は当たるでしょうか..。
```やってみる.py
sorted(word_counts['all'].items(), reverse=True, key=lambda x: x[1])

>>> [('ない', 506),
 ('キミ', 310),
 ('な', 253),
 ('た', 252),
 ('の', 195),
 ('し', 193),
 ('たい', 152),
 ('you', 130),
 ('この', 129),
 ('I', 124),
 ('だ', 120),
 ('恋', 120),
 ('me', 112),
 ('そう', 110),
 ('てる', 110),
 ('よう', 103),
 ('心', 100),
 ('う', 91),
 ('きっと', 85),
 ('こと', 85),
 ('all', 82),
 ('い', 81),
 ('loving', 78),
 ('a', 74),
 ('夢', 73),
 ('何', 70),
 ('あの', 65),
 ('まま', 64),
 ('wanna', 64),
 ('人', 63),
 ('いい', 63),
 ('my', 62),
 ('いつも', 61),
 ('中', 60),
 ('Cling', 60),
 ('Wah', 60),
 ('れ', 59),
 ('up', 58),
 ('今', 57),
 ('love', 56),
 ('ほら', 56),
 ('ん', 56),
 ('spending', 56),
 ...
```
結果は「ない」が一番多く登場した単語になりました！  
<br>
.
<br>
..
<br>
...
<br>
ちょっと納得いかないですね。もう少し品詞を制限するなり、連用詞の除去をするなどしても良かったかなと思います。  
ですが、2番目に多く出現した単語は「キミ」でした。これが実質一番使われている単語というのであれば納得できるので、今回の調査結果としては「**キミ**」が一番多く使われた単語とします！

## 「キミ」が一番使われている曲名が何かを調べる
僕の予想は「Puppy love」でした。
```やってみる.py
target_word = 'キミ'
count_kimi = []

for title in word_counts.keys():
    if title == 'all':
        continue

    cnts = word_counts[title][target_word]
    count_kimi.append([title, cnts])
```
```やってみる.py
sorted(count_kimi, reverse=True, key=lambda x: x[-1])

>>> [['微かなカオリ', 11],
 ['Relax In The City', 10],
 ['Cling Cling', 10],
 ['Cling Cling(Album-mix)', 9],
 ['Zero Gravity', 9],
 ['シークレットシークレット', 9],
 ['Magic of Love', 8],
 ['GLITTER', 8],
 ['love the world', 8],
 ['ナナナナナイロ', 7],
 ['Let Me Know', 7],
 ['Miracle Worker', 7],
 ['いじわるなハロー', 7],
 ['575', 7],
 ['パーフェクトスター・パーフェクトスタイル', 7],
 ['TOKIMEKI LIGHTS', 6],
 ['Clockwork', 6],
 ['GLITTER (Album-mix)', 6],
 ['SEVENTH HEAVEN', 6],
 ['Twinkle Snow Powdery Snow', 6],
 ['メビウス', 5],
 ['Moon', 5],
 ['天空', 5],
 ['Dream Land', 5],
 ['だいじょばない', 5],
 ['Spending all my time', 5],
 ['Puppy love', 5],
 ["Drive'n The Rain", 4],
 ['ポリゴンウェイヴ', 4],
 ['超来輪', 4],
 ['Hold Your Hand', 4],
 ['恋は前傾姿勢', 4],
 ['Spending all my time (Album-mix)', 4],
 ['心のスポーツ', 4],
 ['Have a Stroll', 4],
 ['VOICE', 4],
 ['不自然なガール', 4],
 ['ナチュラルに恋して', 4],
 ...
```
「キミ」が一番使われている曲は「微かなカオリ」でした！これは納得ですね。あと、Puppy loveはそんなに使われていないんですね..。

## 「ない」が一番使われている曲名が何か調べる
一応、Perfumeの歌詞で一番多く出現した単語である「ない」が使われている曲名が何かも調べてみました。  
Perfumeの楽曲で「ない」が使われる場面の多くは、恋愛要素がうまくいかない場面なので、「ない」が多く使われている曲は恋愛系の曲だと思われます（早口）。  
僕の予想は「だいじょばない」でした。  

「キミ」を調べた時と同様のソースコードを実行してみた結果が以下です。
```やってみる.py
>>> [['おいしいレシピ', 21],
 ['パーフェクトスター・パーフェクトスタイル', 19],
 ['1mm', 14],
 ['微かなカオリ', 13],
 ['Have a Stroll', 12],
 ['Flow', 11],
 ['FAKE IT', 11],
 ['575', 11],
 ['Challenger', 10],
 ['不自然なガール', 10],
 ['I still love U', 10],
 ['love the world', 10],
 ['Sweet Refrain', 9],
 ['コンピューターシティ', 9],
 ['アンドロイド&', 8],
 ['いじわるなハロー', 8],
 ['23:30', 8],
 ['コンピューター ドライビング', 8],
 ['ファンデーション', 8],
 ['Hold Your Hand', 7],
 ['Clockwork', 7],
 ['Dream Land', 7],
 ['Handy Man', 7],
 ['Kiss and Music', 7],
 ['ワンルーム・ディスコ', 7],
 ['Dream Fighter', 7],
 ['Moon', 6],
 ['Tiny Baby', 6],
 ['未来のミュージアム', 6],
 ...
```
結果はまさかの「おいしいレシピ」でした！マイナーもマイナーな曲すぎて、ファン力が試されますね。僕が予想した「だいじょばない」はまさかの出現回数1回でした..。  

どうして1回という結果になったのか、「だいじょばない」の歌詞がどのように形態素解析されたかを確認してみた結果が以下です。
```やってみる.py
>>> defaultdict(int,
            {'BABY': 23,
             'だい': 38,
             'じ': 38,
             'ょばないから': 10,
             'それ': 10,
             'ぜんぜん': 13,
             'ょばないけど': 12,
             'しかも': 10,
             'ょばないよね': 5,
             ...
```
「だいじょばない」という歌詞を「だい / じ / ょばないから（けど、よね）」という風に形態素解析されてしまったんですね。それだと「ない」という単語の出現回数が少なくても仕方がないですね。  

これは、形態素解析の性能に依存するものなので、機会があればipadic-NEologdを使ってみて再度チャレンジしてみようと思います。

## おまけ
Perfumeの曲で「再生」という楽曲があります。この曲のMVでは、同じ歌詞が登場している過去の曲のMVを、つぎはぎで繋げるという素晴らしいMVになっています（語彙力）。  
説明が下手なので、一度YouTubeで見てみてほしいです（https://youtu.be/oqxvaDbAHkI?si=44dp1Fm-jHz7IlH- ）。  

今回の形態素解析を用いることで、この曲でつぎはぎされている過去の歌詞を引っ張ってこれるのでは？と思いました。  
「再生」の歌詞と、それ以前の楽曲の歌詞で、同じ歌詞を見つけてみようと思います。
```やってみる.py
target_song_title = '再生'
target_song_lyric = ''
exclude_words = ['', ' ', '\u3000']
dic = defaultdict(set)


for title, lyric in zip(song_titles, song_lyrics):
    title_text = title.text
    lyric_text = lyric.text

    if title_text == target_song_title:
        target_song_lyric = lyric_text
        continue  # 対象の曲は逆引き辞書に含めない

    node = tagger.parseToNode(lyric_text)
    while node:
        word = node.surface
        
        if word in exclude_words:
            node = node.next
            continue
        
        dic[word].add(title_text)

        node = node.next
```
`dic`の中に、`単語: {曲名1, 曲名2, ...}`という具合に特定の単語が出現した曲名を保持しています。  
中身は以下のようになっています。
```やってみる.py
dic

>>> defaultdict(set,
            {'Get': {'Cosmic Treat'},
            
             'ready': {'Cosmic Treat', 'TOKIMEKI LIGHTS', '無限未来'},
             
             'to': {'Cling Cling',
              'Cling Cling(Album-mix)',
              'Cosmic Treat',
              'The best thing',
              'VOICE',
              'ラヴ・クラウド',
              'リニアモーターガール'},
              
             'blast': {'Cosmic Treat'},
             
             'off': {'Cosmic Treat', 'NIGHT FLIGHT', 'Speed of Sound'},
             
             ',': {'Cosmic Treat',
              'Kiss and Music',
              'Starlight dreams',
              'The Light',
              'The best thing'},
             'feel': {'Cosmic Treat',
              'Hurly Burly',
              'Kiss and Music',
              'ハテナビト',
              'ラヴ・クラウド'},
              ...
```
あとは、この辞書を使って、「再生」で使用されている歌詞の単語とヒットする曲名をランダムに選びます。
```やってみる.py
exclude_words = ['', ' ', '\u3000']
res = []

node = tagger.parseToNode(target_song_lyric)
while node:
    word = node.surface

    if word in exclude_words:
        node = node.next
        continue

    try:
        song_title = random.choice(list(dic[word]))
    except:
        song_title = ''

    res.append((word, song_title))
    
    node = node.next
```
```やってみる.py
res

>>> [('最大', ''),
 ('限界', ''),
 ('生き', 'Challenger'),
 ('たい', 'Spring of Life'),
 ('わ', 'Pick Me Up'),
 ('宇宙', '∞ループ'),
 ('全体', ''),
 ('が', 'マワルカガミ (polygon wave live ver.)'),
 ('手品', ''),
 ('いや', 'ジェニーはご機嫌ななめ'),
 ('い', 'マワルカガミ (polygon wave live ver.)'),
 ('正真正銘', ''),
 ('未来', 'さよならプラスティックワールド'),
 ('以来', ''),
 ('偶然', ''),
 ('性', 'さよならプラスティックワールド'),
 ('さえ', 'メビウス'),
 ('運命', 'さよならプラスティックワールド'),
 ('さ', '未来のミュージアム'),
 ('コンピューター', 'コンピューターシティ'),
 ('でも', '不自然なガール'),
 ('解け', "Drive'n The Rain"),
 ('ない', 'コミュニケーション'),
 ('わ', 'If you wanna'),
 ('果てしない', ''),
 ('光線', 'レーザービーム'),
 ('の', '超来輪'),
 ('海', 'Morning Cruising'),
 ('全身全霊', ''),
 ('で', 'イミテーションワールド'),
 ('向かう', 'NIGHT FLIGHT'),
 ('わ', 'FAKE IT'),
 ...
```
一部、過去の楽曲で登場したことがない単語がある模様なので、それだけに絞ってみます。
```やってみる.py
unused_word = set([word for word, title in res if title == ''])
unused_word

>>> {'つなぎとめ',
 'とっくに',
 'ヒカリ',
 '以来',
 '便り',
 '偶然',
 '全体',
 '全身全霊',
 '再生',
 '冷め',
 '巻か',
 '手品',
 '最大',
 '果てしない',
 '正真正銘',
 '生成',
 '行か',
 '覚え',
 '限界'}
```
`ヒカリ`は「無限未来」にありそうだなぁ、と思って確認してみると`光`として歌詞に含まれていました。  今回実施した形態素解析においては、`ヒカリ` ≠ `光`になります。  

こうした表記揺れに対応するためには、日本語の正規化や、表記揺れに対応した辞書をipadicとは別に用意する必要がありますね。  

---

今さら気づきましたが、「再生」がリリースされた以降の曲も含めちゃってました。おまけなのでお許しを〜。

# おわりに
Perfumeが好きだったことも相まって、結構楽しかったです。  
また、自然言語処理の基礎（形態素解析や自然言語の前処理など）を振り返る良い機会になったと思います。