MLB.comのAPIを使ってベースボールのデータを取得する

この記事は、[はじめてのアドベントカレンダー Advent Calendar 2024](https://qiita.com/advent-calendar/2024/first-time)の一部として公開します。

# はじめに
メジャーリーグベースボールでは、MLB.comのAPIを利用することでリアルタイムにスコアを知ったり、選手やチームの情報を得たりすることができます。
APIはREST形式でリクエストし、JSON形式で戻り値を取得できます。
JSON内にMLB Advanced Mediaのcopyright表記があることから、MLB公式の一球速報＝いわゆるGamedayに使われていると考えられます。

APIは多岐にわたり、公式なドキュメントもないので独自に解析できた分だけをここで紹介します。

## 一日のスコア一覧を取得するAPI
エンドポイント：```https://statsapi.mlb.com/api/v1/schedule/games/```

パラメータ：```?sportId=1&date=MM/DD/YYYY```
ここでMM/DD/YYYYには、月日年をそれぞれ２桁／２桁／４桁で指定します。

例えば、2024年4月11日のスコア一覧を取得するURLは以下のようになります。
```https://statsapi.mlb.com/api/v1/schedule/games/?sportId=1&date=04/11/2024```

このURLをブラウザから叩くと、以下のような情報が返ってきます。
[著作権表記]
[その日の試合数]
[試合の詳細情報×試合数分]

実際のJSONで各項目を説明します(見やすさのため整形しています)。
ここでは2024年4月11日の試合から、ニューヨーク・メッツ＠アトランタ・ブレーブスを例に説明します。
```jsonc
{
	"copyright" : "Copyright 2024 MLB Advanced Media,
	 L.P.  Use of any content on this page acknowledges agreement to the terms posted here http : //gdx.mlb.com/components/copyright.txt",
	"totalItems" : 7,
	"totalEvents" : 0,
	"totalGames" : 7,            //その日の試合数
	"totalGamesInProgress" : 0,  //JSON取得時点で進行中の試合数
	"dates" : [
		{
			"date" : "2024-04-11",
			"totalItems" : 7,
			"totalEvents" : 0,
			"totalGames" : 7,
			"totalGamesInProgress" : 0,
			"games" : [  //ここから下に試合の情報が試合数分だけ並ぶ
				{
					"gamePk" : 747137,
					"gameGuid" : "8c370601-f3b6-4eae-8e3d-30aad41aaae7",
					"link" : "/api/v1.1/game/747137/feed/live",  //試合の詳細情報のURL(後述)
					"gameType" : "R",  //レギュラーシーズン・ポストシーズンなどの区分を記号で表したもの
					"season" : "2024",  //シーズンの年度
					"gameDate" : "2024-04-11T16 : 20 : 00Z",
					"officialDate" : "2024-04-11",  //試合日
					"status" : {
						"abstractGameState" : "Final",
						"codedGameState" : "F",
						"detailedState" : "Final",  //試合前／試合中／試合終了の区分
						"statusCode" : "F",  //記区分を記号で表したもの
						"startTimeTBD" : false,
						"abstractGameCode" : "F"
					},
					"teams" : {  //対戦チームの情報
						"away" : {  //アウェイチームの情報
    							"leagueRecord" : {  //その試合が行われた時点での勝ち数・負け数・勝率　※延期になった場合は再試合が行われた時点での数値が入る
								"wins" : 5,
								"losses" : 7,
								"pct" : ".417"
							},
							"score" : 16,  //その試合での得点
							"team" : {  //チーム情報
								"id" : 121,
								"name" : "New York Mets",  //チーム名
								"link" : "/api/v1/teams/121"  //チームの詳細情報のURL(後述)
							},
							"isWinner" : true,  //試合の勝者かどうか
							"splitSquad" : false,  //スプリングトレーニングでチームを２つに分けて試合をしているかどうかを指すと思われる
							"seriesNumber" : 4
						},
						"home" : {  //ホームチームの情報：各項目はアウェイチームと同様
							"leagueRecord" : {
								"wins" : 7,
								"losses" : 4,
								"pct" : ".636"
							},
							"score" : 4,
							"team" : {
								"id" : 144,
								"name" : "Atlanta Braves",
								"link" : "/api/v1/teams/144"
							},
							"isWinner" : false,
							"splitSquad" : false,
							"seriesNumber" : 4
						}
					},
					"venue" : {  //球場の情報
						"id" : 4705,
						"name" : "Truist Park",  //球場名
						"link" : "/api/v1/venues/4705"  //球場の詳細情報のURL(後述)
					},
					"content" : {  //試合の詳細情報その２？のURL(後述)
						"link" : "/api/v1/game/747137/content"
					},
					"isTie" : false,  //引き分けかどうか　※MLBでもオープン戦などでは引き分けがある
					"gameNumber" : 1,
					"publicFacing" : true,
					"doubleHeader" : "N",  //ダブルヘッダーかどうか
					"gamedayType" : "P",
					"tiebreaker" : "N",  //タイブレークになったかどうか
					"calendarEventID" : "14-747137-2024-04-11",
					"seasonDisplay" : "2024",
					"dayNight" : "day",  //デーゲーム・ナイトゲームの区分
					"scheduledInnings" : 9,
					"reverseHomeAwayStatus" : false,
					"inningBreakLength" : 120,
					"gamesInSeries" : 3,
					"seriesGameNumber" : 3,
					"seriesDescription" : "Regular Season",  //レギュラーシーズン・ポストシーズンなどの区分
					"recordSource" : "S",
					"ifNecessary" : "N",
					"ifNecessaryDescription" : "Normal Game"
				},
(中略：ほかの試合の情報が並ぶ)
			],
			"events" : [
				
			]
		}
	]
}
```

試合がその日に完了しないケースもあります。
具体的には天候不順等で試合前に中止になった・サスペンデッドゲームになったケースです。

以下に例を示します。関係ない部分は省略します。
例１：試合前に中止になったケース(ニューヨーク・メッツ＠セントルイス・カージナルス：2024年5月8日から8月5日に順延された試合より)
```jsonc
				{
					"gamePk" : 745184,
					"gameGuid" : "2b29ac99-896e-406b-bb76-9fc46625a8b8",
					"link" : "/api/v1.1/game/745184/feed/live",
					"gameType" : "R",
					"season" : "2024",
					"gameDate" : "2024-05-08T17 : 15 : 00Z",  //試合が行われるはずだった日時
					"officialDate" : "2024-08-05",
					"rescheduleDate" : "2024-08-05T21 : 15 : 00Z",
					"rescheduleGameDate" : "2024-08-05",  //再試合が行われる日付
					"status" : {
						"abstractGameState" : "Final",
						"codedGameState" : "D",
						"detailedState" : "Postponed",  //試合の区分がPostponed(中止)となる
						"statusCode" : "DR",
						"startTimeTBD" : false,
						"reason" : "Rain",  //中止理由：この試合の場合は雨
						"abstractGameCode" : "F"
					},
					"teams" : {
						"away" : {
							"leagueRecord" : {  //勝敗・勝率は再試合が行われた(すなわちrescheduleGameDateの)時点での値となる
								"wins" : 59,
								"losses" : 53,
								"pct" : ".527"
							},
                            //中止の場合はここに得点の項目がない
(中略)
						},
						"home" : {
							"leagueRecord" : {
								"wins" : 57,
								"losses" : 56,
								"pct" : ".504"
							},
(中略)
						}
					},
(中略)
				}
```

例２：サスペンデッドゲームになったケース(ボルチモア・オリオールズ＠セントルイス・カージナルス：2024年5月21日に中断された試合が5月22日に再開された試合より)
```jsonc
				{
					"gamePk" : 745180,
					"gameGuid" : "d2b02f70-4d0f-4290-97dd-df6f52ce3e01",
					"link" : "/api/v1.1/game/745180/feed/live",
					"gameType" : "R",
					"season" : "2024",
					"gameDate" : "2024-05-21T23 : 45 : 00Z",  //試合が始まった日時
					"officialDate" : "2024-05-21",
					"resumeDate" : "2024-05-22T16 : 15 : 00Z",  //試合が再開される日時
					"resumeGameDate" : "2024-05-22",  //試合が再開される日付：この項目またはresumeDateの有無でサスペンデッドかどうかを判別する
					"status" : {  //中止の場合とは異なり、サスペンデッドの理由の項目はない
						"abstractGameState" : "Final",
						"codedGameState" : "F",
						"detailedState" : "Final",
						"statusCode" : "F",
						"startTimeTBD" : false,
						"abstractGameCode" : "F"
					},
					"teams" : {
						"away" : {
							"leagueRecord" : {  //試合が終了した日付の時点での勝敗・勝率
								"wins" : 29,
								"losses" : 17,
								"pct" : ".630"
							},
							"score" : 1,  //試合が終了した時点の得点
(中略)
						},
						"home" : {
							"leagueRecord" : {
								"wins" : 22,
								"losses" : 26,
								"pct" : ".458"
							},
							"score" : 3,
(中略)
						}
					},
(中略)
				},
```

## その他のAPI
前述のJSONの説明の中に(後述)と書かれた項目があります。
これは別途APIが用意されていて、より詳細な情報を取得できる項目です。

基本的には(ID, 名前, URL)の組で表現されていて、URLの項目を使えばIDは基本的に必要はありません。

以下にそれぞれのAPIについて説明します。
ただし、試合一覧APIと違って実際にコードを書いて検証したわけではないので、多分に推測が含まれます。

### 試合詳細を取得するAPI
エンドポイント：```https://statsapi.mlb.com/api/v1.1/game/```

パラメータ：```gamePk/feed/live```
ここでgamePkは試合固有のIDで、試合一覧APIの試合情報に含まれます。

例えば、2024年5月22日　ミネソタ・ツインズ＠ワシントン・ナショナルズの試合詳細を取得するURLは以下のようになります。
```https://statsapi.mlb.com/api/v1.1/game/744856/feed/live```

おそらくこのAPIが最も重要で、試合の詳細な情報が手に入るはずなのですが、項目があまりにも多くて解析できていません。
とりあえず流し読みで分かった点について説明します。
+ おそらく一球ごとの投球結果が含まれており、いわゆる一球速報に相当するデータが全て含まれていると思われます。
+ 一球ごとの球速・縦横の変化量といった詳細な投球内容が含まれています。Statcast導入前(2014年以前)のスコアについては、これらの項目が存在しないのか空欄なのかは未確認です。
+ 打者のホットゾーンに相当すると思しき項目もありますが、未対応なようで中身が空欄になっています。
+ チーム名・所属リーグ・所属地区・スプリングトレーニングの所属リーグなど、他のAPIで取得できる項目も含まれています。
+ 選手情報のAPIのURL(後述)は試合一覧APIには存在せず、このAPIから取得できます。

### チームの情報を取得するAPI
エンドポイント：```https://statsapi.mlb.com/api/v1/teams/```

パラメータ：```ID```
ここでIDはチーム固有のIDで、試合一覧APIのチーム情報に含まれます。
チーム名はシーズンによって変わりうるので、IDもシーズンによって変わってくると思われます。

例えば、2024年のミネソタ・ツインズのチーム詳細を取得するURLは以下のようになります。
```https://statsapi.mlb.com/api/v1/teams/142```

JSONは以下のようになっています(項目も多くないので省略せずにすべて記載します)。
```jsonc
{
	"copyright" : "Copyright 2024 MLB Advanced Media,
	 L.P.  Use of any content on this page acknowledges agreement to the terms posted here http : //gdx.mlb.com/components/copyright.txt",
	"teams" : [
		{
			"springLeague" : {
				"id" : 115,
				"name" : "Grapefruit League",  //スプリングトレーニングでの所属リーグ
				"link" : "/api/v1/league/115",
				"abbreviation" : "GL"
			},
			"allStarStatus" : "N",
			"id" : 142,
			"name" : "Minnesota Twins",  //チームの完全名
			"link" : "/api/v1/teams/142",  //APIへのリンク(上で例示したものと同じ)
			"season" : 2024,  //シーズンの年度
			"venue" : {
				"id" : 3312,
				"name" : "Target Field",
				"link" : "/api/v1/venues/3312"
			},
			"springVenue" : {  //スプリングトレーニングの球場
				"id" : 2862,
				"link" : "/api/v1/venues/2862"
			},
			"teamCode" : "min",
			"fileCode" : "min",
			"abbreviation" : "MIN",  //チームの短縮名
			"teamName" : "Twins",  //チーム名
			"locationName" : "Minneapolis",  //チームが存在する都市
			"firstYearOfPlay" : "1901",  //チームの創設年
			"league" : {  //所属リーグ
				"id" : 103,
				"name" : "American League",
				"link" : "/api/v1/league/103"
			},
			"division" : {  //所属地区
				"id" : 202,
				"name" : "American League Central",
				"link" : "/api/v1/divisions/202"
			},
			"sport" : {  //おそらくメジャー・マイナー・代表チーム等の区分
				"id" : 1,
				"link" : "/api/v1/sports/1",
				"name" : "Major League Baseball"
			},
			"shortName" : "Minnesota",
			"franchiseName" : "Minnesota",
			"clubName" : "Twins",
			"active" : true
		}
	]
}
```

### 球場の情報を取得するAPI
エンドポイント：```https://statsapi.mlb.com/api/v1/venues/```

パラメータ：```ID```
ここでIDは球場固有のIDで、試合一覧APIの球場情報に含まれます。
スプリングトレーニングで使われる球場のデータも存在するようで、球場のバリエーションはMLB30球団の本拠地に限りません。

例として、2024年のナショナルズ・パークの情報を取得するURLは以下のようになります。
```https://statsapi.mlb.com/api/v1/venues/3309```

JSONは以下のようになっています。球場名くらいしか見るところがありません。
```jsonc
{
	"copyright" : "Copyright 2024 MLB Advanced Media,
	 L.P.  Use of any content on this page acknowledges agreement to the terms posted here http : //gdx.mlb.com/components/copyright.txt",
	"venues" : [
		{
			"id" : 3309,
			"name" : "Nationals Park",
			"link" : "/api/v1/venues/3309",
			"active" : true,
			"season" : "2024"
		}
	]
}
```

### 選手の情報を取得するAPI
エンドポイント：```https://statsapi.mlb.com/api/v1/people/```

パラメータ：```ID```
ここでIDは球場固有のIDで、試合詳細APIのJSONに含まれます。

例として、2024年のManuel Margot選手の情報を取得するURLは以下のようになります。
```https://statsapi.mlb.com/api/v1/people/622534```

JSONは以下のようになっています。
```jsonc
{
	"copyright" : "Copyright 2024 MLB Advanced Media,
	 L.P.  Use of any content on this page acknowledges agreement to the terms posted here http : //gdx.mlb.com/components/copyright.txt",
	"people" : [
		{
			"id" : 622534,
			"fullName" : "Manuel Margot",  //フルネーム
			"link" : "/api/v1/people/622534",
			"firstName" : "Manuel",  //ファーストネーム
			"lastName" : "Margot",  //ラストネーム
			"primaryNumber" : "13",
			"birthDate" : "1994-09-28",  //誕生日
			"currentAge" : 30,  //年齢(シーズン開始日とか、何らかの基準があるはず)
			"birthCity" : "San Cristobal",  //出身地
			"birthCountry" : "Dominican Republic",  //出身国
			"height" : "5' 11\"",  //身長
			"weight" : 180,  //体重
			"active" : true,
			"primaryPosition" : {  //守備位置
				"code" : "7",
				"name" : "Outfielder",
				"type" : "Outfielder",
				"abbreviation" : "LF"
			},
			"useName" : "Manuel",
			"useLastName" : "Margot",
			"boxscoreName" : "Margot",
			"nickName" : "Yoandry",  //ニックネーム
			"gender" : "M",  //性別
			"nameMatrilineal" : "Gomez",
			"isPlayer" : true,
			"isVerified" : true,
			"pronunciation" : "man-WEHL mahr-GO",
			"mlbDebutDate" : "2016-09-21",
			"batSide" : {  //打席の左右
				"code" : "R",
				"description" : "Right"
			},
			"pitchHand" : {  //投球の利き腕
				"code" : "R",
				"description" : "Right"
			},
			"nameFirstLast" : "Manuel Margot",
			"nameSlug" : "manuel-margot-622534",
			"firstLastName" : "Manuel Margot",
			"lastFirstName" : "Margot,
			 Manuel",
			"lastInitName" : "Margot,
			 M",
			"initLastName" : "M Margot",
			"fullFMLName" : "Manuel Margot",
			"fullLFMName" : "Margot,
			 Manuel",
			"strikeZoneTop" : 3.47,
			"strikeZoneBottom" : 1.68
		}
	]
}
```
見てのとおりこのAPIでは所属チームや成績の情報を取得することができないので、選手の詳細情報を取得するAPIは別に存在するのではないかと考えています。


### 試合詳細その２？を取得するAPI
エンドポイント：```https://statsapi.mlb.com/api/v1/game/```

パラメータ：```gamePk/content```
ここでgamePkは試合固有のIDで、試合一覧APIの試合情報に含まれます。

例えば、2024年5月22日　ミネソタ・ツインズ＠ワシントン・ナショナルズの試合情報を取得するURLは以下のようになります。
```https://statsapi.mlb.com/api/v1/game/744856/content```

このAPIについてはよく分かりません。
選手の画像のURLや試合のハイライト映像のURLが含まれています。

# サンプル
以下のURLに、日付を指定してスコア一覧を見ることのできるプログラムを置いています。
スコアを見るだけならMLB公式のアプリで十分なのですが、API経由でスコアを取得することができるというサンプルです。
https://ss1.xrea.com/kevintheoutlaw.s1008.xrea.com/zipscore/zipscore.php

コード(PHP製)はGitHubにアップしています。
https://github.com/kk-outlaw/zipscore

# 今後について
試合詳細APIのJSONを解読できれば、かなり詳しいデータを蓄積して分析することができるのではないかと思います。
(たいていの場合はBaseball-ReferenceやFanGraphs、Baseball Savantで事足りてしまうと思いますが……これらのサイトでもカバーできないニッチな視点もあるかもしれません。)
APIについて新しいことが分かれば、記事を更新していきたいと思います。