Produce 101 Japanとは
PRODUCE 101 JAPAN OFFICIAL SITE
韓国から輸入されたオーディション番組の日本版で、歌手デビューしてほしい練習生への投票結果が週ごとに発表される番組です。
週が進むと、60位、35位と足切りが進んで脱落者が出てしまいます。
今回は公式サイトからランキング結果をスクレイピングによって抽出し、
最新順位(2019/11/29時点で9週目)までに生き残っている練習生の順位変動を可視化してみました。
出来上がりの完成図
主な流れ
- スクレイピング
- データ整形
- ランキングを可視化
1. スクレイピングによりランキングを取得する
週ごとの順位を個別に収集できるように関数化。
BeautifulSoupを用いてHTMLの要素を取得しテキスト化。
「順位,名前,週」の形で取得します(一部のみ表示しています)。
def getWeeklyRank(week):
import requests
from bs4 import BeautifulSoup
import re
# 数字でフォーマットしてランキングページのURLを取得
url = 'https://produce101.jp/rank/?week={}'
html = requests.get(url.format(week))
# URLをBeautifulSoupで扱う
soup = BeautifulSoup(html.text, 'lxml')
# 特定のクラスのspan要素とdiv要素を取得する
span_rank = soup.find_all("span", class_="icon-rank")
div_name = soup.find_all("div", class_="name")
# rankとnameが入っているタグのテキスト成分をリストに抽出する
rank = []
for i in range(len(span_rank)):
rank.append(int(span_rank[i].text))
name = []
for i in range(len(div_name)):
name.append(div_name[i].text)
# weeklyRankingをcsvに保存
# 1週目のみ新規作成で次週からは追加モードで書き込む
if week == 1:
f = open('./weeklyRank.txt', 'w')
for i in range(len(rank)):
f.write(str(rank[i])+','+str(name[i])+','+str(week)+'\n')
f.close()
elif week > 1:
f = open('./weeklyRank.txt', 'a')
for i in range(len(rank)):
f.write(str(rank[i])+','+str(name[i])+','+str(week)+'\n')
f.close()
週ごとに関数を実行し、ランキングを取得。
(自動的にすべてを取得した方がかっこいいですが、今回は地道に取得していきます。)
getWeeklyRank(1)
getWeeklyRank(2)
getWeeklyRank(3)
# 4週目は順位発表なし
getWeeklyRank(5)
getWeeklyRank(6)
# 7週目は順位発表なし
getWeeklyRank(8)
getWeeklyRank(9)
2. データ整形によってグラフ化しやすい形式にする
「*辞退」などの名前以外の要素を消し、列見出しを週と名前にしてランキングを入力していく形に整形します。
途中で脱落した練習生の順位は「x」で置き換えておきます。
# 辞退の表記を消す
f = open('weeklyRank.txt', 'r')
data_lines = f.read()
data_lines = data_lines.replace(' ※辞退', '')
f.close()
f = open('weeklyRank.txt', 'w')
f.write(data_lines)
f.close()
HTMLから取得したランキングデータを整形します。
5週目で60位までの足切り、8週目で35位までの足切りがあり、人数が変化するので個別に対応していきます。
def getWeeklyRank_format(data_path):
import pandas as pd
df_rank = pd.read_csv(data_path,header=None, names=('rank', 'name', 'week'))
df = df_rank[['name','week','rank']]
df_week1 = df_rank[df_rank['week'] == 1]
df_week5 = df_rank[df_rank['week'] == 5]
df_week8 = df_rank[df_rank['week'] == 8]
f = open('./weeklyRank_format.txt', 'w')
f.write('week')
# week1のメンバーを取得
name_week1 = []
for e in range(len(df_week1)):
dfe = df[(df['week'] == 1) & (df['rank'] == e+1)]
nameArray = dfe['name'].values[0]
f.write(str(','+nameArray))
name_week1.append(str(nameArray))
# week5のメンバーを取得
name_week5 = []
for e in range(len(df_week5)):
dfe = df[(df['week'] == 5) & (df['rank'] == e+1)]
nameArray = dfe['name'].values[0]
name_week5.append(str(nameArray))
f.write('\n')
# week8のメンバーを取得
name_week8 = []
for e in range(len(df_week8)):
dfe = df[(df['week'] == 8) & (df['rank'] == e+1)]
nameArray = dfe['name'].values[0]
name_week8.append(str(nameArray))
f.write('\n')
# 1週目の練習生順位を列見出しとし、それ以降の順位を変数として記入していく
for i in range(1,10):
if i==1 or i==2 or i==3:
# 0列目にweekを書く
f.write(str(i))
# 次に1週目の並びで練習生の順位を取得する
for j in range(0, len(name_week1)):
dfi = df[(df['week'] == i) & (df['name'] == name_week1[j])]
f.write(str(','+str(dfi['rank'].values[0])))
elif i==4:
continue
elif i==5 or i==6:
# 0列目にweekを書く
f.write(str(i))
# 次に1週目の並びで練習生の順位を取得する
for j in range(0, len(name_week1)):
if name_week1[j] in name_week5:
dfk = df[(df['week'] == i) & (df['name'] == name_week1[j])]
f.write(str(','+str(dfk['rank'].values[0])))
elif name_week1[j] not in name_week5:
f.write(',x')
elif i==7:
continue
elif i==8 or i==9:
# 0列目にweekを書く
f.write(str(i))
# 次に1週目の並びで練習生の順位を取得する
for j in range(0, len(name_week1)):
if name_week1[j] in name_week8:
dfk = df[(df['week'] == i) & (df['name'] == name_week1[j])]
f.write(str(','+str(dfk['rank'].values[0])))
elif name_week1[j] not in name_week8:
f.write(',x')
f.write('\n')
f.close()
関数を実行する。
getWeeklyRank_format('./weeklyRank.txt')
うまくいったか確認してみましょう。
import pandas as pd
df_rank = pd.read_csv('./weeklyRank_format.txt',header=0)
df_rank
3. ランキングを可視化する
今回は見栄えを意識して、ダウンロードしたフォント: JKゴシックLを使って日本語表記をしていきます。
# 1週目から9週目までの練習生ランキング
# フォントをカスタマイズする
import matplotlib.pyplot as plt
%matplotlib inline
import numpy as np
# ttfファイルを直接指定してフォントを適用する
import matplotlib.font_manager
fp = matplotlib.font_manager.FontProperties(fname='/Users/[USER NAME]/.matplotlib/fonts/ttf/JKG-L_3.ttf')
# フィールドを設定
fig, axs = plt.subplots(figsize=(10,25))
x = df_rank['week']
axs.set_xlim(0.94,9.1)
axs.set_xticks([1, 2, 3, 4, 5, 6, 7, 8, 9])
axs.set_ylim(99, 0.6)
axs2 = axs.twinx()
labels = list(df_rank.columns[1:])[0:]
axs.set_yticks(list(np.arange(1,99)))
axs.set_yticklabels(labels, fontproperties=fp, color='darkslateblue')
axs.set_xticklabels(['1週目', '2週目', '3週目','4週目', '5週目', '6週目', '7週目', '8週目', '9週目'], rotation=0, fontsize=14, fontproperties=fp, color='darkslateblue')
axs.spines['top'].set_visible(False)
axs.spines['bottom'].set_visible(False)
axs.spines['right'].set_visible(False)
axs.spines['left'].set_visible(False)
axs.tick_params(left=False)
labels2 = list((np.arange(0,99)))
axs2.set_yticks(list(np.arange(1,99)))
axs2.set_yticklabels(labels2[99:0:-1], fontproperties=fp, color='darkslateblue')
axs2.set_ylim(0,98)
axs2.spines['top'].set_visible(False)
axs2.spines['bottom'].set_visible(False)
axs2.spines['right'].set_visible(False)
axs2.spines['left'].set_visible(False)
axs2.tick_params(right=False)
# 折れ線の色を虹色に
cmap = plt.get_cmap('rainbow')
for i in range(1, 99,1):
y = df_rank[df_rank.columns[i]]
if 'x' in list(y):
continue
else:
axs.plot(x,y,color=cmap(1-i/100),marker='o',markersize=8,linewidth = 3, alpha=0.3)
これでグラフが完成するはずです。
条件を設定すれば、順位を大幅にアップさせた練習生などが可視化できます。