Разоблачение баскетбола с помощью Python

Jan 24 2023
Часть 1: Соскоб с Beautiful Soup
В этой серии блогов используются мощные библиотеки Python, помогающие раскрыть некоторые скрытые статистические истины в баскетболе. Первым шагом в любом подходе, основанном на данных, является определение и сбор необходимых данных.

В этой серии блогов используются мощные библиотеки Python, помогающие раскрыть некоторые скрытые статистические истины в баскетболе. Первым шагом в любом подходе, основанном на данных, является определение и сбор необходимых данных.

К счастью для нас, на сайте Basketball-Reference.com есть страницы с данными о баскетболе, которые можно легко получить. Процессы этого пошагового руководства можно легко применить к любому количеству их страниц, но в этом случае мы планируем собрать сезонную статистику нескольких классов новичков.

Цели

  1. Определите источник данных
  2. Скачать страницу
  3. Определите важные элементы страницы
  4. Предварительная очистка и извлечение
  5. Архив
  • Пакет запросов
  • Пакет "Красивый суп"
  • Пакет панд

Определение источника данных

Basketball-Reference.com содержит сотни тщательно отобранных страниц со статистикой баскетбола, которая варьируется от средних сезонных значений типичных категорий очков, таких как очки, подборы и процент бросков, вплоть до пошаговых действий в каждой игре, сыгранной за последнюю игру. 20 или около того лет. В этом статистическом цунами можно легко заблудиться, если не поставить четкой цели, что именно искать.

Цель этого поста проста: получить данные о новичках, которые помогут оценить истинную ценность и потенциал молодого игрока.

Следующая ссылка является одной из таких страниц. В нем перечислены все соответствующие статистические данные о новичках в конкретном сезоне.

https://www.basketball-reference.com/leagues/NBA_1990_rookies-season-stats.html

Чтобы накопить достаточно данных, чтобы сделать надежные статистические выводы об игроках, одного года данных недостаточно. Необходимы данные, собранные за десятки лет, чтобы помочь отфильтровать шум и сделать вывод о будущем потенциале игрока.

Если действие можно повторить вручную, оно становится отличным кандидатом на автоматизацию. В этом случае номер в приведенном выше URL-адресе соответствует соответствующему году обучения в этом классе новичков. Вооружившись этими знаниями, давайте начнем собирать наши первые строки кода.

import requests
from bs4 import BeautifulSoup
import pandas as pd

years = list(range(1990, 2017))
url_base = "https://www.basketball-reference.com/leagues/NBA_{}_rookies-season-stats.html"

  1. Соответствующие пакеты импортируются
  2. Переменная списка yearsуказывает диапазон желаемых лет - с 1990 по 2017 год.
  3. url_baseслужит для хранения предварительно отформатированной строковой переменной целевого URL

При парсинге веб-страниц крайне важно устранить как можно больше накладных расходов. Поскольку сайт хранит всю свою информацию в интерфейсе HTML, страницу можно легко загрузить и полностью сохранить локально.

#loop iterates through years
for year in years:
    url = url_base.format(year)
    data = requests.get(url)
    #each page year is locally stored as an HTML file
    with open("notebooks/Rookies/{}.html".format(year), "w+") as f:
        f.write(data. Text)

  1. Фигурные скобки, найденные в urlстроке , позволят formatзаменить ее текущим итерируемым годом
    . Например, в первой итерации значение urlбудетhttps://www.basketball-reference.com/leagues/NBA_1990_rookies-season-stats.html
    — во второй итерации вместо этого будет указан следующий год:https://www.basketball-reference.com/leagues/NBA_1991_rookies-season-stats.html
  2. Переменная данных выступает в качестве заполнителя для requests.get()функции и ссылок текущего итерируемого urlстрокового значения.
  3. Затем метод requestsиспользует новую отформатированную строку URL для получения рассматриваемой страницы.
  4. Последующий with open()читает и записывает ( w+) данные страницы из нашего requests.get( data.text) и локально сохраняет вновь созданные файлы HTML.

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

Загружая эти HTML-файлы на локальный компьютер, вы избегаете двух вещей:

  1. Приходится ждать дольше, чем обычно, чтобы собрать те же данные
  2. Блокировка посещения страницы, полное прекращение сбора данных

Есть несколько преимуществ в извлечении данных из форматов HTML. Одним из наиболее важных является способ уникальной идентификации свойств элемента. Если их можно однозначно идентифицировать, их можно очистить с помощью Beautiful Soup. Но для этого нужно знать, как проверять сами эти HTML-элементы.

Как проверить

Нам нужно глубже погрузиться во внутреннюю работу этого документа, но я обещаю, что не буду превращать это в упражнение по изучению HTML.

Если вы знаете, как проверять объекты HTML, не стесняйтесь идти вперед. В противном случае следуйте инструкциям по проверке элементов страницы.

  1. Нажмите на три вертикальные точки в верхней строке меню Chrome.
  2. Выберите «Дополнительные инструменты»
  3. Выберите Инструменты разработчика.
  1. Щелкните правой кнопкой мыши веб-страницу
  2. Выберите «Проверить», чтобы получить доступ к панели инструментов разработчика.

Проверка страницы

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

with open("notebooks/Rookies/2000.html") as f:
    page = f.read()

Все необходимые данные этой страницы размещены в этом элементе таблицы. Прежде чем поспешно высасывать все эти данные как есть, сейчас самое время подумать, стоит ли собирать все в этой таблице.

Предварительная очистка

Предварительная очистка может быть нечастым словом в вашем лексиконе, но для тех из вас, кто регулярно очищает данные, это должно быть. Если вы хотите избежать разочарования из-за потраченных впустую часов работы над проектом по сбору данных, лучше всего сначала отделить плевелы от зерен.

Например, обратите внимание на три элемента, обведенных красным.

Одна строка служит «основным» заголовком таблицы. Две другие строки представляют собой повторяющиеся экземпляры тех же артефактов, что и вверху. Этот узор повторяется каждый 20-й ряд.

Дальнейшая проверка показывает, что все эти строки имеют один и тот же trHTML-тег (строка таблицы). Что отличает каждый из этих элементов от любых других, так это их имена классов.

  1. Основная строка заголовка
    — Класс =over_header
  2. Повторить строки заголовка
    — Класс =over_header thead
  3. Строка категории статистики
    — класс =thead
  4. #array to house list of dataframes
    dfs = []
    #unnecessary table rows to be removed
    classes = ["over_header", "over_header thead", "thead"]
    

  5. Объект массива classesбудет содержать все неизмененные имена классов элемента строки таблицы.

Метод decomposeслужит для удаления таких нежелательных элементов на странице. Согласно официальной документации Beautiful Soup ...

decompose()

Tag.decompose()удаляет тег из дерева, затем полностью уничтожает его и его содержимое.

Ниже приведен фрагмент кода, где мы смогли оптимизировать decomposeметод с помощью forциклов.

#for loop to iterate through the years 
for year in years:
    with open("notebooks/Rookies/{}.html".format(year)) as f:
        page = f.read()
    soup = BeautifulSoup(page, "html.parser")
    #for loop cleans up unnecessary table headers from reappearing in rows
    for i in classes:
        for tr in soup.find_all("tr", {"class":i}):
            tr.decompose()

  1. Этот withметод предоставляет нашему коду структуру переменной pageдля чтения локально сохраненных HTML-файлов при вызове.
  2. Класс синтаксического анализатора HTML инициализируется путем создания экземпляра класса Beautiful Soup и передачи как pageстрокового объекта, так и html.parser.
  3. Второй forцикл перебирает значения в classesмассиве
  4. В третьем forцикле используется метод Beautiful Soup find_allдля идентификации элементов, которые имеют как tr теги, так и имена классов, совпадающие с таковыми вclasses
  5. tr.decomposeслужит для полного исключения каждого из идентифицированных элементов строки таблицы со страницы

Извлечение данных

Наконец-то мы можем начать работать над той частью кода, которая фактически извлекает данные из таблицы. Помните, что таблица со всеми соответствующими данными имеет уникальный идентификатор HTML rookies. Следующие дополнения к нашему коду будут служить для разбора данных этой таблицы.

#the years we wish to parse for
years = list(range(1990, 2017))
#array to house list of dataframes
dfs = []
#unnecessary table headers to be removed
classes = ["over_header","over_header thead", "thead"]
 
for year in years:
    #url = url_base.format(year)
    #data = requests.get(url)
    ##page is saved as an html and placed in Rookies folder
    #with open("notebooks/Rookies/{}.html".format(year), "w+") as f:
        #f.write(data.text)
     #with open("notebooks/Rookies/2000.html") as f:
    with open("notebooks/Rookies/{}.html".format(year)) as f:
        page = f.read()
      soup = BeautifulSoup(page, "html.parser")   #for loop cleans up unnecessary table headers from reappearing in rows
    for i in classes:
        for tr in soup.find_all("tr", {"class":i}):
        tr.decompose()
        
### Scraping Block ###
    #identifies, scrapes, and loads rookie tables into one dataframe
    rookie_table = soup.find(id="rookies")
    rookies = pd.read_html(str(rookie_table))[0]
    rookies["Year"] = year
    dfs.append(rookies)

#new variable turns list of dataframes into single dataframe
all_rookies = pd.concat(dfs)

  1. rookie_tableсоздает экземпляр метода Beautiful Soup findдля идентификации HTML-элемента с идентификаторомrookies
  2. Видя, что Pandas может читать HTML-таблицы, таблица новобранцев загружается в Pandas с помощью метода read_html, передавая его rookie_tableв виде строки.
  3. Присоединение к концу [0]превращает rookiesсписок фреймов данных в один фрейм данных
  4. Столбец «Год» — это конец фрейма rookiesданных.
  5. dfs.append(rookies)служит для размещения всех таблиц каждого года новичка в том порядке, в котором они повторялись, в список фреймов данных
  6. Метод Pandas concatиспользуется для объединения списка фреймов данных в один фрейм данных:all_rookies

Наш последний шаг заключается в том, чтобы взять всю эту полезную, чистую информацию и заархивировать ее в удобном для человека и машиночитаемом формате CSV. Добавление этой строки в конец нашего кода будет полезно, когда вы решите вернуться и сослаться на собранные данные.

#a single dataframe archived as a local CSV using Pandas
all_rookies.to_csv("archive/NBA_Rookies_1990-2016.csv")

import requests
import pandas as pd
from bs4 import BeautifulSoup

years = list(range(1990, 2017))
dfs = []
classes = ["over_header","over_header thead", "thead"] #unnecessary table headers to be removed

for year in years:
    with open("C:/Users/cisco/OneDrive/python/Projects/Basketball/Rookies/{}.html".format(year), encoding = "utf-8") as f:
        page = f.read()
    soup = BeautifulSoup(page, "html.parser")
    #second for loop cleans up unnecessary table headers from reappearing in rows
    for i in classes:
        for tr in soup.find_all("tr", {"class":i}):
            tr.decompose()
    #identifies, scrapes, and loads rookie tables into one dataframe
    table_rookies = soup.find(id="rookies")
    rookies = pd.read_html(str(table_rookies))[0]
    rookies["Year"] = year
    dfs.append(rookies)

#new variable turns list of dataframes into single dataframe
all_rookies = pd.concat(dfs)

#dataframe archived as local CSV
all_rookies.to_csv("archive/NBA_Rookies_1990-2016.csv")

Опять же, процесс, описанный в этом пошаговом руководстве, несомненно, применим практически ко всем остальным страницам на Basketball-Reference.com . В каждом случае стоит предпринять пять простых шагов.

  1. Определите URL-адрес страницы
  2. Скачать страницу
  3. Определите элементы
  4. Предварительная очистка и извлечение
  5. Архив

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