Demaskowanie koszykówki za pomocą Pythona

Jan 24 2023
Część 1: Skrobanie z piękną zupą
W tej serii blogów potężne biblioteki Pythona są wykorzystywane do odkrywania ukrytych prawd statystycznych w koszykówce. Pierwszym krokiem w każdym podejściu opartym na danych jest zidentyfikowanie i zebranie potrzebnych danych.

W tej serii blogów potężne biblioteki Pythona są wykorzystywane do odkrywania ukrytych prawd statystycznych w koszykówce. Pierwszym krokiem w każdym podejściu opartym na danych jest zidentyfikowanie i zebranie potrzebnych danych.

Na szczęście dla nas Basketball-Reference.com zawiera strony z danymi koszykówki, które można łatwo zeskrobać. Procesy tego przewodnika można łatwo zastosować do dowolnej liczby ich stron, ale w tym przypadku planujemy zeskrobać sezonowe statystyki wielu klas nowicjuszy.

Cele

  1. Zidentyfikuj źródło danych
  2. Pobierz stronę
  3. Zidentyfikuj ważne elementy strony
  4. Wstępne oczyszczenie i ekstrakcja
  5. Archiwum
  • Pakiet żądań
  • Piękne opakowanie zupy
  • Pakiet Pandy

Identyfikacja źródła danych

Basketball-Reference.com udostępnia setki wyselekcjonowanych stron ze statystykami koszykówki, które obejmują sezonowe średnie typowych kategorii wyników, takich jak punkty, zbiórki i procenty rzutów, aż po akcję każdy mecz rozegrany w ciągu ostatnich 20 lub więcej lat. Można łatwo zgubić się w tym statystycznym tsunami, jeśli nie ma jasno określonego celu, czego dokładnie szukać.

Cel w tym poście jest prosty: zdobyć dane nowicjusza, które pomogą ocenić prawdziwą wartość i potencjał młodego gracza.

Poniższy link jest jedną z takich stron. Zawiera wszystkie istotne statystyki debiutantów w danym sezonie.

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

Aby zgromadzić wystarczającą ilość danych, aby wyciągnąć solidne wnioski statystyczne na temat graczy, jeden rok danych nie wystarczy. Potrzebne są dane gromadzone przez dziesiątki lat, aby pomóc przefiltrować szum i dojść do wniosku na temat przyszłego potencjału gracza.

Jeśli czynność można powtórzyć ręcznie, sama w sobie jest świetnym kandydatem do automatyzacji. W tym przypadku liczba w powyższym adresie URL odpowiada odpowiedniemu rocznikowi tej klasy debiutanta. Zasileni tą wiedzą, zacznijmy tworzyć nasze pierwsze wiersze kodu.

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. Odpowiednie pakiety są importowane
  2. Zmienna listy yearsokreśla zakres żądanych lat - od 1990 do 2017
  3. url_basesłuży do przechowywania wstępnie sformatowanej zmiennej łańcuchowej docelowego adresu URL

Podczas skrobania stron internetowych konieczne jest usunięcie jak największej ilości kosztów ogólnych. Ponieważ witryna przechowuje wszystkie informacje w interfejsie HTML, stronę można łatwo pobrać i przechowywać lokalnie w całości.

#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. Nawiasy klamrowe znalezione w urlłańcuchu 's posłużą do formatzastąpienia go aktualnie iterowanym rokiem
    — na przykład w pierwszej iteracji wartość urlbędzie wynosićhttps://www.basketball-reference.com/leagues/NBA_1990_rookies-season-stats.html
    — W drugiej iteracji odniesienie do kolejnego roku byłoby zamiast tego:https://www.basketball-reference.com/leagues/NBA_1991_rookies-season-stats.html
  2. Zmienna danych działa jako symbol zastępczy dla requests.get()funkcji i odwołań do aktualnie iterowanej urlwartości ciągu
  3. Następnie metoda requestswykorzystuje nowo sformatowany ciąg adresu URL do pobrania danej strony
  4. Kolejne with open()odczytuje i zapisuje ( w+) dane strony z naszej requests.get( data.text), a także lokalnie przechowuje nowo utworzone pliki HTML

Aby uniknąć powszechnego rosnącego problemu ze skrobaniem witryn, przechowujemy te strony jako lokalne pliki HTML. Widzisz, kiedy odwiedzasz witrynę strony, serwer hostujący tę stronę honoruje twoje żądanie i odsyła odpowiednie dane do twojej przeglądarki. Ale gdy jeden konkretny klient prosi o te same informacje w kółko, nadmiernie obciąża serwer. Administrator serwera ma pełne prawo blokować te uporczywe żądania, aby móc optymalnie świadczyć tę usługę innym online.

Pobierając te pliki HTML na komputer lokalny, unikasz dwóch rzeczy:

  1. Konieczność oczekiwania dłużej niż zwykle na zebranie tych samych danych
  2. Zablokowanie możliwości odwiedzania strony, całkowite wstrzymanie gromadzenia danych

Skrobanie danych z formatów HTML ma kilka zalet. Jednym z najważniejszych jest to, w jaki sposób można jednoznacznie zidentyfikować właściwości elementu. Jeśli można je jednoznacznie zidentyfikować, można je zeskrobać za pomocą Pięknej Zupy. Ale żeby to zrobić, trzeba wiedzieć, jak sprawdzić same te elementy HTML.

Jak sprawdzić

Będziemy musieli zagłębić się w wewnętrzne działanie tego dokumentu, ale obiecuję, że nie zrobię z tego ćwiczenia dotyczącego nauki języka HTML.

Jeśli wiesz, jak sprawdzać obiekty HTML, nie krępuj się. W przeciwnym razie postępuj zgodnie z instrukcjami sprawdzania elementów strony.

  1. Kliknij trzy pionowe kropki na górnym pasku menu Chrome
  2. Wybierz „Więcej narzędzi”
  3. Wybierz Narzędzia dla programistów.
  1. Kliknij prawym przyciskiem myszy na stronie internetowej
  2. Wybierz „Sprawdź”, aby uzyskać dostęp do panelu narzędzi programistycznych

Sprawdzanie strony

Widząc, że wszystkie te strony są przechowywane lokalnie, możemy je wyświetlić, przechodząc do systemu plików, aby otworzyć je w wybranej przeglądarce, lub możemy kontynuować budowanie naszego kodu, implementując następujący fragment kodu.

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

Wszystkie pożądane dane tej strony są przechowywane w tym elemencie tabeli. Zanim pospiesznie wyssiesz wszystkie te dane, teraz jest najlepszy moment, aby zastanowić się, czy wszystko na tej tabeli jest warte zebrania.

Czyszczenie wstępne

Czyszczenie wstępne może nie być częstym słowem w twoim słowniku, ale dla tych z was, którzy regularnie zgarniają dane, powinno nim być. Jeśli chcesz uniknąć frustracji spowodowanej zmarnowanymi godzinami pracy nad projektem gromadzenia danych, najlepiej najpierw oddzielić plewy od ziarna.

Na przykład zwróć uwagę na trzy elementy zaznaczone na czerwono.

Jeden wiersz służy jako „główny” nagłówek tabeli. Pozostałe dwa wiersze to zduplikowane instancje tych samych artefaktów, które znajdują się u góry. Ten wzór powtarza się co 20 rząd.

Dalsza inspekcja ujawnia, że ​​wszystkie te wiersze mają ten sam trtag HTML (wiersz tabeli). Tym, co odróżnia każdy z tych elementów od innych, są ich nazwy klas.

  1. Główny wiersz nagłówka
    — Klasa =over_header
  2. Powtórz wiersze nagłówka
    — Klasa =over_header thead
  3. Wiersz kategorii statystyk
    — klasa =thead
  4. #array to house list of dataframes
    dfs = []
    #unnecessary table rows to be removed
    classes = ["over_header", "over_header thead", "thead"]
    

  5. Obiekt classesarray będzie zawierał wszystkie nazwy klas elementów wierszy tabeli, których nie ma.

Metoda decomposesłuży do usuwania takich niechcianych elementów ze strony. Zgodnie z oficjalną dokumentacją Pięknej Zupy ...

decompose()

Tag.decompose()usuwa tag z drzewa, a następnie całkowicie niszczy go i jego zawartość.

Poniżej znajduje się fragment kodu, w którym udało nam się zoptymalizować decomposemetodę za pomocą forpętli.

#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. Metoda withzapewnia naszemu kodowi strukturę zmiennej, pagektóra po wywołaniu odczytuje lokalnie przechowywane pliki HTML
  2. Klasa parsera HTML jest inicjowana przez utworzenie instancji klasy Beautiful Soup i przekazanie zarówno pageobiektu string, jak i html.parser.
  3. Druga forpętla iteruje po wartościach w classestablicy
  4. Trzecia forpętla wykorzystuje metodę Beautiful Soup find_alldo identyfikowania elementów, które mają zarówno tr tagi, jak i nazwy klas pasujące do tych wclasses
  5. tr.decomposesłuży do całkowitego pominięcia każdego ze zidentyfikowanych elementów wiersza tabeli na stronie

Wyodrębnianie danych

Możemy wreszcie zacząć pracować nad częścią kodu, która faktycznie pobiera dane z tabeli. Pamiętaj, że tabela ze wszystkimi istotnymi danymi ma unikalny identyfikator HTML rookies. Następujące dodatki do naszego kodu będą służyć do analizowania danych z tej tabeli.

#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_tabletworzy instancję metody Beautiful Soup findw celu identyfikacji elementu HTML za pomocą identyfikatorarookies
  2. Widząc, że Pandy mogą czytać tabele HTML, tabela nowicjusza jest ładowana do Pand przy użyciu read_htmlmetody, przekazując do niej rookie_tablejako ciąg znaków
  3. Sczepianie do końca [0]zmienia rookieslistę ramek danych w pojedynczą ramkę danych
  4. Kolumna „Rok” to koniec ramki rookiesdanych
  5. dfs.append(rookies)służy do przechowywania wszystkich tabel z każdego debiutanckiego roku w kolejności, w jakiej zostały iterowane na liście ramek danych
  6. Metoda Pandas concatsłuży do łączenia listy ramek danych w jedną ramkę danych:all_rookies

Nasz ostatni krok polega na pobraniu wszystkich tych użytecznych, czystych informacji i zarchiwizowaniu ich w formacie CSV łatwym do odczytu przez ludzi i maszyny. Dołączenie tej linii do końca naszego kodu będzie przydatne przy podejmowaniu decyzji o powrocie i odwołaniu się do zebranych danych.

#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")

Ponownie, proces przedstawiony w tym przewodniku bez wątpienia będzie miał zastosowanie do prawie każdej innej strony w Basketball-Reference.com . W każdym przypadku warto wykonać pięć prostych kroków.

  1. Zidentyfikuj adres URL strony
  2. Pobierz stronę
  3. Zidentyfikuj elementy
  4. Wstępne oczyszczenie i ekstrakcja
  5. Archiwum

Następnym krokiem w tej serii będzie rzeczywiste wykorzystanie tych danych do uzyskania wglądu w potencjał przyszłych graczy. Więc wypatrujcie przyszłych rat!