Cómo conseguir las tablas de STATS.NBA

En este tutorial quiero mostrar cómo usar las herramientas de Google Chrome para obtener la información necesaria para extraer los datos de stats.nba.com usando el lenguaje R.

1 Algunas definiciones

2 Requisitos para conseguir los datos de la página

3 Extraer los datos y guardarlos

El método que explico funciona para stats.nba.com y funcionaría para muchas otras páginas, pero no funcionará para todas. Solo funcionará para las páginas basadas en javascript que llenan sus datos dinámicamente desde APIS externas.

1. Definiciones

¿Qué es una API y para qué sirve?

Las siglas API corresponden al inglés application programming interface, que se traduce como interfaz de programación de aplicaciones. Esta fórmula se refiere al conjunto de estructuras que permiten que los componentes de un software se comuniquen con otros.

Fuente: https://keepcoding.io/blog/que-es-una-api-y-para-que-sirve/)

¿Qué es un API endpoint ?

Una petición HTTP que retorna JSON. Dicho de otra forma, un endpoint es un extremo de un canal de comunicación. Cuando una API interactúa con otro sistema, los puntos de contacto de esta comunicación se consideran endpoints. Para las API, un punto final puede incluir una URL de un servidor o servicio. Cada endpoint es la ubicación desde la cual las API pueden acceder a los recursos que necesitan para llevar a cabo su función.

Las API funcionan mediante “solicitudes” y “respuestas”. Cuando una API solicita información de una aplicación web o un servidor web, recibirá una respuesta. El lugar donde las API envían solicitudes y donde vive el recurso se denomina endpoint.

Fuente: https://smartbear.com/learn/performance-monitoring/api-endpoints/

2. Requisitos

Este tutorial utiliza la última versión de Google Chrome para encontrar la información del endpoint. El código de este tutorial se escribió en R 4.1.3 usando las siguientes Librerias:

  1. tidyverse

  2. httr

  3. jsonlite

3. Extraer los datos y guardarlos

Buscar los enpoint

En este tutorial descargaré las estadísticas de aclarados por partido de cada jugador de esta temporada en playoffs, que se puede encontrar en este enlace https://www.nba.com/stats/players/isolation/

El primer paso es abrir Google Chrome e ir a la página de la que queremos recopilar los datos.


Después botón derecho del ratón y clikamos inspeccionar

Se debería abrir un panel en el lado derecho de la pantalla o debajo, según esté configurado, como en el pantallazo. A continuación, hacemos clic en la pestaña de red para ver todas las llamadas API realizadas por la página.

En la pestaña Red, seleccionamos XHR y cargamos de nuevo la página.

Una vez que la página se haya cargado, deberemos buscar entre las diferentes llamadas para encontrar la que queremos. Normalmente se llamará algo similar al nombre de la página en la que se encuentra y estará seguido por un montón de parámetros de consulta.

Una vez que hayamos encontrado la API correcta (o si no estamos seguros), podemos hacer clic con el botón derecho en la llamada y copiar la respuesta.

Luego pegamos la respuesta en un visor json como http://jsonviewer.stack.hu/ para verla mejor.

Aquí no funciona el botón derecho, así que le damos paste o a ctrl+ v, después damos a la pestaña Viewer y vamos desplegando para ver el contenido

Yo uso este lector para saber lo que tengo que desplegar para sacar los datos, así que si ya estamos seguros de que tenemos la llamada de la API correcta, podemos copiar el link.

Creamos el código

Para eso lo primero que hay que hacer es cargar las librerías necesarias

library(tidyverse)
library(httr) 
library(jsonlite) 
library(janitor)
library(glue)

La librería httr es una Herramienta útil para trabajar con HTTP organizado por verbos HTTP (GET(), POST(), etc.), jsonlite es para reestructurar los datos del Json extraido, janitor para hacer los nombres de las columnas mas amigables y glue para ahorrarnos trabajo a la hora de escribir el csv

Ahora necesitamos crear un dataFrame con los encabezados de solicitud, pinchamos en Encabezados y los copiamos. Casi todo lo que vemos en el navegador se transmite a la computadora a través del protocolo HTTP . Por ejemplo, cuando abrimos la página de estadísticas de la NBA, el navegador envió muchas solicitudes HTTP ( Solicitud ) y recibió muchas respuestas ( Respuesta ). Para recibir la tabla tenemos que poner los encabezados con los que solicitar las estadísticas para que el servidor nos de una respuesta adecuada con la información que hemos pedido.

Fuente: https://appmaster.io/es/help/how-to/encabezados-de-solicitud-respuesta-en-appmaster

Los dos puntos los cambiamos por iguales y en la linea número 10 usamos comillas simples dentro de las comillas dobles

 headers <- c( #les llamo headers porque es mas corto ;)
  "Accept" = "application/json, text/plain, */*",
  "Accept-Encoding" = "gzip, deflate, br",
  "Accept-Language" = "es-ES,es;q=0.9,en;q=0.8",
  "Connection" = "keep-alive",
  "Host" = "stats.nba.com",
  "If-Modified-Since"= "Tue, 28 Jun 2022 16:52:53 GMT",
  "Origin" = "https://www.nba.com",
  "Referer" = "https://www.nba.com/",
  "sec-ch-ua" = "Not A;Brand';v='99', 'Chromium';v='103', 'Google Chrome';v='103'",
  "sec-ch-ua-mobile" = "?0",
  "sec-ch-ua-platform" = "macOS",
  "Sec-Fetch-Dest" = "empty",
  "Sec-Fetch-Mode" = "cors",
  "Sec-Fetch-Site" = "same-site",
  "User-Agent" = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36",
  "x-nba-stats-origin"= "stats",
  "x-nba-stats-token"= "true"
)

Seguimos. Pegamos el link y aplicamos las funciones httr:: o jsonlite:: si hemos cargado las librerías previamente no haría falta, pero lo pongo para que se sepa de donde vienen las funciones

link <- "https://stats.nba.com/stats/synergyplaytypes?LeagueID=00&PerMode=PerGame&PlayType=Isolation&PlayerOrTeam=P&SeasonType=Playoffs&SeasonYear=2021-22&TypeGrouping=offensive"
res <- httr::GET(url = link, add_headers(.headers = headers))
json_res <- jsonlite::fromJSON(content(res, "text"))

Si ponemos el símbolo de $ después de la variable json_res, veríamos lo siguiente

Elegimos el dataframe resultSets

Y aquí elegimos rowSets y hacemos un dataframe con los rows

df <- data.frame(json_res$resultSets$rowSet)

Obtenemos esto

Y ahora vamos con los encabezados

colnames(df) <- json_res$resultSets$headers[[1]]

ahora tendríamos la tabla completa pero vamos a pasar el clean_names

df <- janitor::clean_names(df)

Y este sería el código completo

library(tidyverse)
library(httr)
library(jsonlite)
library(janitor)
library(glue)


headers <- c( # les llamo headers porque es mas corto ;)
  "Accept" = "application/json, text/plain, */*",
  "Accept-Encoding" = "gzip, deflate, br",
  "Accept-Language" = "es-ES,es;q=0.9,en;q=0.8",
  "Connection" = "keep-alive",
  "Host" = "stats.nba.com",
  "If-Modified-Since" = "Tue, 28 Jun 2022 16:52:53 GMT",
  "Origin" = "https://www.nba.com",
  "Referer" = "https://www.nba.com/",
  "sec-ch-ua" = "Not A;Brand';v='99', 'Chromium';v='103', 'Google Chrome';v='103'",
  "sec-ch-ua-mobile" = "?0",
  "sec-ch-ua-platform" = "macOS",
  "Sec-Fetch-Dest" = "empty",
  "Sec-Fetch-Mode" = "cors",
  "Sec-Fetch-Site" = "same-site",
  "User-Agent" = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36",
  "x-nba-stats-origin" = "stats",
  "x-nba-stats-token" = "true"
)



season <- "2021-22"

link <- paste0("https://stats.nba.com/stats/synergyplaytypes?LeagueID=00&PerMode=PerGame&PlayType=Isolation&PlayerOrTeam=P&SeasonType=Playoffs&SeasonYear=", season, "&TypeGrouping=offensive")
res <- httr::GET(url = link, add_headers(.headers = headers))
json_res <- jsonlite::fromJSON(content(res, "text"))


df <- data.frame(json_res$resultSets$rowSet)
colnames(df) <- json_res$resultSets$headers[[1]]
df <- janitor::clean_names(df)

write.csv(df, glue::glue("{season}.csv"))

Esto es un año solamente, y estas estadísticas están disponibles desde el 2015 pero se puede hacer una función que nos den estos 7 años y también podemos cambiar la palabra playoffs por Regular+Season y nos daría la temporada regular, pero bueno la función seria tal que así

library(tidyverse)
library(httr)
library(jsonlite)
library(janitor)
library(glue)

season <- c("2015-16", 
            "2016-17", 
            "2017-18", 
            "2018-19", 
            "2019-20", 
            "2020-21", 
            "2021-22")

dfseason <- function(season) {
  headers <- c( # les llamo headers porque es mas corto ;)
    "Accept" = "application/json, text/plain, */*",
    "Accept-Encoding" = "gzip, deflate, br",
    "Accept-Language" = "es-ES,es;q=0.9,en;q=0.8",
    "Connection" = "keep-alive",
    "Host" = "stats.nba.com",
    "If-Modified-Since" = "Tue, 28 Jun 2022 16:52:53 GMT",
    "Origin" = "https://www.nba.com",
    "Referer" = "https://www.nba.com/",
    "sec-ch-ua" = "Not A;Brand';v='99', 'Chromium';v='103', 'Google Chrome';v='103'",
    "sec-ch-ua-mobile" = "?0",
    "sec-ch-ua-platform" = "macOS",
    "Sec-Fetch-Dest" = "empty",
    "Sec-Fetch-Mode" = "cors",
    "Sec-Fetch-Site" = "same-site",
    "User-Agent" = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36",
    "x-nba-stats-origin" = "stats",
    "x-nba-stats-token" = "true"
  )


  link <- paste0("https://stats.nba.com/stats/synergyplaytypes?LeagueID=00&PerMode=PerGame&PlayType=Isolation&PlayerOrTeam=P&SeasonType=Playoffs&SeasonYear=", season, "&TypeGrouping=offensive") # LINK PARA PLAYOFFS
  res <- httr::GET(url = link, add_headers(.headers = headers))
  json_res <- jsonlite::fromJSON(content(res, "text"))

  link1 <- paste0("https://stats.nba.com/stats/synergyplaytypes?LeagueID=00&PerMode=PerGame&PlayType=Isolation&PlayerOrTeam=P&SeasonType=Regular+Season&SeasonYear=", season, "&TypeGrouping=offensive") # LINK PARA REGULAR SEASON
  res1 <- httr::GET(url = link1, add_headers(.headers = headers))
  json_res1 <- jsonlite::fromJSON(content(res1, "text"))

  # TABLA PLAYOFF
  df <- data.frame(json_res$resultSets$rowSet)
  colnames(df) <- json_res$resultSets$headers[[1]]
  df <- janitor::clean_names(df)
  df$year <- season
  df$type <- "playoffs"
  
  # TABLA REGULAR SEASON
  df1 <- data.frame(json_res1$resultSets$rowSet)
  colnames(df1) <- json_res1$resultSets$headers[[1]]
  df1 <- janitor::clean_names(df1)
  df1$year <- season
  df1$type <- "regularSeason"
  
  df2 <- rbind(df1, df) #juntamos las tablas
  
  return(df2)
}

df_isolation <- map_df(season, dfseason) #mapeamos la funcion


write.csv(df_isolation, "isolation.csv")

Y este es el resultado

And that’s it si se quiere descargar directamente el csv aquí dejo el link

https://raw.githubusercontent.com/IvoVillanueva/isolation/main/isolation.csv