J1の各試合のフォーメーションを自動取得する
ただの数字、されど数字。
それがフォーメーション。
Introduction
サッカーを戦術的に語るうえでの大きな要素としてフォーメーションがある。
Football LABではシーズン累計のフォーメーション別戦績*1を、クラブ別のSummaryページにおいて示してくれている(例:2020年の名古屋)。
これはこれでとても有益かつ示唆的な情報ではあるのだが、より細かい粒度の情報が欲しいという欲望もある。
具体的には、M月D日のAAA vs BBBで両クラブがどういうフォメだったか、みたいなのがほしい。
で、一応各試合の詳細ページ(例:湘南 vs 鳥栖)にはフォメ図が乗っているのである。
しかしこれらは画像なので、当然だがテキストデータとしてフォメ図を取得することはできない。
でも、それ以外の情報は自動取得しているのに、両軍のフォーメーションだけ目視で入力するのも、イケてない。
で、なんとかして取れないかなーと試行錯誤した記録である。
Implementation
scriptタグの内部を解析する
上でも触れた通り、画像からはフォーメーションの情報を文字・数字情報として取得することはできない。
....のだが、(イチイチ人が作っているわけではなく)javascriptでこういう画像って自動生成してんじゃないのか、と思ってソースコードを精査すると、以下のような非常に怪しい部分がみつかった
mOctagon('homeFormation', 269, sz/10*3,60,13,'石原直樹',0,'','out'); mOctagon('homeFormation', 269, sz/10*7,60,17,'大橋祐紀',0,'',''); mOctagon('homeFormation', 269, sz/10*3,210,10,'山田直輝',0,'','out'); mOctagon('homeFormation', 269, sz/10*7,210,30,'柴田壮介',0,'','out'); mOctagon('homeFormation', 269, sz/10*1,360,42,'高橋諒',0,'','out'); mOctagon('homeFormation', 269, sz/10*5,360,25,'中村駿',0,'yellow1',''); mOctagon('homeFormation', 269, sz/10*9,360,6,'岡本拓也',0,'',''); mOctagon('homeFormation', 269, sz/10*1,510,32,'田中聡',0,'','out'); mOctagon('homeFormation', 269, sz/10*5,510,3,'石原広教',0,'',''); mOctagon('homeFormation', 269, sz/10*9,510,22,'大岩一貴',0,'',''); mOctagon('homeFormation', 269, sz/10*5,660,1,'谷晃生',0,'',''); mOctagon('awayFormation', 122, sz/10*3,60,9,'山下敬大',0,'yellow1','out'); mOctagon('awayFormation', 122, sz/10*7,60,8,'林大地',1,'','out'); .....
どうもこのmOctagon()という関数で、フォメ図上の各選手の位置を指し示しているっぽい。
この第四引数がおそらくY軸上の位置を指し示していると思われるので、それをとってきて、フォーメーションを類推すればいい。
mOctagon...という関数がつかわれている各行から、各選手のフォメ図上のY座標をとってくるには、以下のような簡単な関数をかけばいい。
get_y_coord <- function( oct_c){ spl_info <- str_split( oct_c,",") #読点で分割 y_v <- as.numeric(spl_info[[1]][4]) #y座標を数値として取得 return(y_v) # }
フォーメーションを取得する
11人のY座標が同じ人たちは同列と考えていいので、GKを抜いた10人のうちY座標を同じ人たちをカウントして後列からつなげていくと、"4-2-3-1"とか"3-4-2-1"みたいなよくみるフォーメーション(を表わす数値羅列)になる。
この処理は、以下のような関数で実装できる
#-- 内部関数:数値の羅列からフォメを計算 -- # #引数⇒ GKを抜いた10人のy座標のベクトル num_to_Form <- function( n_form){ coord_df <- data.frame(y = n_form) cnt_df <- ( coord_df %>>% dplyr::group_by( y ) %>>% dplyr::summarise( N = n()) %>>% dplyr::arrange( y) %>>% as.data.frame()) form_chr <- paste0( rev(cnt_df$N) , collapse="-") #Formation return( form_chr) } #num_to_Form num_to_Form( c( 3, 3, 5 ,5 , 13, 13, 13, 15,15,15)) #[1] "3-3-2-2"
ただ、いわゆる4-4-2は"4-2-2-2"になってしまうので、そこは必要なら適宜判定したうえで"4-4-2"にするコードを追加したほうがよいかもしれない。
実装
以上のような内部処理をしつつ、フォーメーション図を取得してくるコードは以下のようなものである
library( rvest) library(pipeR) library(dplyr) library(stringr) getFormation <- function( game_page){ #--目的のScriptのタイプ--# scr_tag <- (html_nodes( game_page , css = "div + script"))[[1]] scr_txt <- html_text( scr_tag ) #-- 改行記号で分割したうえで、画像生成しているcode部分だけとってくる --# code_lines <- ( str_split( scr_txt, "\n"))[[1]] is_oct <- str_detect( code_lines, "mOctagon") #--mOctagon()関数を使っている行だけ抽出。 oct_lines <- code_lines[ is_oct] #コレを次の関数に算入する。 frm_info <- code_to_Form( oct_lines) #下で定義したもの return(frm_info) } ##func ##-- 内部関数:mOctagon()を含む22人分の文字列からフォメを計算-- ## code_to_Form <- function( oct_lines ){ #--Home vs AwayそれぞれのLineをとる--# h_lines <- oct_lines[1:10] a_lines <- oct_lines[12:21] #-- 内部関数:y座標取得 --# get_y_coord <- function( oct_c){ spl_info <- str_split( oct_c,",") #読点で分割 y_v <- as.numeric(spl_info[[1]][4]) #y座標を数値として取得 return(y_v) #数値 } #function #-- 内部関数:数値の羅列からフォメを計算 -- # #引数⇒ GKを抜いた10人のy座標のベクトル num_to_Form <- function( n_form){ coord_df <- data.frame(y = n_form) cnt_df <- ( coord_df %>>% dplyr::group_by( y ) %>>% dplyr::summarise( N = n()) %>>% dplyr::arrange( y) %>>% as.data.frame()) form_chr <- paste0( rev(cnt_df$N) , collapse="-") #Formation return( form_chr) } #num_to_Form #---上で定義した関数群をつかい、各クラブ(Home/Away)のフォメを取得する---# hForm <- num_to_Form(sapply( h_lines , get_y_coord)) aForm <- num_to_Form(sapply( a_lines , get_y_coord)) return( list( Home = hForm ,Away = aForm )) #listにして戻り値に。 } #func , code_to_Form
実行する
ためしに、先日行われた札幌vs横浜FC(URL)のページからフォメ図を問題なく取得できるか、やってみよう
library( rvest) gpage <- read_html("https://www.football-lab.jp/y-fc/report/?year=2021&month=02&date=27") form1 <- getFormation( gpage) form1 # $Home # [1] "3-4-2-1" # # $Away # [1] "4-2-2-2"
問題なく取得できている。
(上でも触れたが"4-2-2-2"はお好みで"4-4-2"に変換してもよいかも)
Enjoy!!
*1:あくまでも試合開始時のフォーメーションで、試合中の変更等は把捉していない