スクレイピングによるProduce 101 Japan練習生順位の可視化

# Produce 101 Japanとは
[PRODUCE 101 JAPAN OFFICIAL SITE](https://produce101.jp)
韓国から輸入されたオーディション番組の日本版で、歌手デビューしてほしい練習生への投票結果が週ごとに発表される番組です。
週が進むと、60位、35位と足切りが進んで脱落者が出てしまいます。

今回は公式サイトからランキング結果をスクレイピングによって抽出し、
最新順位（2019/11/29時点で9週目）までに生き残っている練習生の順位変動を可視化してみました。

### 出来上がりの完成図
練習生の名前は一部伏せております。
![Unknown.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225066/ab35a870-1730-1838-583b-5ab845c67e23.png)


# 主な流れ
1. スクレイピング
2. データ整形
3.  ランキングを可視化

### 1. スクレイピングによりランキングを取得する
週ごとの順位を個別に収集できるように関数化。
BeautifulSoupを用いてHTMLの要素を取得しテキスト化。
「順位,名前,週」の形で取得します（一部のみ表示しています）。
<img width="196" alt="aaa.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225066/cd0fba2b-6a57-80c1-a239-b2fc42989c71.png">



```python

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に保存
    # １週目のみ新規作成で次週からは追加モードで書き込む
    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()
```
週ごとに関数を実行し、ランキングを取得。
（自動的にすべてを取得した方がかっこいいですが、今回は地道に取得していきます。）

```python
getWeeklyRank(1)
getWeeklyRank(2)
getWeeklyRank(3)
# 4週目は順位発表なし
getWeeklyRank(5)
getWeeklyRank(6)
# 7週目は順位発表なし
getWeeklyRank(8)
getWeeklyRank(9)
```

### 2. データ整形によってグラフ化しやすい形式にする
「*辞退」などの名前以外の要素を消し、列見出しを週と名前にしてランキングを入力していく形に整形します。
途中で脱落した練習生の順位は「x」で置き換えておきます。
<img width="986" alt="beforeFormat.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225066/3aeb66cf-4c14-2c5d-eedf-797fcbe2f0b0.png">



```python
# 辞退の表記を消す

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位までの足切りがあり、人数が変化するので個別に対応していきます。

```python
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()
```
関数を実行する。

```python
getWeeklyRank_format('./weeklyRank.txt')
```

うまくいったか確認してみましょう。

```python
import pandas as pd
df_rank = pd.read_csv('./weeklyRank_format.txt',header=0)
df_rank
```
<img width="930" alt="df.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225066/f06cf7fd-3091-f207-ffa8-6025fb9bf323.png">


### 3. ランキングを可視化する
今回は見栄えを意識して、[ダウンロードしたフォント: JKゴシックL](http://font.cutegirl.jp)を使って日本語表記をしていきます。

```python
# １週目から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(['１週目', '２週目', '３週目','４週目', '５週目', '６週目', '７週目', '８週目', '９週目'], 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)
```

これでグラフが完成するはずです。
条件を設定すれば、順位を大幅にアップさせた練習生などが可視化できます。