向井秀徳は結局「諸行無常」しか言ってないんじゃないか?をデータサイエンス ~歌詞取得編~
はじめに
逆噴射バンドの抽選、当たるといいですね。
当記事の目的
向井秀徳が結局「諸行無常」しか言っていない気がしていたのでこの際はっきりさせようと思います。
本テーマは2部構成でお送りします。テーマが思いついたらまた記事を足します。
- 歌詞取得編 → この記事
- 自然言語処理編 →
この記事は歌詞取得編、言わば事前準備です。歌詞の文字をカウントしたりする前に、まず歌詞サイトから向井秀徳が書いた曲の歌詞を全部取得するコードを実装したいと思います。
参考元
本記事は、歌詞の取得先からその取得方法、分析の方法の何から何まで、以下の記事を参考にさせていただきました。
初めてスクレイピングをする人や、ちょっとpandasの扱い方が怪しい方向けにも、できるだけ中のコードの動きや参考にしたサイトを説明しながら書いていこうと思います。
準備
一旦コードを書く前に、スクレイピング対象やその方法をざっくり確認していきます。
1. 取得サイトの確認
歌ネットを使います。普通の用途としてはあまり使ったことはないですね、昔は自動で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. パッケージインポート
必要なパッケージを読み込みます。まだインストールしていなかったら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
正規表現で文字を取得するのに使用します。正規表現って何って方は下から。
補足③ time
歌詞取得の際に曲数分だけ歌ネットにアクセスする処理を行うので、サイト負荷を考えて取得毎に数秒停止する処理を入れるのに使用します。
2. 関数の定義
今回は各バンドごと、大まかに分けて2回のスクレイピングを実装します。
- アーティストのページにて曲目一覧のデータフレームを取得
- 1のデータフレームを元にして、各曲の歌詞・CD番号名を取得
使用する関数は以下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 GIRL、ZAZEN 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
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
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
調べると、Pixiesのカバー曲「Wave of mutilation」が入っているようでした。
# Black Francisの曲を取得 mukai_lyrics.loc[mukai_lyrics['Lyricist'] == 'Black Francis' ]
参考:
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の操作方法(特にレコード抽出)もよりよい方法がありそう(アドバイス、お待ちしています)
- なんかアルバムが足りない気がする?歌詞ネットに全部のアルバムが上がっていない可能性あり
おわりに
分かち書きの他、頻出単語をランク付けするところまで実行します。