Веб-скрапинг расширенной футбольной статистики

В последнее время я все обсуждал роль удачи в футболе. А может, это не чистая удача, а мастерство? Или команды завоевывают свои лиги исключительно на мастерстве? Или важность удачи достаточно большая? Кому везет, а кому нет? Заслуживала эта команда на вылет? И еще куча подобных вопросов.

Но, поскольку я – чувак данных, я подумал, так давайте получим данные и найдем ответы. Хотя, как ты измеришь удачу? Как ты оценишь мастерство? Не существует такой единой метрики, как в компьютерных играх FIFA или PES. Мы должны смотреть на общую картину, на долгосрочные данные с несколькими переменными и с учетом контекста каждой отдельной игры. Потому что в отдельные моменты игроку одной команды просто не хватает удачи, чтобы забить победный гол на последних минутах после полного доминирования над противником, и он заканчивается ничьей, и обе команды получают 1 балл, тогда как было ясно, что первая команда заслужила победу. Одной команде повезло, другой не слишком. Так, в этой ситуации это удача, поскольку одна команда сделала все, создала достаточно опасных моментов, но не забила. Такое происходит. И поэтому мы любим футбол. Потому что здесь все может произойти.

Хотя нельзя измерить удачу, но можно понять, как команда играла на основе относительно новой метрики в футболе – xG, или ожидаемые гола.

xG – это статистический показатель качества созданных и упущенных шансов

Вы можете найти данные по этому показателю на сайте understat.com. Отсюда я и буду скрапить данные.

Понимание данных

Итак, что же это за хрень это xG и почему оно важно. Ответ мы можем найти на домашней странице understat.com.

Ожидаемые голы (xG) – это новая революционная футбольная метрика, которая позволяет оценивать производительность команды и игрока.
В игре с низким количеством очков, таких как футбол, итоговый счет не дает четкой картины производительности.
Вот почему все больше и больше спортивных аналитиков обращаются к передовым моделям, как xG, что является статистическим показателем качества созданных и упущенных шансов.
Нашей целью было создать наиболее точный метод оценки качества ударов по воротам.
Для этого мы подготовили алгоритмы прогнозирования с нейронными сетями с большим набором данных (> 100 000 образцов, более 10 параметров для каждого).

understat.com

Исследователи научили нейронную сеть на основе ситуаций, которые привели к голам, и теперь она дает нам оценку того, сколько реальных шансов команда имела во время матча. Потому что вы можете иметь 25 ударов в течение игры, но если все они находятся на большом расстоянии или под низким углом к ​​воротам или слишком слабы, – короче, удары низкого качества, они не приведут к взятию ворот. Хотя некоторые «эксперты», которые не видели игры будут говорить, что команда доминировала, создали тонны шансов бла-бла-бла-бла. Качество этих шансов имеет значения. И здесь метрика xG становится полезной. С этим показателем вы теперь понимаете, что Месси создает голы в условиях, где очень трудно забить, или вратарь спасает команду, когда должен был быть гол. Все эти вещи складываются, и мы видим чемпионов, которые имеют опытных игроков и каким немного повезло, и мы видим неудачников, которые могут иметь хороших игроков, но не хватает удачи. И цель этого проекта – понять и представить эти цифры, чтобы показать роль удачи в сегодняшнем футболе.

Давайте начнем

Мы начинаем с импорта библиотек, которые будут использоваться в этом проекте:

  • numpy – фундаментальный пакет для научных вычислений с Python
  • pandas – библиотека, обеспечивающая высокую производительность, удобство в использовании структур данных и инструментов анализа данных
  • requests – это единственная HTTP-библиотека без ГМО для Python, безопасна для потребления человеком (люблю эту строку из официальных документации :D)
  • BeautifulSoup – библиотека Python для вывода данных из файлов HTML и XML.
import numpy as np 
import pandas as pd
import requests
from bs4 import BeautifulSoup

Исследование сайта и структура данных

В любом скраппинг проекте первое, что нужно сделать, это исследовать веб-страницу, которую вы хотите считать, и понять, как она работает. Это является фундаментальным. Поэтому мы начинаем оттуда.

На домашней странице можно заметить, что на сайте есть данные для 6 европейских лиг:

  • Испанская Ла Лига
  • Английская Премьер Лига
  • Немецкая Бундес Лига
  • Итальянская Серия А
  • Французская Лига 1
  • Российская Премьер Лига

И мы также видим, что собранные данные начинаются с сезона 2014/2015. Другая заметка, которую мы делаем, – это структура URL: ‘https://understat.com/league' + ‘/лига‘ + ‘/год

Таким образом, мы создаем переменные с этими данными, чтобы иметь возможность выбрать любой сезон или любую лигу.

# создать urls для всех сезонов каждой из лиг
base_url = 'https://understat.com/league'
leagues = ['La_liga', 'EPL', 'Bundesliga', 'Serie_A', 'Ligue_1', 'RFPL']
seasons = ['2014', '2015', '2016', '2017', '2018']

Karma +1 when you share it:

  1. Hi, thanks for this article!

    I just have a question about this part :
    “teams = {}
    for id in data.keys():
    teams[id] = data[id][‘title’]”

    Where the “data” (data.keys) was defined before ? I have an error for this part on my notebook…

    1. Hi,

      data appears when you read the json into it with this line of code:
      data = json.loads(json_data)

      I missed it in the article. Please, check my notebook at Kaggle for working code here

  2. Hi Sergi!

    Your article is awesome! it is really helpful! I love it!!!

    I have a question for you!

    I would like to edit the code for getting the same information (matches, xg, xga), but only for home games and away games. I do not want to have the overall table. I want to have two different tables: home and away

    What do I have to edit in your code in order to only have home games stats and away games stats?

    I am a really beginner, so if you can explain it step by step would be great

    I really hope you can help me out with this! I would really appreciate it

    Thank you

  3. Hello Sergi!

    Thanks for the article and the dataset!

    My plan is to make an analysis taking into consideration the home and away results.

    I have 2 questions for you:

    – which changes need to be made in the code in order to get 2 tables, one for home and other for away games?

    – also, i was looking at the code and this error came up:

    ind_start = string_with_json_obj.index(“(‘”)+2
    ValueError: substring not found

    Can you help me solve it?

    I still have little experience in web scrapping, so your help would be much aprecciated.

    Keep up with your good work!

    Thanks in advance,
    Francisco

    1. Thank you Francisco for kind feedback!

      In order to achieve that you have to separate the data by column ‘h_a’ before summing everything up. If you want to do that on your own you have to stop before the paragraph “Manipulations to make data as in the original source”. In the dataframe you get in that step there will be all raw data and “home/away” column (‘h_a’).

      Here you can find my Kaggle notebook without summing up the data. It contains all the data per every game – the output you get there can be just splitted by column ‘h_a’ manually in Excel or just add an additional line in the code and export 2 CSVs.
      https://www.kaggle.com/slehkyi/web-scraping-football-statistics-per-game-data

      Also, if you don’t want to play too much with scraping, here is the dataset I maintain https://www.kaggle.com/slehkyi/extended-football-stats-for-european-leagues-xg – it has both summary and game records. Updating twice a year.

      Hope it helps! If you still have questions you can reach me on social or by email 🙂 all info in the footer 🙂

  4. Hola Sergi,

    Realizando los pasos que mencionas al llegar a esta parte del código

    import json

    string_with_json_obj = ”

    for el in scripts:
    if’teamsData’ in el.text:
    string_with_json_obj = el.text.strip()

    ind_start = string_with_json_obj.index(“(‘”)+2
    ind_end = string_with_json_obj.index(“‘)”)
    json_data = string_with_json_obj[ind_start:ind_end]

    json_data = json_data.encode(‘utf8’).decode(‘unicode_escape’)

    me salta un error : substring not found ; podrías indicarme como se puede resolver?

    1. Hola 🙂

      Bastante probable que no has descargado los datos en el paso anterior. Para revisar esto añade un par de prints para entender dónde no tienes datos.

      Por ejemplo aquí puedes comprobar si el variable scripts tiene datos:
      print(scripts)
      for el in scripts:
      # aquí puedes ver si hay algunos elementos en script
      print(el)
      if 'teamsData' in el.text:
      string_with_json_obj = el.text.strip()
      # aquí por ejemplo puedes revisar si string_with_json_obj tiene algún dato
      print(string_with_json_obj)

      Y así puedes validar otras cosas. Con simples print()

      Espero que eso ayude 😉

      1. Hello Sergi,

        I’m sorry to bother you, but I’m a beginner and I have about the same problem as the questioner above me.
        I have data in the script variable ( var teamsData = JSON.parse(‘\x7B\x2……………), but if I try to use .text, it won’t return anything. Has anything changed or am I just missing something?
        I’ve tried it on another, simpler, page and it works there, but here it looks like the .text (or get_text ()) function has stopped working here.
        Don’t you know where the problem might be?
        Thanks a lot

        Tomas

        1. Hey 🙂

          You have to use .text on the pile of data that is in the scripts tag, while looping through each tag. If you already extracted that text, your data is in the “string” type, so you have to deal with it as regular string.

          Also, if it doesn’t return anything maybe you didn’t catch any data… I just ran my notebook in Kaggle and it gets all the numbers.

          Try to debug your code: print content of any variable you introduce or change, even if you are sure about the output. Print all the data from scripts and manually check if there is a string ‘teamsData’ and check the type of that data, then print only ‘teamsData’ and its type and so on. Pretty sure you will find what’s wrong.

          Hope that helps 🙂 if not – find me on social or shoot me an email and we will discuss it more in depth.

          Cheers and have a great day 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *