見出し画像

プロ野球の打撃成績を取得できる!?APIを作ってみた!!

noteをご覧のみなさん、こんにちは!
第4開発事業部のTAIKIです。
今回はプロ野球の打撃成績をスクレイピングしてjsonとして取得するAPIを作ったので、そちらについてご紹介したいと思います!

【ライターの紹介】
TAIKI
ALHの第4開発事業部に所属する開発エンジニア。
現在は中途社員の研修指導を担当している。
近頃はテニスに熱を入れています。誰にも打ち返せないストロークを完成させるのが最終目標!

今回のAPIを作ろうと思ったきっかけ


私はプロ野球観戦が趣味なのですが、この趣味であるプロ野球を題材にJavaの復習を兼ねてアプリ作成してみようと思いAPI作成に取り掛かることにしました!

スクレイピングとは?


エンジニアの方はもちろんご存知だど思いますが、非エンジニアの方向けにスクレイピングとは何かを説明させていただきます。
スクレイピングとは「データを収集しかつ目的に合わせて加工すること」を意味します。この「データ」とはWeb上の画像やデータを指します。
似たような言葉に「クローリング」がありますが、こちらはデータの収集を意味していて、抽出・加工することまでは含まれていないんですね。
もし、スクレイピングをAPIで自動化できれば、データ活用の効率は格段にアップします!

主な仕様について


Javaバージョン  :1.8

  • フレームワーク  :SpringBoot

  • ビルドツール   :maven

  • 動作ソフト    :JDK

  • 動作環境     :ローカル用(localhost:8080)

  • 主な外部ライブラリ:jsoup、jackson

  • インタフェース  :RESTful API

  • データ取得元   :NPB公式サイト

URLパラメータは以下。

  • ・リーグ (セ or パ)
    ・成績年 (2005~2022)

それぞれ指定がない場合はセ・リーグと2022年に設定されてある。以下凡例。

Git情報


Git情報はこちらをご覧ください↓

Javaコード


Controller

package com.baseballstatsbatting.springbootbaseballsample;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.jsoup.Jsoup;
import org.jsoup.select.Elements;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;


@RestController
public class ResultController {
@GetMapping("/NPB")
public List<Stats> GetStats(@RequestParam Map<String,String> requestParams) throws IOException{
//URLパラメータ(league)のバリデーション処理と変数へのセット
String league = validParamLeague(requestParams.get("league"));
//URLパラメータ(year)のバリデーション処理と変数へのセット
String year = validParamYear(requestParams.get("year"));
//リーグと年情報を引き渡しNPB公式サイトをスクレイピング
Elements scrapingData = Jsoup.connect("https://npb.jp/bis/"+year+"/stats/bat_"+league+".html").get().select("tr");
//スクレイピングデータをリストに変換 (リーグ変数でセ・パ方式に変換)
List<Stats> stats = setStats(scrapingData, league);
return stats;
}

public final static List<Stats> setStats(Elements scrapingData, String league) throws IOException {
//配列を用意
List<Stats> stats = new ArrayList<Stats>();
//スクレイピングデータの配列数文ループ
for (int i = 2; i < scrapingData.size()-1; i++) {
Stats stat = new Stats();
if (i != scrapingData.size()) {
//スクレイピングデータの""を除去
String[] data = scrapingData.get(i).text().split(" ");
//以降statマップにセット
Double SluggingPercentage = Double.parseDouble(data[23]);
Double OnBasePercentage = Double.parseDouble(data[24]);
stat.setName(data);
stat.setLeague(getLeagueString(league));
stat.setBattingAverage(Double.parseDouble(data));
stat.setGames(Integer.parseInt(data));
stat.setAtBats(Integer.parseInt(data[5]));
stat.setPlateAppearance(Integer.parseInt(data[6]));
stat.setRuns(Integer.parseInt(data[7]));
stat.setHits(Integer.parseInt(data[8]));
stat.setDoubles(Integer.parseInt(data[9]));
stat.setTriples(Integer.parseInt(data[10]));
stat.setHomeruns(Integer.parseInt(data[11]));
stat.setTotalBases(Integer.parseInt(data[12]));
stat.setRbi(Integer.parseInt(data[13]));
stat.setStolenBases(Integer.parseInt(data[14]));
stat.setStolenBaseDeath(Integer.parseInt(data[15]));
stat.setSacrificeBunts(Integer.parseInt(data[16]));
stat.setSacrificeFlies(Integer.parseInt(data[17]));
stat.setBaseOnBalls(Integer.parseInt(data[18]));
stat.setHitByPitches(Integer.parseInt(data[20]));
stat.setStrikeOuts(Integer.parseInt(data[21]));
stat.setDoublePlay(Integer.parseInt(data[22]));
stat.setSluggingPercentage(SluggingPercentage);
stat.setOnBasePercentage(OnBasePercentage);
stat.setOps(SluggingPercentage + OnBasePercentage);
//選手1人分の成績を配列に追加
stats.add(stat);
}
}
//選手情報の配列をリターン
return stats;
}

public final static String validParamLeague(String league) {
//URLパラメータとしてリーグ情報が引き渡さなかったらc(セントラルリーグの意)にする
if(league == null) {
league = "c";
//URLパラメータとしてリーグ情報が引き渡された文字列がcまたはp以外は強制的にcにする
} else if(!league.equals("c") && !league.equals("p")){
league = "c";
}
return league;
}

public final static String validParamYear(String year) {
//URLパラメータとして年情報が引き渡さなかったら2022年にする
if(year == null) {
year = "2022";
//URLパラメータとして年情報が引き渡された文字列が数字以外は強制的に2022にする
} else if (!year.matches("[0-9]+")) {
year = "2022";
//URLパラメータとして年情報が引き渡された文字列が2022より大きい数字であれば2022年にする
} else if(Integer.parseInt(year) > 2022){
year = "2022";
//URLパラメータとして年情報が引き渡された文字列が2005より小さい数字であれば2022年にする
} else if (Integer.parseInt(year) < 2005) {
year = "2022";
}
return year;
}

public final static String getLeagueString(String league) throws IOException {
//URLパラメータとしてリーグ情報がcで引き渡された場合「セ」として返す
if(league.equals("c")) {
league = "セ";
} else {
//URLパラメータとしてリーグ情報がc以外で引き渡された場合「パ」として返す
league = "パ";
}
return league;
}
}

Model

getter/setterは省略。

package com.baseballstatsbatting.springbootbaseballsample;

public class Stats {
private int id; //ID
private String name; //選手名
private String league; //リーグ名
private int year; //記録年
private Double battingAverage; //打率
private int games; //試合数
private int atBats; //打席
private int plateAppearance; //打数
private int hits; //安打
private int doubles; //二塁打
private int triples; //三塁打
private int homeruns; //本塁打
private int totalBases; //塁打
private int rbi; //打点
private int runs; //得点
private int strikeOuts; //三振
private int baseOnBalls; //四球
private int hitByPitches; //死球
private int sacrificeBunts; //犠打
private int sacrificeFlies; //犠飛
private int stolenBases; //盗塁
private int stolenBaseDeath; //盗塁死
private int doublePlay; //併殺打
private Double onBasePercentage; //出塁率
private Double sluggingPercentage; //長打率
private Double ops; //OPS
private Double scoringRangePercentage; //得点圏
private int errors; //失策

表示画面


ブラウザで動かした画面はこちら。

実際にスクレイピングしてデータ収集した結果はこちら。

API作成を振り返ってみて


久々にコーディングをしたのであらゆるところで手詰まりになりました...。
とはいえ趣味を絡めて復習の機会を設けられたので非常に有意義な時間になりました♪
今回のAPI作成で苦労した点は、欲しい情報をブロック要素から読み解いてピンポイントで取得することです。こちらについては、要素指定によって取得できる情報がどう変化するか何度も試して乗り越えることができました。
また、今回の作成を通してブロックの要素を読み解く過程で、フロントエンド側の知識が習得することができました。そして、得られた気付きとしては、バックエンド側から渡すデータによってフロントエンド側の技術者の機能実装に助力できるということです。
今後の技術的な目標としては、データのソートや絞り込みをDB側・バックエンド・フロントエンドでそれぞれ実装していきたいと考えています。

ソースは恥ずかしいですがあえて公開しているので、gitからクローンして遊んだり読んで些細なことでもコメントしてもらえると今後に活かせますのでよろしくお願いします。
あと今回リーグ・単年で取得するロジックですが、NPB公式から両リーグ・17年分全てを一括で取得するロジックも裏では作ったので色々と試して遊べそうです^o^

以上、ここまでお読みいただきありがとうございました!



        ↓ ↓ ↓ 採用サイトはこちら ↓ ↓ ↓ 


↓ ↓ ↓ ALHについてはこちら ↓ ↓ ↓


↓ ↓ ↓ もっとALHについて知りたい? ↓ ↓ ↓