Si llegaste hasta esta lección, significa que ya tienes una buena idea de cómo funciona el web scraping y cómo usar la fantástica librería BeautifulSoup
para extraer los datos que necesitas de un HTML. Sin embargo, en el mundo del web scraping, no todo es tan perfecto como en los tutoriales. A veces, nuestro sueño de recolectar datos se convierte en una lucha contra los errores. Así que hablemos de cómo superar los obstáculos y hacer que nuestro scraper sea lo más resistente posible.
1. Errores comunes en web scraping
Error 404 y otros errores HTTP
El clásico problema: intentas acceder a una página y, en lugar del contenido, te devuelven un orgulloso "404 Not Found". Esto puede pasar porque la página fue eliminada o movida. Otros errores HTTP comunes incluyen 403 (Forbidden), 500 (Internal Server Error) y 503 (Service Unavailable).
Cambios en la estructura del HTML
Invertiste un montón de tiempo escribiendo código para extraer datos, y al día siguiente el sitio decidió "lucirse" y cambió su estructura HTML. ¡Ay, otra vez a reescribir todo!
Restricciones en la cantidad de solicitudes
Algunos sitios empiezan a sospechar algo raro cuando los scrapers les hacen demasiadas solicitudes durante todo el día. En el mejor de los casos, te bloquearán por un tiempo, y en el peor, para siempre.
Tiempos de espera y timeouts
A veces, las páginas tardan mucho en cargar, y tu script puede fallar si el tiempo de espera supera el timeout predeterminado.
2. Métodos para manejar errores
Uso de try-except
Tus scripts no deberían fallar ante cualquier sorpresa. Agregar bloques try-except
te ayuda a capturar errores y hacer que tu web scraper no se detenga, sino que siga funcionando como si nada.
import requests
from bs4 import BeautifulSoup
url = 'http://example.com/some-nonexistent-page'
try:
response = requests.get(url)
response.raise_for_status() # Lanza HTTPError para respuestas malas
except requests.exceptions.HTTPError as errh:
print("Error HTTP:", errh)
except requests.exceptions.ConnectionError as errc:
print("Error de Conexión:", errc)
except requests.exceptions.Timeout as errt:
print("Error de Timeout:", errt)
except requests.exceptions.RequestException as err:
print("OOps: Algo más ocurrió", err)
Un buen script no solo captura la excepción, sino que también tiene una acción de respuesta para cada tipo de error. ¿Te bloquearon por IP? Cambias al siguiente proxy. ¿El sitio falló? Mientras tanto, extraes datos de otro sitio. Si no se encuentran algunos elementos en la página que deberían estar, puedes notificar al propietario del scraper para que actualice el script y enviarle un correo electrónico.
Logging
"¿Para qué los logs?" — preguntarás. Porque los logs son tus segundos ojos. Te ayudarán a entender qué salió mal y a corregir el error lo más rápido posible.
import logging
logging.basicConfig(filename='scraper.log', level=logging.INFO)
try:
# Tu código de web scraping
pass
except Exception as e:
logging.error("Ocurrió una excepción", exc_info=True)
Uso de timeouts y reintentos
A veces, lo único que necesitas es esperar un poco e intentar de nuevo. Para este propósito, los timeouts y los retries son ideales.
try:
response = requests.get(url, timeout=5)
response.raise_for_status()
except requests.exceptions.Timeout:
print("Timeout ocurrió. Reintentando...")
# Reintentar la solicitud o realizar otra acción
3. Ejemplos de web scraping resistente
Un scraper simple con manejo de errores
Hagamos un pequeño pero confiable scraper que pueda manejar algunos errores comunes.
import requests
from bs4 import BeautifulSoup
import time
import logging
logging.basicConfig(filename='scraper.log', level=logging.INFO)
url = 'http://example.com/some-nonexistent-page'
def fetch_html(url):
try:
response = requests.get(url, timeout=5)
response.raise_for_status()
return response.text
except requests.exceptions.HTTPError as errh:
logging.error("Error HTTP: %s", errh)
except requests.exceptions.ConnectionError as errc:
logging.error("Error de Conexión: %s", errc)
except requests.exceptions.Timeout as errt:
logging.warning("Timeout ocurrió. Reintentando...")
time.sleep(1) # Esperar un poco e intentar de nuevo
return fetch_html(url)
except requests.exceptions.RequestException as err:
logging.error("OOps: Algo más ocurrió %s", err)
return None
html_content = fetch_html(url)
if html_content:
soup = BeautifulSoup(html_content, 'html.parser')
# Tu código de extracción de datos
else:
print("No se pudo obtener el contenido de la página.")
Guardar datos por partes
Para no perder los datos que ya has extraído en caso de un fallo, guárdalos por partes. Por ejemplo, si estás extrayendo información de una lista de páginas, al guardar los resultados a medida que avanzas, minimizas el riesgo de perder datos.
import csv
def save_to_csv(data, filename='data.csv'):
with open(filename, mode='a', newline='') as file:
writer = csv.writer(file)
writer.writerow(data)
De esta forma, aunque tu script falle a mitad del trabajo, no perderás todos los datos y podrás continuar desde el último estado guardado.
La página que estás scrapeando podría cambiar parcialmente y la mayor parte de los datos seguirían estando disponibles. Sería una pena cancelar el scraping y perder datos valiosos solo porque falta un pequeño porcentaje de ellos.
GO TO FULL VERSION