Smascherare il basket usando Python

Jan 24 2023
Parte 1: raschiare con una bella zuppa
In questa serie di blog, vengono sfruttate potenti librerie Python per aiutare a scoprire alcune verità statistiche nascoste nel basket. Il primo passo in qualsiasi approccio basato sui dati è identificare e raccogliere i dati necessari.

In questa serie di blog, vengono sfruttate potenti librerie Python per aiutare a scoprire alcune verità statistiche nascoste nel basket. Il primo passo in qualsiasi approccio basato sui dati è identificare e raccogliere i dati necessari.

Fortunatamente per noi, Basketball-Reference.com ospita pagine di dati sul basket che possono essere facilmente recuperate. I processi di questa procedura dettagliata possono essere facilmente applicati a qualsiasi numero delle loro pagine, ma per questo caso, abbiamo in programma di raschiare le statistiche stagionali di più classi principianti.

Gli obiettivi

  1. Identificare l'origine dati
  2. Scarica la Pagina
  3. Identifica gli elementi importanti della pagina
  4. Pre-pulire ed estrarre
  5. Archivio
  • Pacchetto richieste
  • Bellissimo pacchetto di zuppe
  • Pacchetto Panda

Identificare l'origine dei dati

Basketball-Reference.com ospita centinaia di pagine curate sulle statistiche del basket che vanno dalle medie stagionali di categorie tipiche di box score come punti, rimbalzi e percentuali di tiro, fino all'azione play-by-play di ogni partita giocata nell'ultima 20 anni circa. Si può facilmente perdere la strada in questo tsunami statistico se non c'è un obiettivo chiaro su cosa cercare esattamente.

L'obiettivo qui in questo post è semplice: ottenere dati sui principianti che aiuteranno a valutare il vero valore e il potenziale di un giovane giocatore.

Il seguente link è una di queste pagine. Elenca tutte le statistiche rilevanti dei debuttanti in una particolare stagione.

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

Per accumulare dati sufficienti per fare solide deduzioni statistiche sui giocatori, un anno di dati non basta. Sono necessari dozzine di anni di dati raccolti per aiutare a filtrare il rumore e giungere a una conclusione sul potenziale futuro di un giocatore.

Se un'azione può essere ripetuta manualmente, si rende un ottimo candidato per l'automazione. In questo caso, il numero nell'URL sopra corrisponde al rispettivo anno di quella classe principiante. Forti di questa conoscenza, iniziamo a mettere insieme le nostre prime righe di codice.

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. I pacchetti appropriati vengono importati
  2. La yearsvariabile elenco specifica l'intervallo degli anni desiderati, dal 1990 al 2017
  3. url_baseserve a memorizzare la variabile stringa preformattata dell'URL di destinazione

Nello scraping delle pagine web, è imperativo rimuovere quanto più sovraccarico possibile. Visto che il sito memorizza tutte le loro informazioni sul front-end HTML, la pagina può essere facilmente scaricata e archiviata localmente nella sua interezza.

#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. Le parentesi graffe trovate all'interno della urlstringa di serviranno per consentire a formatdi sostituirlo con l'anno correntemente iterato
    — Ad esempio, nella sua prima iterazione, il urlvalore saràhttps://www.basketball-reference.com/leagues/NBA_1990_rookies-season-stats.html
    - Alla sua seconda iterazione, l'anno successivo verrebbe invece indicato:https://www.basketball-reference.com/leagues/NBA_1991_rookies-season-stats.html
  2. La variabile di dati funge da segnaposto per la requests.get()funzione e i riferimenti del urlvalore di stringa attualmente iterato
  3. Il requestsmetodo utilizza quindi la stringa URL appena formattata per recuperare la pagina in questione
  4. Il successivo with open()legge e scrive ( w+) i dati della pagina dal nostro requests.get( data.text) e memorizza localmente i file HTML appena creati

Per evitare un problema crescente comune nello scraping del sito, memorizziamo queste pagine come file HTML locali. Vedi, quando visiti una pagina del sito, il server che ospita detta pagina onora la tua richiesta e invia i dati appropriati al tuo browser. Ma avere un client specifico che richiede ripetutamente le stesse informazioni mette a dura prova il server. L'amministratore del server ha tutti i diritti di bloccare queste richieste persistenti per poter fornire in modo ottimale questo servizio ad altri online.

Scaricando questi file HTML sul tuo computer locale, eviti due cose:

  1. Dover aspettare più del solito per raccogliere gli stessi dati
  2. Essere bloccati dalla visita della pagina, interrompendo del tutto la raccolta dei dati

Ci sono alcuni vantaggi nello scraping dei dati dai formati HTML. Uno dei più importanti è il modo in cui le proprietà degli elementi possono essere identificate in modo univoco. Se possono essere identificati in modo univoco, possono essere raschiati usando Beautiful Soup. Ma per farlo, bisogna sapere come ispezionare questi stessi elementi HTML.

Come ispezionare

Avremo bisogno di approfondire il funzionamento interno di questo documento, ma prometto che non ne farò un esercizio sull'apprendimento dell'HTML.

Se sai come ispezionare oggetti HTML, sentiti libero di fare un salto avanti. Altrimenti, segui come ispezionare gli elementi della pagina.

  1. Fai clic sui tre punti verticali nella barra dei menu in alto di Chrome
  2. Scegli "Altri strumenti"
  3. Seleziona Strumenti per sviluppatori.
  1. Fare clic con il tasto destro sulla pagina web
  2. Scegli "Ispeziona" per accedere al pannello degli strumenti per sviluppatori

Ispezione della pagina

Visto che tutte queste pagine sono memorizzate localmente, possiamo scegliere di visualizzarle entrando nel file system per aprirle nel nostro browser desiderato, oppure possiamo continuare a costruire il nostro codice implementando il seguente frammento di codice.

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

Tutti i dati desiderati di questa pagina sono contenuti in quell'elemento della tabella. Prima di risucchiare frettolosamente tutti questi dati così come sono, ora è il momento migliore per considerare se vale la pena raccogliere tutto su questo tavolo.

Pre-pulizia

Pre-pulizia potrebbe non essere una parola frequente nel tuo vocabolario, ma per quelli di voi che si vedono raschiare dati su base regolare, dovrebbe esserlo. Se vuoi evitare la frustrazione di sprecare ore di progresso in un progetto di raccolta dati, è meglio separare prima la pula dal grano.

Ad esempio, prendi nota dei tre elementi riquadrati in rosso.

Una riga funge da intestazione della tabella "principale". Le altre due righe sono istanze duplicate degli stessi artefatti trovati in alto. Questo schema si ripete ogni 20 righe.

Un'ulteriore ispezione rivela che tutte queste righe hanno lo stesso trtag HTML (riga della tabella). Ciò che distingue ciascuno di questi elementi da tutti gli altri sono i nomi delle loro classi.

  1. Riga di intestazione principale
    — Classe =over_header
  2. Ripeti righe di intestazione
    — Classe =over_header thead
  3. Riga della categoria statistica
    — Classe =thead
  4. #array to house list of dataframes
    dfs = []
    #unnecessary table rows to be removed
    classes = ["over_header", "over_header thead", "thead"]
    

  5. L' classesoggetto array conterrà tutti i nomi di classe dell'elemento di riga della tabella unwarted.

Il decomposemetodo serve a rimuovere tali elementi indesiderati in una pagina. Secondo la documentazione ufficiale di Beautiful Soup ...

decompose()

Tag.decompose()rimuove un tag dall'albero, quindi lo distrugge completamente e il suo contenuto.

Di seguito è riportato uno snippet di codice in cui siamo stati in grado di ottimizzare il decomposemetodo con l'aiuto dei forloop.

#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. Il withmetodo fornisce al nostro codice la struttura per la pagevariabile per leggere i file HTML memorizzati localmente quando viene richiamata
  2. Una classe parser HTML viene inizializzata creando un'istanza della classe Beautiful Soup e passando sia l' pageoggetto stringa che html.parser.
  3. Il secondo forciclo scorre i valori classesnell'array
  4. Il terzo forciclo utilizza il metodo di Beautiful Soup find_allper identificare gli elementi che hanno sia tr tag che nomi di classe corrispondenti a quelli inclasses
  5. tr.decomposeserve per omettere interamente dalla pagina ciascuno degli elementi della riga della tabella identificati

Estrazione dei dati

Possiamo finalmente iniziare a lavorare sulla parte del codice che effettivamente estrae i dati dalla tabella. Ricorda che la tabella con tutti i dati rilevanti ha l'ID univoco HTML rookies. Le seguenti aggiunte al nostro codice serviranno per analizzare i dati di questa tabella.

#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_tableistanzia il metodo di Beautiful Soup findper identificare l'elemento HTML con IDrookies
  2. Vedendo che Pandas può leggere le tabelle HTML, la tabella rookie viene caricata in Pandas utilizzando il read_htmlmetodo, passandovi rookie_tablecome stringa
  3. L'aggiunta alla fine [0]trasforma rookiesda un elenco di frame di dati in un singolo frame di dati
  4. Una colonna "Anno" è la fine del rookiesframe di dati
  5. dfs.append(rookies)serve per ospitare tutte le tabelle di ogni anno da principiante nell'ordine in cui sono state iterate in un elenco di frame di dati
  6. Il metodo Pandas concatviene utilizzato per combinare l'elenco dei frame di dati in un unico frame di dati:all_rookies

Il nostro passaggio finale consiste nel prendere tutte queste informazioni utili e pulite e archiviarle in un formato CSV facilmente leggibile dall'uomo e dalla macchina. L'aggiunta di questa riga alla fine del nostro codice servirà per essere utile quando si decide di tornare indietro e fare riferimento ai dati raccolti.

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

Ancora una volta, il processo seguito in questa procedura dettagliata si applicherà senza dubbio a quasi tutte le altre pagine su Basketball-Reference.com . Ci sono cinque semplici passaggi che vale la pena seguire in ogni caso.

  1. Identifica l'URL della pagina
  2. Scarica la Pagina
  3. Identifica gli elementi
  4. Pre-pulire ed estrarre
  5. Archivio

Il prossimo capitolo di questa serie utilizzerà effettivamente questi dati per ottenere informazioni sul potenziale del futuro giocatore. Quindi stai attento alle puntate future!