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 reimport 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).textprint(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.
戻るとしても以前の規模ではない、という前提で組んだほうがよさそうです。
NOTEFBref そのものが使えなくなったわけではありません。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 pdimport 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 xAChristopher 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.94CAUTIONこのエンドポイントは公式に文書化されたものではなく、いつ変わってもおかしくありません。実際、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.2011 試合が 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 が使えず & が要る理由 を書きました。