¿Alguna vez te topaste con alguna tabla de Wikipedia que justo necesitabas para tu investigación o proyecto de análisis? Como la tabla de medallas de los Juegos Panamericanos, Las Elecciones Presidenciales o datos de las Exportaciones de un Pais, ¿Si? Bueno, Aquí aprenderás como obtener datos de cualquier tabla.

Esta es la tabla que buscamos obtener que contiene información del Censo de la Provincia de Trujillo del año 2017). Para hacer Web Scraping a esta página, utilizaremos las bibliotecas Requests y Pandas de Python.

image.png

Al finalizar este Tutorial, tendrás como resultado una tabla lista para su análisis. Lo podrás guardar en tu directorio de trabajo o descargar en tu computadora como archivo CSV (o el formato que gustes). Espero que te resulte útil; sin más que añadir... "Happy Coding!"

Fase 1: Conección y obtención de codigo fuente

Comenzaremos importando la biblioteca requests para realizar la petición HTTP que nos devolverá el código fuente de la página web y pandas, para usar su método .read_html() que nos ayudará a extraer todas las tablas HTML. Usaremos este método y no la biblioteca Beautiful Soup porque de esta forma es mucho más fácil y funciona bien en cualquier página web que contenga tablas HTML debido a su estructura simple de leer.

Para tu conocimiento:Usando .read_html() no solo nos permite extraer tablas HTML desde un enlace (lo que usaremos hoy), sino también desde un archivo html o una cadena de caracteres (string) que contenga HTML.

import requests
# Manipular código y guardar datos tabulares en archivo CSV
import pandas as pd


# url de la página web a «escrapear»
url = 'https://es.wikipedia.org/wiki/Provincia_de_Trujillo_(Per%C3%BA)'

# pasar "User-agent" para simular interacción con la página usando Navegador web
headers = {"User-agent": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36'}

respuesta = requests.get(url, headers=headers)

# El código de respuesta <200> indicará que todo salió bien
print(respuesta)
<Response [200]>

Aparte de la respuesta positiva que nos devolvió la petición, seguro te diste cuenta que pasamos el parámetro headers al método requests.get(), esto sirve para evitar que el servidor de la página web nos bloqueen el acceso. Aunque con Wikipedia no tenemos ese problema, otras páginas son restrictivas cuando se trata de bots tratando de «escrapear» sus datos. Siguiento esta buena práctica, nos ahorarremos dolores de cabeza luego.

Por último, guardamos en una variable todas las tablas encontradas en el objecto HTML.

all_tables = pd.read_html(respuesta.content, encoding = 'utf8')

Fase 2: Análisis de estructura HTML y extracción de datos

Ahora veamos cuantas tablas hay en la página web

print(f'Total de tablas encontradas: {len(all_tables)}')
Total de tablas encontradas: 6

Debido a que hay varias tablas, usaremos el parámetro match y le pasaremos una palabra o frase que solo se encuentre en la tabla que nos interesa.

matched_table = pd.read_html(respuesta.text, match='Fuente: Censos Nacionales 2017')

# imprime numero de tablas que coinciden con parametro match
print(f'Total de tablas encontradas: {len(matched_table)}')
Total de tablas encontradas: 1

Guardamos nuestra tabla de interés en una variable.

censo_trujillo = matched_table[0]

# Verificamos si es la tabla que buscamos
censo_trujillo.tail(5)
UBIGEO Distrito Hogares Viviendas Población
8 130109 Salaverry 5599 5244 18 944
9 130110 Simbal 1662 1151 4061
10 130111 Víctor Larco Herrera 19 543 18 461 68 506
11 NaN TOTAL 273 619 250 835 970 016
12 Fuente: Censos Nacionales 2017: X de Población... Fuente: Censos Nacionales 2017: X de Población... Fuente: Censos Nacionales 2017: X de Población... Fuente: Censos Nacionales 2017: X de Población... Fuente: Censos Nacionales 2017: X de Población...

¡Bien! Es la tabla que buscamos exportar, pero vemos que las 2 últimas filas no son necesarias, por lo que pasamos a eliminarlas.

censo_trujillo.drop(censo_trujillo.tail(2).index, inplace=True)

# Verificar si se eliminaron los registros no deseados
censo_trujillo.tail(2)
UBIGEO Distrito Hogares Viviendas Población
9 130110 Simbal 1662 1151 4061
10 130111 Víctor Larco Herrera 19 543 18 461 68 506

Ahora asignaremos el UBIGEO como índice de la tabla.

censo_trujillo.set_index('UBIGEO', inplace = True)

# Verificamos el cambio de índice
censo_trujillo.head(2)
Distrito Hogares Viviendas Población
UBIGEO
130101 Trujillo 87 963 82 236 314 939
130102 El Porvenir 57 878 50 805 190 461

También, vemos que en las columnas Hogares,Viviendas y Población que contienen números, hay un espacio en medio de los números que le da Wikipedia como formato para mejorar su visualización. Sin embargo, necesitamo que nuestros datos numéricos estén limpios sin ningún simbolo o espacios para poder realizar operaciones.

Removamos ese espacio que no aporta nuestros datos. Para ello usaremos la biblioteca normalize.

from unicodedata import normalize

Creamos una funcion y lo correremos a toda nuestra tabla para quitar los molestos espacios en blanco.

def remove_whitespace(x):
    """Funcion para normalizar datos con Unicode para luego quitar los espacios usando .replace().

    Argumentos de entrada: Nombre de columna o lista con nombres de columnas.
    Retorna: columna o columnas sin espacios en blanco
    """
    if isinstance(x, str):
        return normalize('NFKC', x).replace(' ', '')
    else:
        return x

Aplicamos la funcion para quitar espacios en blanco a toda las columnas con datos numéricos.

numeric_cols = ['Hogares','Viviendas','Población']

# Aplicar función remove_whitespace a columnas en variable y las reemplazamos en tabla
censo_trujillo[numeric_cols] = censo_trujillo[numeric_cols].applymap(remove_whitespace)

# Verificamos si se quitaron los espacios en blanco
censo_trujillo.head()
Distrito Hogares Viviendas Población
UBIGEO
130101 Trujillo 87963 82236 314939
130102 El Porvenir 57878 50805 190461
130103 Florencia De Mora 7777 8635 37262
130104 Huanchaco 20206 16534 68409
130105 La Esperanza 49773 47896 189206

Ahora veamos los tipos de datos.

censo_trujillo.dtypes
Distrito     object
Hogares      object
Viviendas    object
Población    object
dtype: object

Como vemos, todos las columnas son de tipo de dato Objeto (lo que Pandas considera como una cadena de caracteres o String). Como Object es muy amplio, necesitamos definir el tipo de dato correcto a cada columna para que luego se realizar operaciones con los datos.

Aquí va la primera opción para hacerlo, reusando la variable numeric_cols usada líneas arriba.

censo_trujillo[numeric_cols] = censo_trujillo[numeric_cols].apply(pd.to_numeric)

# Verificamos que las columnas con números tengan el tipo de dato numérico asignado
censo_trujillo.dtypes
Distrito     object
Hogares       int64
Viviendas     int64
Población     int64
dtype: object

Como ves, nuestras columnas con números ya tienen el formato correcto, pero la columna Distrito aún se mantiene como Object. Aunque no habría mayor inconveniente, es mejor especificar que datos contiene cada columna.

Aquí va la seguida opción para cambiar el tipo de dato a múltiples columnas.

convert_dict = {
    'Distrito': 'string',
    'Hogares': 'int',
    'Viviendas': 'int',
    'Población': 'int'
}

censo_trujillo = censo_trujillo.astype(convert_dict)

# Verificamos que las columnas con números tengan el tipo de dato numérico asignado
censo_trujillo.dtypes
Distrito     string
Hogares       int64
Viviendas     int64
Población     int64
dtype: object

Fase 3: Guardado del Conjunto de Datos

Por fin, una ves los datos están limpios y con el tipo de dato correcto, vamos a guardarlos en formato CSV para usarlos luego.

censo_trujillo.to_csv('censo_provincia_trujillo_2017.csv')

# Leamos el archivo para verificar su creacion
pd.read_csv('censo_provincia_trujillo_2017.csv').head(3)
UBIGEO Distrito Hogares Viviendas Población
0 130101 Trujillo 87963 82236 314939
1 130102 El Porvenir 57878 50805 190461
2 130103 Florencia De Mora 7777 8635 37262

Si lo trabajaste en Jupyter Notebook o tu Editor de Código Favorito, debe estar ubicado en la misma carpeta donde corriste tu archivo .pynb. Pero, si lo trabajaste en Google Colab (como yo), puedes usar la biblioteca files para descargar el archivo en la computadora donde estés trabajando.

from google.colab import files

# Descarga archivo con datos de tabla
files.download("censo_provincia_trujillo_2017.csv")

print('Listo, en un momento saldrá la opción "Guardar Como" para descargar el archivo...')
Listo, en un momento saldrá la opción "Guardar Como" para descargar el archivo...

¡Genial! Ya tenemos los datos de una tabla de Wikipedia lista para su uso. Aunque la tabla extraida es pequeña en dimensión, esta forma de trabajo puedes aplicarla para extraer cualquier tabla que encuentres interesante, ya sea de Wikipedia o de cualquier otra página web que contenga tablas HTML.

Resumiendo lo realizado

  • Leímos las tablas HTML de una página de Wikipedia
  • Removimos los espacios en formato Unicode que impedían la conversión al tipo de dato correcto
  • Convertimos el tipo de dato de todas las columnas al correcto
  • Guardamos la tabla extraída como formato csv para su posterior utilización
  • Finalmente, descargamos el archivo csv en la computadora de trabajo

¡Espera! Una cosa más, como estámos en el mes de diciembre, te regalo este pensamiento:

Si lo lees y lo entiendes está genial, aunque eso no te asegura haberlo aprendido. Para dominarlo necesitas practicarlo.

Por eso, te sugiero que guardes este artículo en tus Favoritos para que lo practiques luego. O si quieres practicarlo ya mismo, aquí te dejo el artículo en Google Colab, donde podrás abrir y correr el código sin necesidad de instalar nada, solo tu navegador web y tus ganas de aprender.

Si te gustó, puedes ver mis otras publicaciones, seguro te serán de utilidad. Si gustas apoyarme, comparte este artículo en tus Redes Sociales (Facebook, Linkedint, Twitter) o si estás de buen ánimo, invítame una taza de café ☕. Nos vemos 🏃💨