今回のガジェットはエンゼルスの大谷さんの活躍が毎日楽しみでしょうがない僕のためのガジェットです。今シーズンはぐっと我慢の試合も多い感じなのですが、データを見ると実は安定していたり、やっぱ記録を見るとすごいなと思います。実際このプログラムを作ったのはシーズン開始してすぐくらいだったけど、毎日毎日、自動で動いてくれておりまして、しっかりデータを取ってきてくれております。
例えばこんな状況で
この記事を書いている今日は、今シーズン3度目の1試合2ホーマー!いや!最高です!!そんな大谷さんの情報はヤフーさんが毎日更新してくれているのですが、直近6試合とかの表示なので、日々の変化という点で焦点を当てるとやはり毎日毎日、せっせとその情報をとってきておかないといけないなということで、毎日定期的に1度だけスクレイピングでデータをいただいてきています。そしてそれをまとめて、可視化しています。今シーズンの大谷さん追い込まれちゃうとちょっと厳しい、とか、調子悪いのかなと思いつつも実際データはそこまで悪くないとか色々見えます。この前スタメンを外れて九回に代打で出てきた時のMVPコール!痺れました!!!いずれは骨格なんかを画像で調べてデータを出していきたいですね!!
www.youtube.com
このガジェットでできることはこちら
・毎日定期的にyahooスポーツさんの大谷さんのページの情報を取得しにいきます。
・取得した情報をそれぞれ処理してcsvにまとめていきます。
・まとめたデータはstreamlitを使用して可視化することができます。
評価(自己)
役立ち度 ★★★(平均)
効率化 ★★★(平均)
ミス防止 ★★★(平均)
楽しさ ★★★★★(最高)
操作 ★★★★(良し)
※ファンとしては見ていて最高なのですがかなり自己満足です。。csvファイルいっぱいなので若干効率化にはなっていないかもですが、一旦セットすれば後はもう!
実行環境
使用環境 streamlit
使用言語 python
使用ライブラリ numpy、pandas、BeautifulSoup、altair
コード
※こっちがデータを取得してまとめるコードです。(automaterで毎日動かしてます)
import numpy as np
import pandas as pd
import requests
from bs4 import BeautifulSoup
def main():
url = 'https://baseball.yahoo.co.jp/mlb/player/2100825/top'
res = requests.get(url)
soup = BeautifulSoup(res.text, 'html.parser')
base_names = ['pitch_df', 'hit_df', 'scorer_df']
names = ['recent_p_result', 'byteam_p_result', 'monthly_p_result', 'byfield_p_result', 'recent_h_result', 'byteam_h_result', 'monthly_h_result', 'byfield_h_result', 'bycount_h_result', 'bybase_h_result']
years_title_names = ['pitch_title', 'hit_title']
idxs = [4, 5]
nums = [30, 26]
results = soup.find_all('section', attrs={'class': 'bb-modCommon01'})[:3]
for base_name, result in zip(base_names, results):
elems_th = result.find_all('th')
keys = [elem_th.text for elem_th in elems_th]
elems_td = result.find_all('td')
values = [elem_td.text for elem_td in elems_td]
_df = {}
for key, value in zip(keys, values):
_df[key] = value
exec(f'{base_name} = pd.DataFrame([_df])')
conditional_results = soup.find_all('section', attrs={'class': 'bb-modCommon01'})[3]
tables = conditional_results.find_all('table')
extract_data = tables[7]
del tables[7]
for name, table in zip(names, tables):
elems_th = table.find_all('th')
cols = [elem_th.text for elem_th in elems_th]
elems_td = table.find_all('td')
vals = [elem_td.text for elem_td in elems_td]
new_vals = []
for val in vals:
new_vals.append(val.replace('\n', ''))
chuncked_vals = np.array_split(new_vals, len(new_vals)/len(cols))
exec(f'{name} = pd.DataFrame(chuncked_vals, columns=cols)')
elems_th = extract_data.find_all('th')
cols = [elem_th.text for elem_th in elems_th]
elems_td = extract_data.find_all('td')
vals = [elem_td.text for elem_td in elems_td]
vals.insert(12, vals[0])
vals.insert(36, vals[24])
new_vals = []
for val in vals:
new_vals.append(val.replace('\n', ''))
chuncked_vals = np.array_split(new_vals, len(new_vals)/len(cols))
bydominant_arms_result = pd.DataFrame(chuncked_vals, columns=cols)
for year_title_name, idx, num in zip(years_title_names, idxs, nums):
years_title = soup.find_all('section', attrs={'class': 'bb-modCommon01'})[idx]
elems_th = years_title.find_all('th')[:num]
cols = [elem_th.text for elem_th in elems_th]
elems_td = years_title.find_all('td')
vals = [elem_td.text for elem_td in elems_td]
new_vals = [val.replace('\n', '') for val in vals]
chuncked_vals = np.array_split(new_vals, len(new_vals)/len(cols))
totals = years_title.find_all('th')[num:]
total_vals = [total.text for total in totals]
new_total_vals = [total_val.replace('\n', '') for total_val in total_vals]
new_total_vals.insert(1, '-')
total_title = np.array(new_total_vals)
chuncked_vals.append(total_title)
exec(f'{year_title_name} = pd.DataFrame(chuncked_vals, columns=cols)')
df_lists = base_names + names + years_title_names
df_lists.append('bydominant_arms_result')
for df_list in df_lists:
exec(f'{df_list}.to_csv(df_list+".csv", index=False)')
df_dailyh = pd.read_csv('recent_h_result.csv')
stacked_h_result = pd.read_csv('h_result.csv')
df_hit = pd.read_csv('hit_df.csv')
df_today = pd.DataFrame(df_dailyh.iloc[0, :])
df_today = df_today.T
df_today['打率'] = df_hit['打率']
stacked_h_result = pd.concat([stacked_h_result, df_today])
stacked_h_result = stacked_h_result.drop_duplicates('日付')
stacked_h_result.reset_index(drop=True)
stacked_h_result.to_csv('h_result.csv', index=False)
df_dailyp = pd.read_csv('recent_p_result.csv')
stacked_p_result = pd.read_csv('p_result.csv')
df_todayp = pd.DataFrame(df_dailyp.iloc[0, :])
stacked_p_result = pd.concat([stacked_p_result, df_todayp.T])
stacked_p_result = stacked_p_result.drop_duplicates('日付')
stacked_p_result.reset_index(drop=True)
stacked_p_result.to_csv('p_result.csv', index=False)
if __name__ == '__main__':
main()
こっちがstreamlitで可視化する方のコードです。
import pandas as pd
import streamlit as st
from glob import glob
import altair as alt
st.title("Ohtani's season")
st.write('### ■今シーズンの打者成績')
df_hit = pd.read_csv('hit_df.csv')
result_hit = st.multiselect(
'項目を選択してください。',
list(df_hit.columns),
['打率', '安打', '本塁打', '盗塁', '三振', 'OPS']
)
if not result_hit:
st.error('少なくとも一つの項目は選んでください')
else:
data = df_hit.loc[:,result_hit]
data.columns = ' , '.join(data.columns).split(',')
st.dataframe(data)
st.write('##### 月別')
m_hit = pd.read_csv('monthly_h_result.csv')
m_result_hit = st.multiselect(
'項目を選択してください。',
list(m_hit.columns),
['月', '打率', '安打', '本塁打', '盗塁', '三振', 'OPS']
)
if not m_result_hit:
st.error('少なくとも一つの項目は選んでください')
else:
data = m_hit.loc[:,m_result_hit]
data.columns = ' , '.join(data.columns).split(',')
st.dataframe(data)
st.write('#### 打者成績積み上げ')
hit_stacked = pd.read_csv('h_result.csv')
hit_stacked.columns = ' 日付 ', ' 対戦チーム ', ' 打数', ' 安打', ' 本塁打', ' 打点', ' 得点', ' 三振', ' 四球', ' 死球', ' 打席結果 ', ' 打率 '
st.dataframe(hit_stacked)
st.write('#### 各打撃結果推移を見る')
ymin1 = 0
ymax1 = 1
ymin2 = 0
ymax2 = 7
hit_graph = pd.read_csv('h_result.csv')
kind = st.selectbox('項目を選んでください', hit_graph.columns[3:], index=0)
base = alt.Chart(hit_graph).encode(
alt.X('日付:N', axis=alt.Axis(title=None))
).properties(
width=600, height=400, title='成績推移')
line1 = base.mark_line(opacity=0.3, color='#57A44C').encode(
alt.Y('打率:Q',
axis=alt.Axis(title='打率', titleColor='#57A44C'),
scale=alt.Scale(domain=[ymin1, ymax1])
)
)
line2 = base.mark_line(stroke='#5276A7', interpolate='monotone').encode(
alt.Y(f'{kind}:Q',
axis=alt.Axis(title='種類', titleColor='#5276A7'),
scale=alt.Scale(domain=[ymin2, ymax2])
)
)
chart = alt.layer(line1, line2).resolve_scale(
y = 'independent'
)
st.altair_chart(chart, use_container_width=True)
st.write('#### カウント別打撃成績')
bycount_result = pd.read_csv('bycount_h_result.csv')
chart3 = alt.Chart(bycount_result).mark_bar().encode(
x=alt.X('カウント:N', title='カウント(ボール-ストライク)'),
y=alt.Y('打率:Q', title='打率')
).properties(
width=400, height=300, title='地域別平均馬力')
st.altair_chart(chart3, use_container_width=True)
st.write('### ■今シーズンの投手成績')
st.write('##### シーズン通算')
df_pitch = pd.read_csv('pitch_df.csv')
result_pitch = st.multiselect(
'項目を選択してください。',
list(df_pitch.columns),
['防御率', '登板', '勝利', '敗戦', '奪三振']
)
if not result_pitch:
st.error('少なくとも一つの項目は選んでください')
else:
data = df_pitch.loc[:,result_pitch]
data.columns = ' , '.join(data.columns).split(',')
st.dataframe(data)
st.write('##### 月別')
m_pitch = pd.read_csv('monthly_p_result.csv')
m_result_pitch = st.multiselect(
'項目を選択してください。',
list(m_pitch.columns),
['月', '防御率', '登板', '勝利', '敗戦', '奪三振']
)
if not m_result_pitch:
st.error('少なくとも一つの項目は選んでください')
else:
data = m_pitch.loc[:,m_result_pitch]
data.columns = ' , '.join(data.columns).split(',')
st.dataframe(data)
st.write('#### 投手成績積み上げ')
pitch_stacked = pd.read_csv('p_result.csv')
pitch_stacked.columns = ' 日付 ', ' 対戦チーム ', ' 登板', ' 結果', ' 投球回', ' 投球数', ' 打者', ' 被安打', ' 被本塁打', ' 奪三振',' 与四球', ' 与死球', ' 暴投', ' ボーク', ' 失点', ' 自責点'
st.dataframe(pitch_stacked)
st.write('#### 各投球結果推移を見る')
p_ymin1 = 0
p_ymax1 = 120
p_ymin2 = 0
p_ymax2 = 20
pitch_graph = pd.read_csv('p_result.csv')
p_kind = st.selectbox('項目を選んでください', pitch_graph.columns[6:], index=3)
base = alt.Chart(pitch_graph).encode(
alt.X('日付:N', axis=alt.Axis(title=None))
).properties(
width=600, height=400, title='成績推移')
line1 = base.mark_line(opacity=0.3, color='#57A44C').encode(
alt.Y('投球数:Q',
axis=alt.Axis(title='打率', titleColor='#57A44C'),
scale=alt.Scale(domain=[p_ymin1, p_ymax1])
)
)
line2 = base.mark_line(stroke='#5276A7', interpolate='monotone').encode(
alt.Y(f'{p_kind}:Q',
axis=alt.Axis(title='種類', titleColor='#5276A7'),
scale=alt.Scale(domain=[p_ymin2, p_ymax2])
)
)
chart2 = alt.layer(line1, line2).resolve_scale(
y = 'independent'
)
st.altair_chart(chart2, use_container_width=True)
以上です! 頑張れ大谷さん!!今年もMVP!!