歌詞と曲名の関係性

ゆるめの内容です。

先日、友人とのリモート飲み会でMr. Childrenのライブ映像を流していたら、 自然とイントロクイズをする流れになりました。

ただ酔っ払っていることもあって答えあぐねていると、 そのうち桜井さんが歌詞で答えを言っちゃうんですね。

ここでふと疑問に思ったのは、「歌詞の中に曲名の文字列を含む楽曲の割合はどのくらいだろう🤔」ということです。 そこで以前Pythonで書いたスクレイピングのコードを流用し、さくっと集計してみました。

使用したコード

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import time
import re
from urllib.parse import urljoin
import requests
import lxml.html
import argparse


URL_UTA_NET = 'http://www.uta-net.com/'


def main():
    """
    scrape_list_page(): artist の楽曲一覧から song ページの URL を取得
    scrape_song()     : 1つの song のページから曲名、アーティスト名、歌詞を取得
    extract_song_id()     : URL から曲IDを抽出
    """
    parser = argparse.ArgumentParser()
    parser.add_argument(
        'artist_id', help='Artist ID. (show uta-net ' + URL_UTA_NET + ')')
    args = parser.parse_args()

    artist_url = URL_UTA_NET + 'artist/' + args.artist_id

    count_contain = 0
    count_songs = 0
    artist_name = ''

    session = requests.Session()  # 楽曲情報を取得するのに使用するsession
    song_urls = scrape_song_list(artist_url)  # イテレータ

    for i, song_url in enumerate(song_urls):
        song_id = extract_song_id(song_url)
        #print(i, song_url)
        count_songs += 1

        print(i+1)

        response = session.get(song_url)
        song_info = scrape_song(response)
        artist_name = song_info['artist']

        print(song_info)

        if (does_contain_title(song_info)):
            count_contain += 1
            print(' Lyric contains title!!! ')
        else:
            print(' Lyric does not contain title. ')
        print()

        time.sleep(5)

    print()
    print('結果: %sは %d/%d 曲が歌詞にタイトルを含みます' %
          (artist_name, count_contain, count_songs))


def scrape_song_list(artist_url):
    """
    パーマリンク一覧の中から楽曲ごとのURLを抽出
    例えば、<td class="side td1"><a href="/song/69260/">... から "/song/69260"

    2020/04/26: 楽曲一覧が複数ページにまたがる場合に対応
    """

    # ページ数を取得
    response = requests.get(artist_url)
    num_page = int(re.search(r'全([0-9]+)ページ中', response.text).group(1))
    print('num_page: %s' % num_page)

    for i in range(1, num_page + 1):
        try:
            response = requests.get(artist_url + '/0/' + str(i) + '/')
            root = lxml.html.fromstring(response.content)
        except:
            break

        for a in root.cssselect('td.side.td1 a[href^="/song/"]'):
            url = urljoin(response.url, a.get('href'))
            yield url

    return


def scrape_song(response):
    """
    引数 response から曲名、アーティスト、作詞者、作曲者、歌詞を取得
    """
    root = lxml.html.fromstring(response.content)
    song = {'url': response.url,
            'key': extract_song_id(response.url),
            'title': root.cssselect('div.title h2')[0].text,
            'artist': root.cssselect('div.kashi_artist span[itemprop="byArtist name"]')[0].text,
            'lyricist': root.cssselect('div.artist_etc.clearfix h4')[0].text,
            'comporser': root.cssselect('div.artist_etc.clearfix h4')[1].text,
            }
    item = lxml.html.tostring(root.cssselect('#kashi_area')[0]).decode('utf-8')
    lyric = lxml.html.fromstring(item).text_content()
    song['lyric'] = lyric.replace('\u3000', ' ')

    return song


def extract_song_id(url):
    """
    URL から楽曲IDを抽出
    """
    return re.search(r'/song/([0-9]+)/$', url).group(1)


def does_contain_title(song_info):
    """
    TODO: 表記ゆれへの対応
    """
    return song_info['title'].strip() in song_info['lyric']


if __name__ == '__main__':
    main()

以前のコードを流用したので1,2時間で書けました。

やっていることとしては、

  1. 指定したアーティストの楽曲リストを歌詞サイトから取得
  2. 全楽曲のなかで、歌詞の中に曲名を含む割合を算出

実行結果

$ ./scrape_song.py 684    # uta-netでのアーティストID

(中略)

結果: Mr.Childrenは 118/235 曲が歌詞にタイトルを含みます (0.502128)

ほぼ50%でした。意外と少ない印象ですがどうでしょう。

注意点としては、完全一致で判定しているので表記ゆれはカウントされず、実際より低い割合になってしまうことです。 例えば、知らない人はいないであろう代表曲「innocent world」については、 曲名は英字で「innocent world」ですが、歌詞に含むのはカタカナで「イノセント ワールド」です。 この場合は「歌詞に曲名を含む」曲にカウントされません。

まあ集計のポリシーなので何が正しいとかはありませんが。 本音をいうと複雑な判定基準を設けるのが面倒だっただけです。 興味のある方はdoes_contain_title(song_info)をいじってみてください。

おまけ

他のアーティストに対しても集計してみた。

結果: BUMP OF CHICKENは 38/125 曲が歌詞にタイトルを含みます 0.304000
結果: 水樹奈々は 62/276 曲が歌詞にタイトルを含みます 0.224638
結果: FLOWは 57/176 曲が歌詞にタイトルを含みます 0.323864

作詞者の個性を考察してみると面白いと思います。 その意味ではアーティストごとではなく作詞者ごとに集計したほうがよいですね。

では。