Démasquer le basket-ball à l'aide de Python
Dans cette série de blogs, de puissantes bibliothèques Python sont exploitées pour aider à découvrir certaines vérités statistiques cachées dans le basket-ball. La première étape de toute approche axée sur les données consiste à identifier et à collecter les données nécessaires.
Heureusement pour nous, Basketball-Reference.com héberge des pages de données de basket-ball qui peuvent être facilement récupérées. Les processus de cette procédure pas à pas peuvent être facilement appliqués à n'importe quel nombre de leurs pages, mais pour ce cas, nous prévoyons de supprimer les statistiques saisonnières de plusieurs classes de recrues.
Les objectifs
- Identifier la source de données
- Télécharger la page
- Identifier les éléments de page importants
- Pré-nettoyage et extraction
- Archive
- Paquet de demandes
- Beau paquet de soupe
- Forfait Pandas
Identification de la source de données
Basketball-Reference.com héberge des centaines de pages organisées sur les statistiques de basket-ball qui vont des moyennes saisonnières des catégories de scores de boîte typiques comme les points, les rebonds et les pourcentages de tir, jusqu'à l'action play-by-play de chaque jeu joué dans le dernier 20 ans ou plus. On peut facilement se perdre dans ce tsunami statistique s'il n'y a pas d'objectif clair sur ce qu'il faut rechercher exactement.
L'objectif ici dans cet article est simple : obtenir des données sur les recrues qui aideront à évaluer la véritable valeur et le potentiel d'un jeune joueur.
Le lien suivant est une de ces pages. Il répertorie toutes les statistiques pertinentes des recrues d'une saison particulière.
https://www.basketball-reference.com/leagues/NBA_1990_rookies-season-stats.html
Afin d'accumuler suffisamment de données pour faire des inférences statistiques solides sur les joueurs, une année de données ne suffira pas. Il doit y avoir des dizaines d'années de données collectées pour aider à filtrer le bruit et à tirer une conclusion sur le potentiel futur d'un joueur.
Si une action peut être répétée manuellement, elle devient un excellent candidat pour l'automatisation. Dans ce cas, le numéro dans l'URL ci-dessus correspond à l'année respective de cette classe de recrue. Forts de ces connaissances, commençons à assembler nos premières lignes de code.
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"
- Les packages appropriés sont importés
- La
yearsvariable de liste spécifie la plage des années souhaitées - 1990 à 2017 url_basesert à stocker la variable de chaîne pré-formatée de l'URL cible
Lors du grattage de pages Web, il est impératif de supprimer autant de frais généraux que possible. Étant donné que le site stocke toutes ses informations sur le frontal HTML, la page peut être facilement téléchargée et stockée localement dans son intégralité.
#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)
- Les accolades trouvées dans la
urlchaîne de serviront à permettre à laformatde la remplacer par l'année actuellement itérée
— Par exemple, dans sa première itération, laurlvaleur serahttps://www.basketball-reference.com/leagues/NBA_1990_rookies-season-stats.html
— Lors de sa deuxième itération, l'année suivante serait référencée à la place :https://www.basketball-reference.com/leagues/NBA_1991_rookies-season-stats.html - La variable de données agit comme un espace réservé pour la
requests.get()fonction et les références de laurlvaleur de chaîne actuellement itérée - La
requestsméthode utilise ensuite la chaîne d'URL nouvellement formatée pour récupérer la page en question - La suite
with open()lit et écrit (w+) les données de la page à partir de notrerequests.get(data.text), et stocke localement les fichiers HTML nouvellement créés
Pour éviter une douleur croissante commune dans le grattage de site, nous stockons ces pages sous forme de fichiers HTML locaux. Voyez, lors d'une visite sur un site de page, le serveur hébergeant ladite page honore votre demande et renvoie les données appropriées à votre navigateur. Mais le fait qu'un client spécifique demande sans cesse les mêmes informations exerce une pression excessive sur le serveur. L'administrateur du serveur a tout à fait le droit de bloquer ces demandes persistantes afin de pouvoir fournir ce service de manière optimale aux autres en ligne.
En téléchargeant ces fichiers HTML sur votre machine locale, vous évitez deux choses :
- Devoir attendre plus longtemps que d'habitude pour collecter les mêmes données
- Être bloqué de visiter la page, arrêtant complètement la collecte de données
Il y a quelques avantages à extraire les données des formats HTML. L'un des plus importants est la manière dont les propriétés des éléments peuvent être identifiées de manière unique. S'ils peuvent être identifiés de manière unique, ils peuvent être grattés à l'aide de Beautiful Soup. Mais pour cela, il faut savoir inspecter ces éléments HTML eux-mêmes.
Comment inspecter
Nous devrons approfondir les rouages de ce document, mais je promets de ne pas en faire un exercice d'apprentissage du HTML.
Si vous savez comment inspecter les objets HTML, n'hésitez pas à aller de l'avant. Sinon, veuillez suivre la procédure d'inspection des éléments de la page.
- Cliquez sur les trois points verticaux dans la barre de menu supérieure de Chrome
- Choisissez "Plus d'outils"
- Sélectionnez Outils de développement.
- Faites un clic droit sur la page Web
- Choisissez "Inspecter" pour accéder au panneau des outils de développement
Inspection de la page
Étant donné que toutes ces pages sont stockées localement, nous pouvons choisir de les afficher soit en accédant au système de fichiers pour les ouvrir dans le navigateur de notre choix, soit en continuant à créer notre code en implémentant l'extrait de code suivant.
with open("notebooks/Rookies/2000.html") as f:
page = f.read()
Toutes les données souhaitées de cette page sont hébergées dans cet élément de table. Avant d'aspirer à la hâte toutes ces données telles quelles, c'est maintenant le meilleur moment pour déterminer si tout ce qui se trouve sur cette table vaut la peine d'être collecté.
Pré-nettoyage
Le pré-nettoyage n'est peut-être pas un mot fréquent dans votre vocabulaire, mais pour ceux d'entre vous qui se voient régulièrement gratter des données, cela devrait l'être. Si vous voulez éviter la frustration des heures perdues à progresser sur un projet de collecte de données, il est préférable de séparer d'abord l'ivraie du bon grain.
Par exemple, notez les trois éléments encadrés en rouge.
Une ligne sert d'en-tête de tableau « principal ». Les deux autres lignes sont des instances en double des mêmes artefacts trouvés en haut. Ce motif se répète tous les 20 rangs.
Une inspection plus approfondie révèle que toutes ces lignes ont la même trbalise HTML (ligne de tableau). Ce qui distingue chacun de ces éléments des autres, ce sont leurs noms de classe.
- Ligne d'en-tête principale
— Classe =over_header - Répéter les lignes d'en-tête
— Classe =over_header thead - Ligne de catégorie de statistiques
— Classe =thead - L'
classesobjet de tableau contiendra tous les noms de classe de l'élément de ligne de tableau non déformé.
#array to house list of dataframes
dfs = []
#unnecessary table rows to be removed
classes = ["over_header", "over_header thead", "thead"]
La decomposeméthode sert à supprimer ces éléments indésirables dans une page. Selon la documentation officielle de Beautiful Soup ...
decompose()
Tag.decompose()supprime une balise de l'arborescence, puis la détruit complètement ainsi que son contenu.
Vous trouverez ci-dessous un extrait de code dans lequel nous avons pu optimiser la decomposeméthode à l'aide de forboucles.
#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()
- La
withméthode fournit à notre code la structure de lapagevariable pour lire les fichiers HTML stockés localement lorsqu'elle est appelée - Une classe d'analyseur HTML est initialisée en instanciant la classe Beautiful Soup et en transmettant à la fois l'
pageobjet string ethtml.parser. - La deuxième
forboucle parcourt les valeurs duclassestableau - La troisième
forboucle utilise la méthode de Beautiful Soupfind_allpour identifier les éléments qui ont à la foistrdes balises et des noms de classe correspondant à ceux declasses tr.decomposesert à omettre complètement chacun des éléments de ligne de tableau identifiés de la page
Extraction des données
Nous pouvons enfin commencer à travailler sur la partie du code qui extrait réellement les données de la table. N'oubliez pas que le tableau contenant toutes les données pertinentes possède l'ID unique HTML rookies. Les ajouts suivants à notre code serviront à analyser les données de cette table.
#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_tableinstancie la méthode de Beautiful Soupfindpour identifier l'élément HTML avec IDrookies- Voyant que Pandas peut lire les tables HTML, la table des recrues est chargée dans Pandas à l'aide de la
read_htmlméthode, en y passantrookie_tablesous forme de chaîne - Passer à la fin
[0]passerookiesd'une liste de trames de données à une seule trame de données - Une colonne "Année" est la fin de la
rookiestrame de données dfs.append(rookies)sert à héberger toutes les tables de chaque année de recrue dans l'ordre dans lequel elles ont été itérées dans une liste de trames de données- La méthode Pandas
concatest utilisée pour combiner la liste des trames de données en une seule trame de données :all_rookies
Notre dernière étape consiste à prendre toutes ces informations utiles et propres et à les archiver dans un format CSV facilement lisible par l'homme et la machine. Repérer cette ligne jusqu'à la fin de notre code sera utile au moment de décider de revenir et de référencer les données collectées.
#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")
Encore une fois, le processus suivi dans cette procédure pas à pas s'appliquera sans aucun doute à la plupart des autres pages de Basketball-Reference.com . Il y a cinq étapes simples à suivre dans chaque cas.
- Identifier l'URL de la page
- Télécharger la page
- Identifier les éléments
- Pré-nettoyage et extraction
- Archive
La prochaine étape de cette série utilisera ces données pour mieux comprendre le potentiel des futurs joueurs. Soyez donc à l'affût des prochains versements!
![Qu'est-ce qu'une liste liée, de toute façon? [Partie 1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)



































