Basketball mit Python entlarven

Jan 24 2023
Teil 1: Schaben mit Beautiful Soup
In dieser Blogreihe werden leistungsstarke Python-Bibliotheken genutzt, um einige verborgene statistische Wahrheiten im Basketball aufzudecken. Der erste Schritt bei jedem datengesteuerten Ansatz besteht darin, die benötigten Daten zu identifizieren und zu sammeln.

In dieser Blogreihe werden leistungsstarke Python-Bibliotheken genutzt, um einige verborgene statistische Wahrheiten im Basketball aufzudecken. Der erste Schritt bei jedem datengesteuerten Ansatz besteht darin, die benötigten Daten zu identifizieren und zu sammeln.

Zum Glück für uns hostet Basketball-Reference.com Seiten mit Basketballdaten, die leicht gelöscht werden können. Die Prozesse dieser exemplarischen Vorgehensweise können problemlos auf eine beliebige Anzahl ihrer Seiten angewendet werden. In diesem Fall planen wir jedoch, saisonale Statistiken mehrerer Rookie-Klassen zu extrahieren.

Die Ziele

  1. Identifizieren Sie die Datenquelle
  2. Laden Sie die Seite herunter
  3. Identifizieren Sie wichtige Seitenelemente
  4. Vorreinigen und Extrahieren
  5. Archiv
  • Paket anfordern
  • Wunderschönes Suppenpaket
  • Pandas-Paket

Identifizieren der Datenquelle

Basketball-Reference.com beherbergt Hunderte von kuratierten Seiten zu Basketballstatistiken, die von saisonalen Durchschnittswerten typischer Box-Score-Kategorien wie Punkte, Rebounds und Schussquoten bis hin zur Play-by-Play-Aktion jedes im letzten Spiel gespielten Spiels reichen 20 oder so Jahre. Man kann sich in diesem statistischen Tsunami leicht verirren, wenn man sich nicht ein klares Ziel gesetzt hat, worauf genau man achten muss.

Das Ziel hier in diesem Beitrag ist einfach: Rookie-Daten zu sammeln, die dabei helfen, den wahren Wert und das Potenzial eines jungen Spielers einzuschätzen.

Der folgende Link ist eine solche Seite. Es listet alle relevanten Statistiken der Rookies einer bestimmten Saison auf.

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

Um genügend Daten zu sammeln, um solide statistische Rückschlüsse auf Spieler zu ziehen, reicht ein Jahr an Daten nicht aus. Es müssen Daten aus Dutzenden von Jahren gesammelt werden, um das Rauschen zu filtern und zu einer Schlussfolgerung über das zukünftige Potenzial eines Spielers zu gelangen.

Wenn eine Aktion manuell wiederholt werden kann, ist sie ein guter Kandidat für die Automatisierung. In diesem Fall entspricht die Zahl in der URL oben dem jeweiligen Jahr dieser Rookie-Klasse. Mit diesem Wissen beginnen wir mit der Zusammenstellung unserer ersten Codezeilen.

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. Die entsprechenden Pakete werden importiert
  2. Die yearsListenvariable gibt den Bereich der gewünschten Jahre an – 1990 bis 2017
  3. url_basedient zur Speicherung der vorformatierten String-Variable der Ziel-URL

Beim Scraping von Webseiten ist es unbedingt erforderlich, so viel Overhead wie möglich zu entfernen. Da die Website alle Informationen im HTML-Frontend speichert, kann die Seite problemlos heruntergeladen und vollständig lokal gespeichert werden.

#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. Die geschweiften Klammern in der urlZeichenfolge dienen dazu, formatsie durch das aktuell iterierte Jahr zu ersetzen . In der ersten Iteration lautet
    der Wert beispielsweiseurlhttps://www.basketball-reference.com/leagues/NBA_1990_rookies-season-stats.html
    – Bei der zweiten Iteration würde stattdessen auf das Folgejahr verwiesen:https://www.basketball-reference.com/leagues/NBA_1991_rookies-season-stats.html
  2. Die Datenvariable fungiert als Platzhalter für die requests.get()Funktion und Referenzen des aktuell iterierten urlZeichenfolgewerts
  3. Die requestsMethode verwendet dann die neu formatierte URL-Zeichenfolge, um die betreffende Seite abzurufen
  4. Der nachfolgende with open()liest und schreibt ( w+) die Seitendaten aus unserem requests.get( data.text) und speichert die neu erstellten HTML-Dateien lokal

Um die beim Site-Scraping üblichen Wachstumsprobleme zu vermeiden, speichern wir diese Seiten als lokale HTML-Dateien. Wenn Sie eine Seite besuchen, berücksichtigt der Server, der diese Seite hostet, Ihre Anfrage und sendet die entsprechenden Daten an Ihren Browser zurück. Wenn jedoch ein bestimmter Client immer wieder nach denselben Informationen fragt, wird der Server übermäßig belastet. Der Serveradministrator hat durchaus das Recht, diese dauerhaften Anfragen zu blockieren, um diesen Dienst anderen online optimal zur Verfügung stellen zu können.

Durch das Herunterladen dieser HTML-Dateien auf Ihren lokalen Computer vermeiden Sie zwei Dinge:

  1. Man muss länger als gewöhnlich warten, um die gleichen Daten zu sammeln
  2. Der Besuch der Seite wird gesperrt, wodurch die Datenerfassung vollständig gestoppt wird

Das Scrapen von Daten aus HTML-Formaten bietet einige Vorteile. Eine der wichtigsten ist, wie Elementeigenschaften eindeutig identifiziert werden können. Wenn sie eindeutig identifiziert werden können, können sie mit Beautiful Soup abgekratzt werden. Dazu muss man jedoch wissen, wie man diese HTML-Elemente selbst überprüft.

So prüfen Sie

Wir müssen tiefer in die Funktionsweise dieses Dokuments eintauchen, aber ich verspreche, dass ich daraus keine Übung zum Erlernen von HTML machen werde.

Wenn Sie wissen, wie man HTML-Objekte überprüft, können Sie gerne weitermachen. Andernfalls befolgen Sie bitte die Anweisungen zum Überprüfen von Seitenelementen.

  1. Klicken Sie auf die drei vertikalen Punkte in der oberen Menüleiste von Chrome
  2. Wählen Sie „Weitere Tools“
  3. Wählen Sie Entwicklertools aus.
  1. Klicken Sie mit der rechten Maustaste auf die Webseite
  2. Wählen Sie „Inspizieren“, um auf das Bedienfeld „Entwicklertools“ zuzugreifen

Überprüfen der Seite

Da alle diese Seiten lokal gespeichert sind, können wir sie anzeigen, indem wir entweder in das Dateisystem gehen, um sie in unserem gewünschten Browser zu öffnen, oder wir können mit der Erstellung unseres Codes fortfahren, indem wir den folgenden Codeausschnitt implementieren.

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

In diesem Tabellenelement sind alle gewünschten Daten dieser Seite untergebracht. Bevor Sie alle diese Daten hastig aufsaugen, sollten Sie jetzt überlegen, ob es sich lohnt, alles in dieser Tabelle zu sammeln.

Vorreinigung

„Vorbereinigung“ ist vielleicht kein häufig vorkommendes Wort in Ihrem Wortschatz, aber für diejenigen unter Ihnen, die regelmäßig Daten durchsuchen, sollte es so sein. Wenn Sie den Frust über verschwendete Stunden an Fortschritten bei einem Datenerfassungsprojekt vermeiden möchten, trennen Sie am besten zunächst die Spreu vom Weizen.

Beachten Sie beispielsweise die drei rot umrandeten Elemente.

Eine Zeile dient als „Haupt“-Tabellenkopf. Die anderen beiden Zeilen sind Duplikate derselben Artefakte, die oben zu finden sind. Dieses Muster wiederholt sich jede 20. Reihe.

Eine weitere Untersuchung zeigt, dass alle diese Zeilen das gleiche tr(Tabellenzeilen-)HTML-Tag haben. Was jedes dieser Elemente von allen anderen unterscheidet, sind ihre Klassennamen.

  1. Hauptkopfzeile
    – Klasse =over_header
  2. Kopfzeilen wiederholen
    – Klasse =over_header thead
  3. Statistikkategoriezeile
    – Klasse =thead
  4. #array to house list of dataframes
    dfs = []
    #unnecessary table rows to be removed
    classes = ["over_header", "over_header thead", "thead"]
    

  5. Das classesArray-Objekt enthält alle Klassennamen des unwarteten Tabellenzeilenelements.

Die decomposeMethode dient dazu, solche unerwünschten Elemente auf einer Seite zu entfernen. Gemäß der offiziellen Beautiful Soup-Dokumentation ...

decompose()

Tag.decompose()Entfernt ein Tag aus dem Baum und zerstört es und seinen Inhalt anschließend vollständig.

Nachfolgend finden Sie einen Codeausschnitt, in dem wir die decomposeMethode mithilfe von forSchleifen optimieren konnten.

#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. Die withMethode stellt unserem Code die Struktur für die pageVariable bereit, um bei Aufruf lokal gespeicherte HTML-Dateien zu lesen
  2. pageEine HTML-Parser-Klasse wird initialisiert, indem die Beautiful Soup-Klasse instanziiert und sowohl das String-Objekt als auch übergeben wird html.parser.
  3. Die zweite forSchleife durchläuft die Werte im classesArray
  4. Die dritte forSchleife verwendet die Methode von Beautiful Soup, find_allum Elemente zu identifizieren, deren Tags tr und Klassennamen mit denen in übereinstimmenclasses
  5. tr.decomposedient dazu, jedes der identifizierten Tabellenzeilenelemente vollständig von der Seite wegzulassen

Extrahieren der Daten

Endlich können wir mit der Arbeit an dem Teil des Codes beginnen, der tatsächlich Daten aus der Tabelle extrahiert. Denken Sie daran, dass die Tabelle mit allen relevanten Daten die eindeutige HTML-ID hat rookies. Die folgenden Ergänzungen zu unserem Code dienen dazu, die Daten dieser Tabelle zu analysieren.

#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_tableInstanziiert die Methode von Beautiful Soup find, um das HTML-Element mit der ID zu identifizierenrookies
  2. Da Pandas HTML-Tabellen lesen kann, wird die Rookie-Tabelle mithilfe der Methode in Pandas geladen und als String read_htmlübergebenrookie_table
  3. Wenn man bis zum Ende anhängt, [0]wird rookiesaus einer Liste von Datenrahmen ein einzelner Datenrahmen
  4. Eine Spalte „Jahr“ bildet das Ende des rookiesDatenrahmens
  5. dfs.append(rookies)dient dazu, alle Tabellen jedes Rookie-Jahres in der Reihenfolge unterzubringen, in der sie in einer Liste von Datenrahmen iteriert wurden
  6. Die Pandas-Methode concatwird verwendet, um die Liste der Datenrahmen in einem einzigen Datenrahmen zusammenzufassen:all_rookies

Unser letzter Schritt besteht darin, all diese nützlichen, sauberen Informationen zu erfassen und im leicht für Menschen und Maschinen lesbaren CSV-Format zu archivieren. Wenn Sie diese Zeile an das Ende unseres Codes anhängen, können Sie sich entscheiden, noch einmal auf die gesammelten Daten zu verweisen.

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

Auch hier gilt: Der in dieser exemplarischen Vorgehensweise befolgte Prozess gilt zweifellos für die meisten anderen Seiten auf Basketball-Reference.com . In jedem Fall lohnen sich fünf einfache Schritte.

  1. Identifizieren Sie die Seiten-URL
  2. Laden Sie die Seite herunter
  3. Identifizieren Sie die Elemente
  4. Vorreinigen und Extrahieren
  5. Archiv

Als nächstes in dieser Serie werden wir diese Daten tatsächlich nutzen, um Einblicke in das zukünftige Spielerpotenzial zu gewinnen. Halten Sie also Ausschau nach zukünftigen Folgen!