歌詞と曲名の関係性
ゆるめの内容です。
先日、友人とのリモート飲み会で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時間で書けました。
やっていることとしては、
- 指定したアーティストの楽曲リストを歌詞サイトから取得
- 全楽曲のなかで、歌詞の中に曲名を含む割合を算出
実行結果
$ ./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
作詞者の個性を考察してみると面白いと思います。 その意味ではアーティストごとではなく作詞者ごとに集計したほうがよいですね。
では。