Demaskowanie koszykówki za pomocą Pythona
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
- Zidentyfikuj źródło danych
- Pobierz stronę
- Zidentyfikuj ważne elementy strony
- Wstępne oczyszczenie i ekstrakcja
- 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"
- Odpowiednie pakiety są importowane
- Zmienna listy
yearsokreśla zakres żądanych lat - od 1990 do 2017 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)
- Nawiasy klamrowe znalezione w
urlłańcuchu 's posłużą doformatzastą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 - Zmienna danych działa jako symbol zastępczy dla
requests.get()funkcji i odwołań do aktualnie iterowanejurlwartości ciągu - Następnie metoda
requestswykorzystuje nowo sformatowany ciąg adresu URL do pobrania danej strony - Kolejne
with open()odczytuje i zapisuje (w+) dane strony z naszejrequests.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:
- Konieczność oczekiwania dłużej niż zwykle na zebranie tych samych danych
- 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.
- Kliknij trzy pionowe kropki na górnym pasku menu Chrome
- Wybierz „Więcej narzędzi”
- Wybierz Narzędzia dla programistów.
- Kliknij prawym przyciskiem myszy na stronie internetowej
- 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.
- Główny wiersz nagłówka
— Klasa =over_header - Powtórz wiersze nagłówka
— Klasa =over_header thead - Wiersz kategorii statystyk
— klasa =thead - Obiekt
classesarray będzie zawierał wszystkie nazwy klas elementów wierszy tabeli, których nie ma.
#array to house list of dataframes
dfs = []
#unnecessary table rows to be removed
classes = ["over_header", "over_header thead", "thead"]
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()
- Metoda
withzapewnia naszemu kodowi strukturę zmiennej,pagektóra po wywołaniu odczytuje lokalnie przechowywane pliki HTML - Klasa parsera HTML jest inicjowana przez utworzenie instancji klasy Beautiful Soup i przekazanie zarówno
pageobiektu string, jak ihtml.parser. - Druga
forpętla iteruje po wartościach wclassestablicy - Trzecia
forpętla wykorzystuje metodę Beautiful Soupfind_alldo identyfikowania elementów, które mają zarównotrtagi, jak i nazwy klas pasujące do tych wclasses 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)
rookie_tabletworzy instancję metody Beautiful Soupfindw celu identyfikacji elementu HTML za pomocą identyfikatorarookies- Widząc, że Pandy mogą czytać tabele HTML, tabela nowicjusza jest ładowana do Pand przy użyciu
read_htmlmetody, przekazując do niejrookie_tablejako ciąg znaków - Sczepianie do końca
[0]zmieniarookieslistę ramek danych w pojedynczą ramkę danych - Kolumna „Rok” to koniec ramki
rookiesdanych 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- 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.
- Zidentyfikuj adres URL strony
- Pobierz stronę
- Zidentyfikuj elementy
- Wstępne oczyszczenie i ekstrakcja
- 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!

![Czym w ogóle jest lista połączona? [Część 1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)



































