【競馬】【やり直し】rvestでyahoo競馬にある過去のレース結果をスクレイピングしてみた。

みなさんおはこんばんにちは。 前回の投稿で、yahoo競馬にある過去のレース結果をスクレイピングしてみました。 osashimix.hatenablog.com しかし、取得したデータ数が少ないこともあり、何かおかしいなと思っていました。詳しく調べてみると、どうやら前回のコードでは過去のレース結果の全てを引っ張ることは出来ていないようです。そこで、今回はそのやり直しを行いたいと思います。基本的なコードは前回と変わりませんが、read_htmlとRSQLiteを使用している部分が変更されています。まず、パッケージの呼び出しです。

# rvestによる競馬データのwebスクレイピング
#install.packages("rvest")
#if (!require("pacman")) install.packages("pacman")
#install.packages("beepr")
#install.packages("RSQLite")
pacman::p_load(qdapRegex)
library(rvest)
library(stringr)
library(dplyr)
library(beepr)
library(RSQLite)

SQLiteへの接続を行います。horse_data.dbというデータベースファイルをあらかじめ作成し、そこにデータを格納していきます。

# warnning禁止
options(warn=-1)

# SQLiteへの接続
con = dbConnect(SQLite(), "horse_data.db", synchronous="off")

ここからスクレイピングを行っていきます。そもそも前回はyahoo競馬の以下のページよりレース結果のurlを取得してました。 f:id:osashimix:20180630230705p:plain これだと1994年1月5日(水)のレースは①日刊スポーツ賞金杯と②スポーツニッポン金杯しかないことになっていますが、もちろんそんなことはありません。どうやらその日の注目レースのみが掲載されているようです。では、どこにその日のレース一覧があるのかということですが、上の画像でいうところの1回中山1日や1回阪神1日というリンクに飛ぶと以下のようなページにたどり着きます。

f:id:osashimix:20180630231459p:plain

ここに競馬場ごとの全12レースの結果のリンクが掲載されています。というわけで、いったんこのページまで飛んで、ここから全レースのリンクを取得し、レース結果を一つずつ取得していきたいと思います。

for(year in 1994:2018){
  
  # yahoo競馬のレース結果一覧ページの取得
  for (k in 1:12){
  # tryCatchでエラー処理
    tryCatch(
      {
        keiba.yahoo <- read_html(str_c("https://keiba.yahoo.co.jp/schedule/list/", year,"/?month=",k))
        Sys.sleep(2)
        race_lists <- keiba.yahoo %>%
          html_nodes("a") %>% 
          html_attr("href") # 全urlを取得
        
        # 競馬場ごとの各日のレースリストを取得
        race_lists <- race_lists[str_detect(race_lists, pattern="race/list/\\d+/")==1] # 「result」が含まれるurlを抽出
      }
      , error = function(e){signal <- 1}
    )
    
    for (j in 1:length(race_lists)){
      
      tryCatch(
        {
          race_list <- read_html(str_c("https://keiba.yahoo.co.jp",race_lists[j]))
          race_url <- race_list %>% html_nodes("a") %>% html_attr("href") # 全urlを取得
          
          # レース結果のをurlを取得
          race_url <- race_url[str_detect(race_url, pattern="result")==1] # 「result」が含まれるurlを抽出
        }
        , error = function(e){signal <- 1}
      )
      
      for (i in 1:length(race_url)){
        
        print(str_c("現在、", year, "年", k, "月",j, "グループ、", i,"番目のレースの保存中です"))
        
        tryCatch(
          {
            race1 <-  read_html(str_c("https://keiba.yahoo.co.jp",race_url[i])) # レース結果のurlを取得
            signal <- 0
            Sys.sleep(2)
          }
          , error = function(e){signal <- 1}
        )
        
        # レースが中止orエラーでなければ処理を実行
        if (identical(race1 %>%
                      html_nodes(xpath = "//div[@class = 'resultAtt mgnBL fntSS']") %>%
                      html_text(),character(0)) == TRUE && signal == 0){
          
          # レース結果をスクレイピング
          race_result <- race1 %>% html_nodes(xpath = "//table[@id = 'raceScore']") %>%
            html_table()
          race_result <- do.call("data.frame",race_result) # リストをデータフレームに変更
          # レース結果のurlが取得できないことがあったので、再処理のコードを準備
          x <- 0
          while (length(race_result) == 0){
            race1 <-  read_html(str_c("https://keiba.yahoo.co.jp",race_url[i])) # レース結果のurlを取得
            race_result <- race1 %>% html_nodes(xpath = "//table[@id = 'raceScore']") %>%
              html_table()
            race_result <- do.call("data.frame",race_result) # リストをデータフレームに変更
            x <- x + 1 
            if (x == 10) break
          }
          
          colnames(race_result) <- c("order","frame_number","horse_number","horse_name/age","time/margin","passing_rank/last_3F","jockey/weight","popularity/odds","trainer") # 列名変更
          
          # 通過順位と上り3Fのタイム
          
          race_result <- dplyr::mutate(race_result,passing_rank=as.character(str_extract_all(race_result$`passing_rank/last_3F`,"(\\d{2}-\\d{2}-\\d{2}-\\d{2})|(\\d{2}-\\d{2}-\\d{2})|(\\d{2}-\\d{2})")))
          race_result <- dplyr::mutate(race_result,last_3F=as.character(str_extract_all(race_result$`passing_rank/last_3F`,"\\d{2}\\.\\d")))
          race_result <- race_result[-6]
          
          # タイムと着差
          race_result <- dplyr::mutate(race_result,time=as.character(str_extract_all(race_result$`time/margin`,"\\d\\.\\d{2}\\.\\d|\\d{2}\\.\\d")))
          race_result <- dplyr::mutate(race_result,margin=as.character(str_extract_all(race_result$`time/margin`,"./.馬身|.馬身|.[:space:]./.馬身|[ア-ン-]+")))
          race_result <- race_result[-5]
          
          # 馬名、馬齢、馬体重
          race_result <- dplyr::mutate(race_result,horse_name=as.character(str_extract_all(race_result$`horse_name/age`,"[ァ-ヴー・]+")))
          race_result <- dplyr::mutate(race_result,horse_age=as.character(str_extract_all(race_result$`horse_name/age`,"牡\\d+|牝\\d+|せん\\d+")))
          race_result <- dplyr::mutate(race_result,horse_weight=as.character(str_extract_all(race_result$`horse_name/age`,"\\d{3}")))
          race_result <- dplyr::mutate(race_result,horse_weight_change=as.character(str_extract_all(race_result$`horse_name/age`,"\\([\\+|\\-]\\d+\\)|\\([\\d+]\\)")))
          race_result$horse_weight_change <- sapply(rm_round(race_result$horse_weight_change, extract=TRUE), paste, collapse="")
          race_result <- race_result[-4]
          
          # ジョッキー
          race_result <- dplyr::mutate(race_result,jockey=as.character(str_extract_all(race_result$`jockey/weight`,"[ぁ-ん一-龠]+\\s[ぁ-ん一-龠]+|[:upper:].[ァ-ヶー]+")))
          race_result <- race_result[-4]
          
          # オッズと人気
          race_result <- dplyr::mutate(race_result,odds=as.character(str_extract_all(race_result$`popularity/odds`,"\\(.+\\)")))
          race_result <- dplyr::mutate(race_result,popularity=as.character(str_extract_all(race_result$`popularity/odds`,"\\d+[^(\\d+.\\d)]")))
          race_result$odds <- sapply(rm_round(race_result$odds, extract=TRUE), paste, collapse="")
          race_result <- race_result[-4]
          
          # レース情報
          race_date <- race1 %>% html_nodes(xpath = "//div[@id = 'raceTitName']/p[@id = 'raceTitDay']") %>%
            html_text()
          race_name <- race1 %>% html_nodes(xpath = "//div[@id = 'raceTitName']/h1[@class = 'fntB']") %>%
            html_text()
          race_distance <- race1 %>% html_nodes(xpath = "//p[@id = 'raceTitMeta']") %>%
            html_text()
          
          race_result <- dplyr::mutate(race_result,race_date=as.character(str_extract_all(race_date,"\\d+年\\d+月\\d+日")))
          race_result <- dplyr::mutate(race_result,race_name=as.character(str_replace_all(race_name,"\\s","")))
          race_result <- dplyr::mutate(race_result,race_distance=as.character(str_extract_all(race_distance,"\\d+m")))
          #race_result <- dplyr::mutate(race_result,race_type=as.character(str_extract_all(race_distance,pattern = "ダート")))
          
          race_result <- dplyr::arrange(race_result,horse_number) # 馬番順に並べる     
               
          ## ファイル貯めるのかく
          if (k == 1 && i == 1 && j == 1){
            dataset <- race_result
          } else {
            dataset <- rbind(dataset,race_result)
          } # if文2の終わり
        }else
        {
          print("保存できませんでした") 
        }# if文1の終わり
      } # iループの終わり
    } # jループ終わり
  } # kループの終わり
  beep(3)
  write.csv(dataset,"race_result2.csv", row.names = FALSE)
  dbWriteTable(con, "race_result", dataset)
  dbSendQuery(con, "INSERT INTO dataset select * from race_result")
  dbSendQuery(con, "DROP TABLE race_result")
} # yearループの終わり
beep(5)

options(warn = 1)

dbDisconnect(con)

このコードを実行し、データを取得したところ、データ数は1171008になりました。前回とは比較にならないほどのデータ量になり、分析を行うときはRSQLiteを用いてデータを落として来ようと思います。分析結果も近々公開する予定です。