カルチャーとデータサイエンス

ビジネスの意思決定は無理でも、せめてカルチャーはデータサイエンスしたい

向井秀徳は結局「諸行無常」しか言ってないんじゃないか?をデータサイエンス ~歌詞取得編~

はじめに

f:id:fyiwdwytm:20191010082624p:plain
逆噴射バンド
逆噴射バンドの抽選、当たるといいですね。

当記事の目的

向井秀徳が結局「諸行無常」しか言っていない気がしていたのでこの際はっきりさせようと思います。

本テーマは2部構成でお送りします。テーマが思いついたらまた記事を足します。

  1. 歌詞取得編 → この記事
  2. 自然言語処理編 →

この記事は歌詞取得編、言わば事前準備です。歌詞の文字をカウントしたりする前に、まず歌詞サイトから向井秀徳が書いた曲の歌詞を全部取得するコードを実装したいと思います。

参考元

本記事は、歌詞の取得先からその取得方法、分析の方法の何から何まで、以下の記事を参考にさせていただきました。

qiita.com

初めてスクレイピングをする人や、ちょっとpandasの扱い方が怪しい方向けにも、できるだけ中のコードの動きや参考にしたサイトを説明しながら書いていこうと思います。

準備

一旦コードを書く前に、スクレイピング対象やその方法をざっくり確認していきます。

1. 取得サイトの確認

www.uta-net.com

歌ネットを使います。普通の用途としてはあまり使ったことはないですね、昔は自動でitunesに歌詞入れるソフトとかあったな・・

https://www.uta-net.com/artist/7546/みたいな感じでパスパラメータにアーティストのIDを含んでいるので、ちょっと取得が楽そうです。他のサイトも同じかな?

2. 取得アーティストの確認

ご存知、「NUMBER GIRL」並びに「ZAZEN BOYS」にて向井秀徳が作詞した曲を取得対象とします。

曲提供では、最近だと椎名林檎の「神様、仏様」とか、ちょっと昔だとhalの「6階の少女」とかあるけど、取得が面倒なので今回は対象外です。

ちなみに6階の少女、アルバム「ブルー」に入っているverは演奏が向井+54-71でめちゃくちゃかっこいいので是非聞いてほしいです。たしかサブスクで聴けるはずです。

3. 取得方法の定義

言語はPythonを使用、スクレイピングのコアな手法としてはBeautiful Soupを使います。Beautiful Soupは、htmlのような非構造化データを行列を持つ構造化データに変換する(=スクレイピング)ことに特化したライブラリです。

そもそもスクレイピングって?という方は以下の記事をご確認ください。 qiita.com

実装

コードを実装します。なお、実装するコードはすべてJupyter Notebookで書くことを前提としています。

  1. パッケージインポート
  2. 関数(スクレイピング用)の定義
  3. 曲一覧の取得
  4. 歌詞の取得
  5. データ整形
  6. 出力

1. パッケージインポート

必要なパッケージを読み込みます。まだインストールしていなかったらpip installなりconda installしましょう。

import requests # 補足①
from bs4 import BeautifulSoup
import pandas as pd # データフレーム操作
import re # 補足②
from time import sleep #補足③
import sys 
import numpy as np # 行列演算

補足① requests

Beautiful SoupはURLからそのままhtmlを抜き出すことはできません。なので、URLからHTMLを取得するためにrequestsを使用します。

import requests
response = requests.get("https://www.google.com/")

これで、response.textにはHTMLが格納されます。他にもAPIを叩いたりできるみたいですね。

補足② re

正規表現で文字を取得するのに使用します。正規表現って何って方は下から。

qiita.com

補足③ time

歌詞取得の際に曲数分だけ歌ネットにアクセスする処理を行うので、サイト負荷を考えて取得毎に数秒停止する処理を入れるのに使用します。

2. 関数の定義

今回は各バンドごと、大まかに分けて2回のスクレイピングを実装します。

  1. アーティストのページにて曲目一覧のデータフレームを取得
  2. 1のデータフレームを元にして、各曲の歌詞・CD番号名を取得

f:id:fyiwdwytm:20191010235504p:plain

使用する関数は以下3つです。

# 関数①
def scraping_web_page(url):
    sleep(1)
    html = requests.get(url)
    soup = BeautifulSoup(html.content, 'html.parser')
    return soup

# 関数②
def scraping_songlist(url):
    html = requests.get(url)
    soup = BeautifulSoup(html.content, 'html.parser')

    # タグを含むリストtagsを作成
    tags = []
    tags.append(soup.find_all(href=re.compile('/song/\d+/$'))) # href属性が/songs/数値であるタグをすべてリストで取得
    tags.append(soup.find_all(href=re.compile('/song/\d+/$'))) # 上と同じ処理だが、tags[0]はURLのパスパラメータ、tags[1]は曲名に変換する
    tags.append(soup.find_all(class_=re.compile('td2'))) # 歌手名を取得
    tags.append(soup.find_all(class_=re.compile('td3'))) # 作詞者を取得
    tags.append(soup.find_all(class_=re.compile('td4'))) # 作曲者を取得

    # リストtagsからHTMLタグを除いたリストcontentsを作成
    contents = []
    for i, tag in enumerate(tags):
        tmp_list = []
        for element in tag:
            if i == 0: # tags[0]はURLのパスパラメータに変換するためhref属性から、それ以外はstring型の部分から格納値を取得する。
                tmp_list.append(element.get('href')) 
            else:
                tmp_list.append(element.string)
        contents.append(tmp_list)

    # リストcontentsをデータフレームartist_dfに変換
    artist_df = pd.DataFrame({
        'URL' : contents[0],
        'SongName' : contents[1],
        'Artist' : contents[2],
        'Lyricist' : contents[3],
        'Composer' : contents[4]})

    # URLパスパラメータにドメイン名を加えて完全なURLを生成
    artist_df.URL = artist_df.URL.apply(lambda x : 'https://www.uta-net.com' + x)
    return artist_df

# 関数③
def scraping_lyrics(artist_df):
    # 各歌詞サイトのsoupオブジェクトを集めたリストsoupsを生成
    soups = []
    for i, url in artist_df.URL.iteritems():
        soups.append(scraping_web_page(url))
        
    #歌詞、発売日、商品番号をリストに格納する。
    lyrics = []
    sales_dates = []
    cd_nums = []
    for soup in soups:
        lyrics.append(soup.find(id='kashi_area').text) # 該当歌詞のサイトから歌詞エリアを取得する
        sales_dates.append(soup.find(id='view_amazon').text[4:14]) # 該当歌詞のサイトから発売日を取得する
        cd_nums.append(soup.find(id='view_amazon').text[19:28]) # 該当歌詞のサイトからCD番号を取得する(後ほどアルバム名に変換する)

 # inputとなるデータフレームに歌詞、発売日、商品番号の列を追加する。
    artist_df['Lyric'] = lyrics
    artist_df['Sales_Date'] = sales_dates
    artist_df['CD_Number'] = cd_nums 
    return artist_df

関数①scraping_web_page

  • 間隔を空けてhtmlの非構造データを取得します。

関数②scraping_songlist

  • アーティスト一覧のURL(https://www.uta-net.com/artist/<アーティスト番号>/)から、URL、曲名、バンド名、作曲者、作詞者を取得します。

関数①scraping_web_page:

  • 各歌詞のURLから、曲の歌詞、発売日、アルバム名を取得します。大量にURLをスクレイピングする処理が走るので、関数①を利用することで負荷を下げます。

3. 曲目一覧の取得

NUMBER GIRLZAZEN BOYSの曲目一覧をそれぞれ関数scraping_songlistで取得します。その後、取得した2つのデータフレームを向井秀徳の曲一覧として結合します。

nb_songs = scraping_songlist("https://www.uta-net.com/artist/4309/")
zazen_songs = scraping_songlist("https://www.uta-net.com/artist/7546/")
mukai_songs = pd.concat([nb_songs, zazen_songs], ignore_index = True)
mukai_songs

f:id:fyiwdwytm:20191011004502p:plain

4. 歌詞の取得

向井秀徳の曲一覧mukai_songsを引数として、関数scraping_lyricsを呼び出します。mukai_songsに歌詞、発売日、CD番号を付与したデータフレームmukai_lyricsを作ります。

関数を作ってあるので、ここでは処理を呼ぶだけです。

mukai_lyrics = scraping_lyrics(mukai_songs)
mukai_lyrics

5. データ整形

CD番号をアルバム名に変換する処理と、カバー曲(向井秀徳が作曲していない曲)をデータから取り除く処理をします。

CD番号をアルバム名に変換

分析上必要ではないですが、どの曲がどのアルバムに入っているかは分かるようにします。

# CD番号の一覧を取得
mukai_cd_number = mukai_lyrics.groupby('CD_Number')[['URL']].count() #URL列をCD_Numberでグループ化して数をカウント
mukai_cd_number

f:id:fyiwdwytm:20191011211110p:plain

後は根気でwikiで調べ、マッピングしたもので変換します。

mukai_album_list = {
    ':DQC-964S':'すとーりーず',
    ':MSAL-000':'ZAZEN BOYS III',
    ':MSAL-1MA':'ZAZEN BOYS',
    ':MSAL-4DK':'',
    ':MSAL-4MA':'ZAZEN BOYS II',
    ':TOCT-220':'DESTRUCTION BABY',
    ':TOCT-221':'鉄風 鋭くなって',
    ':TOCT-222':'NUM-AMI-DABUTZ',
    ':TOCT-241':'SCHOOL GIRL DISTORTIONAL ADDICT',
    ':TOCT-243':'SAPPUKEI',
    ':TOCT-247':'NUM-HEAVYMETALLIC',
    ':TOCT-256':'OMOIDE IN MY HEAD 1 〜BEST & B-SIDES〜',
    '':'URBAN GUITAR SAYONARA',
}
mukai_lyrics['Album_Name'] = mukai_lyrics['CD_Number'].apply(lambda x : mukai_album_list[x])

カバー曲をデータから取り除く

念のため作曲者の一覧を出力すると、別の作曲者の曲が1曲入っていることがわかります。

mukai_lyricist_list = mukai_lyrics.groupby('Lyricist')[['URL']].count()
mukai_lyricist_list

f:id:fyiwdwytm:20191011211800p:plain

調べると、Pixiesのカバー曲「Wave of mutilation」が入っているようでした。

# Black Francisの曲を取得
mukai_lyrics.loc[mukai_lyrics['Lyricist'] == 'Black Francis' ]

f:id:fyiwdwytm:20191011211915p:plain

参考:


NUMBER GIRL Wave of mutilation

レコードから除く処理を行います。

mukai_lyrics.drop(6, inplace=True)
mukai_lyrics.reset_index(drop=True,inplace=True)
mukai_lyrics

一旦データフレームとしてはこれで完成です。

6. 出力

使い回せるよう、CSVにして保存します。

mukai_lyrics.to_csv("mukai_lyrics.csv")

Todo

  • Beautiful Soupでの曲取得 ~ データフレームの格納はもっとスマートにできる方法がありそう
  • Pandasの操作方法(特にレコード抽出)もよりよい方法がありそう(アドバイス、お待ちしています)
  • なんかアルバムが足りない気がする?歌詞ネットに全部のアルバムが上がっていない可能性あり

おわりに

次回はこのCSVを使って自然言語処理を行います。

分かち書きの他、頻出単語をランク付けするところまで実行します。