2612 文字
13 分
Pythonでサッカー分析を学ぼうとしたら、無料のxGが消えていた

Python の学習として、好きなサッカークラブの試合データを触るところから始めようと思いました。
日本語・英語を問わず入門記事はたくさんあります。ところが、そのほとんどが前提にしているデータソースが、いま動きません。

FBref の高度スタッツ——xG(Expected Goals)をはじめとする、いわゆる「サッカー分析らしい指標」が、2026年1月に丸ごと消えたためです。この記事は速報ではなく、半年後に学習を始めて入り口で足止めを食らった側から、いま何が使えるのかを実際に取得して確かめた記録です。

TIP

記事中のコードをまとめて実行できる ノートブックを用意しました。Google Colabで動くため、追加インストールなしで上から流すだけで動きます。

Google Colab で開く(ソース: c12o-dev/notebooks)

リンクを開いてそのまま実行できます。所要 1〜2 分ほどです。

2026年1月20日に何が起きたか#

一次ソースは Sports Reference の公式ブログ FBref & Stathead Data Update(2026年1月20日)です。要点はこの一文に尽きます。

last week the provider of our advanced soccer data sent us a letter terminating our access to their data feeds and requiring the deletion of their data from the site immediately.

データ提供元から契約終了とデータ削除を通告され、従うほかなかった、という説明です。注意したいのは、この記事が提供元を名指ししていないこと。コミュニティでは Opta(Stats Perform)だと受け止められていますが、公式声明にその名前は出てきません。

確かなのは、その 8 日前の 2026年1月12日に Stats Perform が FIFA の公式ベッティングデータ・ストリーミング配信権の独占ディストリビューターに決まったと発表されていることです。因果関係が公表されているわけではないので、並んだ事実として置いておきます。

半年後の今も戻っていない#

この記事のメインはここです。1月時点の記事は「消えた」までしか書いていません。では 2026年8月の今はどうなのか。

FBref は現在ボット対策が入っていて、curl や自動取得ツールからは 403 が返ります。そこで Internet Archive に残っている 2026年7月31日のスナップショットで、欧州5大リーグのシュート系スタッツ一覧のテーブル列を確認しました。

import re
import requests
URL = ("https://web.archive.org/web/20260731082148/"
"https://fbref.com/en/comps/Big5/shooting/players/Big-5-European-Leagues-Stats")
html = requests.get(URL, timeout=60).text
print(sorted(set(re.findall(r'data-stat="([a-z_0-9]+)"', html))))

返ってきた列は goals / shots / shots_on_target / shots_on_target_pct / goals_per_shot / pens_made といった基礎スタッツだけでした。xg、npxg に相当する列は 1 つも残っていません。半年経っても、シュート数は取れるが xG は取れない状態が続いているということです。

線引きは PK 関連を見るとはっきりします。PK の試投数・成功数(pens_att / pens_made)は残っている一方で、PK を除いた期待得点である npxg は消えています。試合で数えられる事実は残り、モデルで推定した値だけが抜けたという消え方です。

公式ブログも、将来の高度データについてこう書いています。

if we present advanced data in the future it will certainly have a dramatically smaller scale.

戻るとしても以前の規模ではない、という前提で組んだほうがよさそうです。

NOTE

FBref そのものが使えなくなったわけではありません。100 以上の大会にわたる試合結果・出場時間・得点といった基礎データは今も健在で、歴史データの参照先としては引き続き有力です。消えたのは xG などの高度スタッツだけです。

代替を実際に叩いて確かめた#

「代替はこれ」と列挙している記事は増えましたが、リンクを並べただけのものも多いので、学習で使う前提の 3 つを実際に取得しました。以下はすべて 2026年8月上旬時点で動作を確認したものです。

Understat —— xG が要るならまずここ#

Understat は欧州5大リーグの xG を独自モデルで公開しているサイトで、選手単位の xG / npxG / xA / xGChain / xGBuildup が取れます。FBref の穴を埋める第一候補です。

ただし、ここに落とし穴があります。日本語の入門記事の多くが書いている「HTML に埋め込まれた var playersData = JSON.parse('...') を正規表現で抜く」方法は、もう通りません。 現在の Understat はページ読み込み後に XHR でデータを取りに行く作りに変わっていて、HTML 側には該当の変数が存在しないためです。

代わりに、ページ自身が叩いているエンドポイントを使います。

import pandas as pd
import requests
LEAGUE, SEASON = "Serie_A", "2025"
res = requests.get(
f"https://understat.com/getLeagueData/{LEAGUE}/{SEASON}",
headers={
"User-Agent": "Mozilla/5.0",
"X-Requested-With": "XMLHttpRequest",
"Referer": f"https://understat.com/league/{LEAGUE}/{SEASON}",
},
timeout=30,
)
df = pd.DataFrame(res.json()["players"])
print(df.columns.tolist())
['id', 'player_name', 'games', 'time', 'goals', 'xG', 'assists', 'xA',
'shots', 'key_passes', 'yellow_cards', 'red_cards', 'position',
'team_title', 'npg', 'npxG', 'xGChain', 'xGBuildup']

列は揃っていますが、このエンドポイントは数値もすべて文字列で返します。 goals が "17"、xG が "17.13883000984788" という具合です。気づかずに df["goals"].sum() とすると足し算ではなく文字列連結になるので、pd.to_numeric() を通してから使ってください。

セリエA 2025/26 で 586 行。ミランの xG 上位はこうなりました。

player_name games time goals xG assists xA
Christopher Nkunku 32 1343 7 9.94 3 1.61
Rafael Leão 29 1874 9 9.60 3 4.98
Christian Pulisic 30 1612 8 9.54 4 5.04
Youssouf Fofana 33 2231 2 4.59 3 3.64
Adrien Rabiot 29 2546 6 4.36 4 3.94
CAUTION

このエンドポイントは公式に文書化されたものではなく、いつ変わってもおかしくありません。実際、HTML 埋め込み方式が消えたのが良い例です。学習用途でも取得間隔は空けて、一度取ったものはローカルに保存して使い回してください。

なお Understat の robots.txt は User-agent: * / Disallow: / で、自動アクセスを許可していません。ライブラリ経由でも同じことなので、負荷をかけない範囲での利用にとどめてください。

自分でリクエストを組みたくない場合は、soccerdata が Understat を含む複数サイトのスクレイパーをまとめてくれています。キャッシュも面倒を見てくれるので、学習目的ならこちらのほうが安全です。

StatsBomb Open Data —— イベント単位で学ぶなら#

StatsBomb Open Data は、パス・シュート・タックルといったイベント 1 件ずつが JSON で公開されている無料データセットです。GitHub の raw ファイルなので、ボット対策も認証もありません。

BASE = "https://raw.githubusercontent.com/statsbomb/open-data/master/data"
events = requests.get(f"{BASE}/events/3773386.json", timeout=60).json()
shots = [e for e in events if e["type"]["name"] == "Shot"]
print(len(events), len(shots), shots[0]["shot"]["statsbomb_xg"]) # 3891 29 0.201

1 試合が 3,891 イベント、うちシュート 29 本。各シュートに statsbomb_xg が入っており、シュート時点の全選手位置(freeze_frame)まで付いてきます。収録は 80 コンペティション。ただし最新シーズンを網羅的に追う用途には向きません。ワールドカップやメッシのキャリア全試合など、公開範囲が限定されているためです。

利用条件も明快で、README は「分析や考察を公開するときは出典を StatsBomb と明示すること」だけを求めています。

football-data.co.uk —— xG はないが手軽#

football-data.co.uk はリーグ・シーズンごとの CSV を直接置いてくれています。pandas の 1 行で読めるのが強みです。

df = pd.read_csv("https://www.football-data.co.uk/mmz4281/2526/I1.csv")
print(df.shape) # (380, 131)
print([c for c in df.columns if "xg" in c.lower()]) # []

380 試合 × 131 列。ただし中身は結果・シュート数・カード数と、大量のブックメーカーオッズです。xG は入っていません。 前処理や結合の練習台としては手軽ですが、xG を扱いたいなら別のソースが要ります。

学習目的ならどう組むか#

3 つ触ったうえでの結論です。

公開・再配布まで考えるなら StatsBomb Open Data と football-data.co.uk。 この 2 つは利用条件がはっきりしています(StatsBomb は出典明示を求めるのみ、football-data.co.uk は robots.txt で自動アクセスを許可)。分析結果を記事や成果物として出す前提なら、こちらを土台にするのが安全です。

xG を使いたいなら Understat を主軸にする。 シーズン単位・選手単位で xG が揃う無料ソースとして、現状これが最も実用的です。ただし上のとおり自動アクセスは許可されていないので、手元の学習にとどめ、エンドポイントの変更に備えて取得と分析のコードは分けておきます。

取得したデータは必ずローカルに保存する。 毎回リクエストを投げる書き方は、相手への負荷も自分の試行錯誤の速度も悪くします。一度 CSV か Parquet に落として、以降はそれを読みます。

イベントデータの練習は StatsBomb Open Data で。 「シュート 1 本ごとの位置と xG」を扱う経験は、集計済みのテーブルからは得られません。対象大会は限られますが、学ぶだけなら十分です。

入門記事は 2026年1月より前かどうかを最初に見る。 それ以前の記事は FBref に高度スタッツがある前提で書かれています。手順が動かないのは自分のせいではないので、データソースの節だけ読み替えて進めます。

まとめ#

無料で xG が手に入る時代が終わったわけではありませんが、「FBref を叩けば全部ある」時代は終わりました。手順どおりにやって動かないときは、コードではなくデータソースのほうを疑ってください。

次は、ここで取ってきた Understat のデータを実際に pandas で触っていきます。手始めに、絞り込みで最初につまずく Pandas で and が使えず & が要る理由 を書きました。

Pythonでサッカー分析を学ぼうとしたら、無料のxGが消えていた
https://blog.c12o.net/posts/football-data-sources-2026/
作者
Seu (c12o)
公開日
2026-08-01
ライセンス
CC BY-NC-SA 4.0