私はMr.Childrenが中学生の時から好きで、アルバムは全て持っていますし、ライブにも10回以上は行っています。
最近、Pythonの勉強を初めてWEBスクレイピングという技術や、テキストマイニングという技術があることを知り、ふとミスチルの歌詞でどんな単語が多く使われているのかを調べてみたくなりました。
そのためにまずは歌詞を集めないといけませんが、持っているCDの歌詞カードを見て手打ちで入力していくのは大変ですし、歌詞サイトからコピペしていくのも大変です。
そこでPythonのライブラリである「Beautiful Soup」を使って、歌詞サイトから歌詞を自動で取得してcsvに出力することにしました。
実際にやってみたところ、243曲の歌詞を約5分で取得することができました。
- Pythonのライブラリ「Beatiful Soup」を使用したWEBスクレイピングの方法
- 歌詞サイトから自動で歌詞を収集する方法
- 歌詞のみではなく、アルバム名や発売日などもいっしょに取得する方法
集めた歌詞をCSVに出力した結果
対象としたのは、2021年時点で発表されている曲を対象としています。
集めたのは歌詞だけではなく、以下のような項目もデータ収集しました。
- 曲名
- 発売日
- 表示回数(歌詞ネットで歌詞を見られた回数)
- 歌詞
発売日や表示回数は、後でデータを分析する際に使いたかったので収集しました。
以下がcsvに出力したファイルをエクセルで開いた結果です(曲名や歌詞は、書き換えていますが実際には入っています)。
楽曲数は243曲です。
上記は全楽曲中でどんな単語が一番使われているかを分析する際に使えます。
ただし、上記にはアルバム情報が入っておらず、アルバム別に分析を行いたい場合のためにアルバム情報も入った別のパターンでもデータ収集しました。
以下がcsvに出力したファイルをエクセルで開いた結果です(曲名や歌詞は、書き換えていますが実際には入っています) 。
上記のデータはアルバム情報が入っていますが、同じ曲が複数のアルバムに入っている場合もあるので(例えば、オリジナルアルバムとベストアルバム)、全楽曲数は301曲になっています。
同じ曲が複数入っているので、分析の際に注意が必要そうです。
スクレイピングで歌詞を自動収集する
スクレイピングで歌詞を集めるサイトは「歌ネット」にしました。
見る限り、ミスチルの楽曲は揃っていそうです。
まずは、ミスチルの歌詞一覧のページを確認します。
全243曲の歌詞が、2ページに渡って搭載されています。
歌詞ページに入ると、曲のタイトルや発売日、表示回数や歌詞が搭載されています。
このようなサイト構成になっているのを踏まえて、歌詞を取得していきます。
コード
まず、私の作業環境について触れておきます。
- Windows10 Home 64ビット
- python 3.8.3
- beautifulsoup4 4.9.1
- requests 2.24.0
スクレイピングを行ったPythonコードは以下です。
内容や自分が工夫した点については、コードの後で説明します。
import requests
from bs4 import BeautifulSoup
import pandas as pd
import time
import re
from datetime import datetime
#スクレイピングしたデータを入れる表を作成
list_df = pd.DataFrame(columns=['曲名','発売日', '表示回数', '歌詞'])
for page in range(1,3): # ミスチルの歌詞ページは2ページなので、範囲は1-2。 base_url = 'https://www.uta-net.com' #歌詞一覧ページ url = 'https://www.uta-net.com/artist/684/0/' + str(page) + '/' response = requests.get(url) soup = BeautifulSoup(response.text, 'lxml') links = soup.find_all('td', class_='side td1') for link in links: a = base_url + (link.a.get('href')) #歌詞詳細ページ response = requests.get(a) soup = BeautifulSoup(response.text, 'lxml') # 曲名を取得 song_name = soup.find('h2').text # 発売日、表示回数などを取得 detail = soup.find('p', class_="detail").text # 発売日を取得 match = re.search(r'\d{4}/\d{2}/\d{2}', detail) release_date = datetime.strptime(match.group(), '%Y/%m/%d').date() # 表示回数を取得 p = r'この曲の表示回数:(.*)回' impressions = re.search(p, detail).group(1) # 歌詞を取得 song_lyrics = soup.find('div', itemprop='lyrics') song_lyric = song_lyrics.text song_lyric = song_lyric.replace('\n','') song_lyric = song_lyric.replace('この歌詞をマイ歌ネットに登録 >このアーティストをマイ歌ネットに登録 >','') #サーバーに負荷を与えないため1秒待機 time.sleep(1) #取得した歌詞を表に追加 tmp_se = pd.DataFrame([[song_name], [release_date], [impressions], [song_lyric]], index=list_df.columns).T list_df = list_df.append(tmp_se)
#csv保存
list_df.to_csv('list.csv', mode = 'w', encoding='utf-8')
コードの解説
今回取得したのは以下の項目です。
- 曲名
- 発売日
- 表示回数
- 歌詞
以下の部分で、歌詞一覧ページのURLの指定を行っています。
url = 'https://www.uta-net.com/artist/684/0/' + str(page) + '/'
ミスチルの歌詞一覧のページに行くとURLは下記のようになっていました。
https://www.uta-net.com/artist/684/
しかし、2ページ目に行くと以下のようなURLになりました。
https://www.uta-net.com/artist/684/0/2/
そこから1ページ目に戻ると以下のようなURLになりました。
https://www.uta-net.com/artist/684/0/1/
末尾の数字を変えることでページを遷移できます。
page
という変数に1と2を入れることでURLを変えられました。
Googleデベロッパーツールで確認すると、<td>
タグのside td1
というクラスの最初の<a>
タグが曲の歌詞ページのリンクになっているので、links
という変数に各曲の情報をリストとして格納し、for文でlinks
リストから一つずつの楽曲情報をlink
として取り出して、URLを取得しています。
links = soup.find_all('td', class_='side td1')
for link in links: a = base_url + (link.a.get('href'))
曲名は歌詞ページの<h2>
タグに書かれていたので、以下のコードで取得できます。
# 曲名を取得
song_name = soup.find('h2').text
発売日と表示回数は、同じ場所にまとめて格納されていたので、少し工夫が必要でした。
デベロッパーツールで確認すると、<p>
タグのdetail
というクラスのテキストにまとめて記載されていました。
detail
を取得したあと、正規表現で発売日と表示回数を抜き出して取得しています。
# 発売日、表示回数などを取得
detail = soup.find('p', class_="detail").text
# 発売日を取得
match = re.search(r'\d{4}/\d{2}/\d{2}', detail)
release_date = datetime.strptime(match.group(), '%Y/%m/%d').date()
# 表示回数を取得
p = r'この曲の表示回数:(.*)回'
impressions = re.search(p, detail).group(1)
歌詞はdivタグのitemprop='lyrics'
という属性の場所に入っていましたので、下記のように取り出しました。
ただし、改行
や、この歌詞をマイ歌ネットに登録 >このアーティストをマイ歌ネットに登録 >
という内容が末尾に歌詞と一緒に入っていたので、空白で置換しています。
# 歌詞を取得
song_lyrics = soup.find('div', itemprop='lyrics')
song_lyric = song_lyrics.text
song_lyric = song_lyric.replace('\n','')
song_lyric = song_lyric.replace('この歌詞をマイ歌ネットに登録 >このアーティストをマイ歌ネットに登録 >','')
あまりにも高速でプログラムを回すと、歌詞ネットのサーバーに負荷を掛けることになるので、以下のように1秒スリープすることでプログラムを回すスピードを落としています。
#サーバーに負荷を与えないため1秒待機
time.sleep(1)
アルバム名も取得するコード
上記のコードではアルバム名は取得できないので、別ページからアルバム名も含めて歌詞を取得します。
下記のページから、アルバム毎に分けられた状態で歌詞を見ることができます。
コード
コードは以下です。
内容や自分が工夫した点については、コードの後で説明します。
import requests
from bs4 import BeautifulSoup
import pandas as pd
import time
import re
from datetime import datetime
# 歌ネットのURL
base_url = 'https://www.uta-net.com'
#スクレイピングしたデータを入れる表を作成
list_df = pd.DataFrame(columns=['アルバム名', 'アルバム発売日', '曲名', '曲の発売日', '表示回数', '歌詞'])
#アルバム一覧ページ
url = 'https://www.uta-net.com/user/search_index/artist.html?AID=684'
response = requests.get(url)
soup = BeautifulSoup(response.text, 'lxml')
albums = soup.find_all('table', class_='album_table')
for album in albums: album_title = album.select_one('div.album_title a').text # アルバム名 album_release_date = album.select('div.album_title dd')[0].text # アルバム発売日 links = album.select('li a') for link in links: a = base_url + (link.get('href')) #歌詞詳細ページ response = requests.get(a) soup = BeautifulSoup(response.text, 'lxml') # 曲名を取得 song_name = soup.find('h2').text print(song_name) # 発売日、表示回数などを取得 detail = soup.find('p', class_="detail").text # 発売日を取得 match = re.search(r'\d{4}/\d{2}/\d{2}', detail) release_date = datetime.strptime(match.group(), '%Y/%m/%d').date() print(release_date) # 表示回数を取得 p = r'この曲の表示回数:(.*)回' impressions = re.search(p, detail).group(1) print(impressions) song_lyrics = soup.find('div', itemprop='lyrics') song_lyric = song_lyrics.text song_lyric = song_lyric.replace('\n','') song_lyric = song_lyric.replace('この歌詞をマイ歌ネットに登録 >このアーティストをマイ歌ネットに登録 >','') #サーバーに負荷を与えないため1秒待機 time.sleep(1) #取得した歌詞を表に追加 tmp_se = pd.DataFrame([[album_title], [album_release_date], [song_name], [release_date], [impressions], [song_lyric]], index=list_df.columns).T list_df = list_df.append(tmp_se) print(list_df)
print(list_df)
#csv保存
list_df.to_csv('mrchildren-scraiping-album.csv', mode = 'w', encoding='utf-8')
コードの解説
今回取得したのは以下の項目です。アルバム名とアルバムの発売日を追加しています。
- アルバム名
- アルバム発売日
- 曲名
- 発売日
- 表示回数
- 歌詞
アルバム毎に情報を取得するために、まずはアルバム毎のブロックを取得しています。
album_table
というクラスに、アルバム名やアルバム毎の曲名や歌詞ページのリンクが入っていました。
#アルバム一覧ページ
url = 'https://www.uta-net.com/user/search_index/artist.html?AID=684'
response = requests.get(url)
soup = BeautifulSoup(response.text, 'lxml')
albums = soup.find_all('table', class_='album_table')
アルバム名はalbum_title
というクラスの子要素の<a>
タグに記載されています。
アルバム発売日はalbum_title
というクラスの子要素の<dd>
タグに記載されています。
歌詞ページのリンクは、<li>
タグの子要素の<a>
タグにあります。
注意点としては、歌詞が無い曲は<a>
タグが設定されていないので今回は抽出されません。
for album in albums: album_title = album.select_one('div.album_title a').text # アルバム名 album_release_date = album.select('div.album_title dd')[0].text # アルバム発売日 links = album.select('li a') for link in links: a = base_url + (link.get('href'))
あとは、歌詞ページに入って情報を取得していくので、上記のコードと同じ内容で取得できます。
参考にしたサイト
以下のサイトを参考にさせていただきました。
今回は、スクレイピングで歌詞を自動で取得してみました。
約250曲を手打ちやコピペで歌詞をまとめようとするとどれだけ時間がかかるかわかりませんが、スクレイピングを活用すると約5分という時間で情報が取得できました。
次は取得した歌詞を単語ごとに分けて分析する「形態素解析」を行い、歌詞の中で一番使われている単語を調べてみました。
以下にリンクがありますので、興味があれば見てください。
ここまで記事を見ていただき、ありがとうございました!
ミスチルの歌詞をJanomeで解析してWordCloudで可視化してみた