論理の流刑地

地獄の底を、爆笑しながら闊歩する

前後半のゴール数/シュート数/ボール支配率を取得する by rvest

Introduction

rvestでスクレイピングするシリーズ。
今回は、以下記事でふれられている、「グランパスは先行逃げ切り特化型」説を検証するためのデータをこしらえていく。
grapo.net

サッカーって、そもそもリードされてから逆転するのが難しい競技ではありますが、こうも極端だと笑ってしまいます。
ハーフタイムでリードされてたら、勝てないどころか勝点0ってアンタ………そりゃきついぜ。
(上記サイトよりラグ氏)

本当にグランパスが他チームと比較して「先行逃げ切り特化型」なのかは、データにもとづく比較に基づいて判断する必要があるだろう

rvestでのスクレイピングに関する、基本的な解説はコチラ↓
ronri-rukeichi.hatenablog.com

どのページからとってくるか

前後半のscoreだけ取得するのであれば、Jリーグ公式記録ページ(例:湘南vs浦和)が手っ取り早い。
一覧ページもおあつらえ向きにあるので、一覧のリンクを取得するのもアホほど楽なのである。

しかし、シュート数を取得するとなると、話はかわってくる。
フットボールラボ*1の試合結果ページ(例:2019年の札幌vs清水)をみると、
得点の時間帯だけでなく、シュート、さらにはポゼッション率についても15分の粒度で得ることができる。


これをとりにいくのが、のちのちの利用機会を考えてもよいだろう。

データ取得の方法:得点編

場合わけ(得点をとってない場合もあるよねという話)

点数に関して、以下のようなテーブルをスクレイピング対象として、時間帯別の得点を取得してくるわけだが、

f:id:ronri_rukeichi:20200930084007p:plain

サッカーにおいてそこまで珍しくないスコアレスドローの試合(例:札幌vs 名古屋)においては、このテーブル自体がなくなる。
ので、デバッグ用のケースとして、以下のみっつの種類のURLを用意しておくのがよいだろう。

  1. 無得点
  2. いずれかのチームのみが得点
  3. Home/Away両方のチームが得点

上の得点テーブルはcssセレクターでいえば".tblCompare+.lineTbl table"*2によって取得することができる。

そして、上のセレクタで取得できる要素数が1であれば得点の入った試合、0であればスコアレスな試合、といった形で場合わけができる。

データ取得to整形

目標のテーブルが取得できたら、以下のような関数をかけばサクっとお目当てのデータがとれる

#####-------===[内部関数:Score tableから前後半/分/Home or Away、を取得する]===-------#####
#[1], tbl : スコア表のテーブル
scoreTimeTbl <- function(tbl){
  
  min_HorA <- function(chr){
    c_n <- nchar(chr)
    half <- substr( chr ,1, 2 ) #前後半のいずれかを判別するための文字列
    min_chr <- substr( chr , 3 , c_n - 1)
    ret_c <- c( half, min_chr)
    names( ret_c) <- c("Half","Minutes")
    return(ret_c)
  }# function
  
  ##--- データフレーム形式に変換する---##
  score_df <- as.data.frame(do.call( what = rbind , args=lapply(tbl[,3], min_HorA )))
  score_df$Half <- factor( score_df$Half , levels=c("前半","後半"))
  score_df$Minutes <-  as.numeric( as.character(score_df$Minutes))
  
  ##--- Home / Awayの変数をつくる---##
  home_idx <-which( tbl[,1] != "")
  away_idx <-which( tbl[,5] != "")
  h_a <-  factor( rep( NA, nrow( tbl)) ,levels=c("Home", "Away"))
  h_a[home_idx] <- "Home"
  h_a[away_idx] <- "Away"
  score_df$HorA <- h_a
  
  return( score_df)
} #function


#####-------===[内部関数:scoreTimeTblの戻り値を前後半のスコアに変換]===-------#####
getHalfScore <- function(score_df){
  df_1st <- dplyr::filter( score_df , Half == "前半")
  df_2nd <- dplyr::filter( score_df , Half == "後半")
  
  #結局欲しいのは、前後半のゴール数/被ゴール数、という形式になってくるといえる。
  goal_h_1st <- length(which( df_1st$HorA == "Home"))
  goal_a_1st <- length(which( df_1st$HorA == "Away"))
  goal_h_2nd <- length(which( df_2nd$HorA == "Home"))
  goal_a_2nd <- length(which( df_2nd$HorA == "Away"))
  
  #どっちが先制したかを判定し、変数として追加する
  goal_first_h <-  ifelse( score_df[1,"HorA" ] == "Home", 1 , 0 )
  goal_first_a <-  ifelse( score_df[1,"HorA" ] == "Away", 1 , 0 )
  
  # 戻り値としてdata frame形式に変換する
  rdf <-  data.frame( Goal_For_1st = c(goal_h_1st , goal_a_1st ), Goal_Against_1st = c( goal_a_1st , goal_h_1st), Goal_For_2nd = c( goal_h_2nd , goal_a_2nd), Goal_Against_2nd = c( goal_a_2nd , goal_h_2nd), Goal_First = c( goal_first_h, goal_first_a))
  rownames( rdf) <- c("Home","Away")
  return( rdf)
} #function

使い方は以下の通り。

scr_page <-    read_html( "https://www.football-lab.jp/sapp/report/?year=2019&month=03&date=09")
st_df <-  getScoreTbl(scr_page)

st_df
# Goal_For_1st Goal_Against_1st Goal_For_2nd Goal_Against_2nd Goal_First
# Home            2                1            3                1          1
# Away            1                2            1                3          0


また、ここから、先制したかorされたかという変数も作成することができる。

時間帯別のポゼッション/シュート数を取得する

時間帯別のシュート/ポゼッションもfootball labのページでは15分刻みで記載されている。

f:id:ronri_rukeichi:20201001091813p:plain

これらの情報も、以下の方法で取得できる。

Possessionを取得する

cssセレクタ的には、".homePoss em"や".awayPoss em"で、ポゼッションの情報にアクセスする。
以下のような関数を用意する。

getPossession <- function( gpage ){
  
  ##--- Home --- ##
  hm_ps_l <-  html_nodes(gpage, css= ".homePoss em") #タグで取得
  hm_ps_v <-  sapply( hm_ps_l  , function( ps_node){
    pos_txt <- html_text( ps_node) #XX%
    pos_n <- nchar( pos_txt)
    return( as.numeric( substr( pos_txt , 1, pos_n- 1)))
  }) #sapply
  
  
  ##--- Away --- ##
  aw_ps_l <-  html_nodes(gpage, css= ".awayPoss em") #タグで取得
  aw_ps_v <-  sapply( aw_ps_l  , function( ps_node){
  pos_txt <- html_text( ps_node) #XX%
  pos_n <- nchar( pos_txt)
  return( as.numeric( substr( pos_txt , 1, pos_n- 1)))
  }) #sapply
  
  return( list(Home = hm_ps_v ,  Away = aw_ps_v))
} #Function

つかった結果はこちら、

> getPossession( scr_page)
# $Home
# [1] 42.4 42.8 50.5 50.0 45.8 65.6
# $Away
# [1] 57.6 57.2 49.5 50.0 54.2 34.4

ポゼッションも時間帯別に取得できている。

シュート数

シュート数も上と同じ要領で時間帯別に取得できる。
cssセレクタに"td.homeShot"/"td.awayShot"を指定し、imgタグあるいはspanタグを幾つ含んでいるのか..
という感じでみていけばいい。

Conclusion

これで、時間帯別の各データがそろった。
次の記事で実際の分析を行いたいが、その前に差分を取得するプログラムを実装しなくては。


Astral Chain OP(オープニングテーマ)Savior Full Version

Enjoy!!

*1:前回記事で書いた通り、個人ブログでのデータ利用に関しては許容されている

*2:tblCompareクラスに隣接したlineTblクラスの子孫階層にあるtableタグ、を取りに行っている