Volver a la lista

Fundamentos del web scraping — BeautifulSoup

Aprenda a extraer los datos deseados de una página web utilizando las bibliotecas Requests y BeautifulSoup de Python.

Intermedio
|
10min
|
Verificado (2026-07)
rastreo webBeautifulSouprequestsanálisis de HTMLscraping
Progreso0/17 (0%)

Fundamentos del web crawling — BeautifulSoup

Al finalizar este tema

Podrás implementar el flujo básico para obtener páginas web con requests y extraer los datos deseados con BeautifulSoup.


¿Qué es el web crawling?

El web crawling consiste en que un programa visite páginas web y recopile datos automáticamente. También se le conoce como scraping.

Es realizar con código lo que hace un navegador:

  1. Enviar una solicitud (request) al servidor web
  2. Recibir el HTML
  3. Extraer los datos deseados del HTML

Paso 1 — Obtener el HTML (requests)

python
import requests
url = "https://example.com"
response = requests.get(url)
print(response.status_code) # 200 = correcto
print(response.text[:200]) # Primeros 200 caracteres del HTML

requests.get(url) envía una solicitud al servidor como un navegador y recibe el HTML enviado por el servidor en response.text.

python
# Comprueba el código de estado
if response.status_code == 200:
html = response.text
elif response.status_code == 404:
print("No se encontró la página")
elif response.status_code == 403:
print("Acceso denegado")

Paso 2: Análisis de HTML (BeautifulSoup)

python
from bs4 import BeautifulSoup
html = """
<html>
<body>
<h1 class="title">Título de la noticia</h1>
<div class="content">
<p>Este es el primer párrafo.</p>
<p>Este es el segundo párrafo.</p>
</div>
<ul id="tags">
<li>Python</li>
<li>Datos</li>
<li>Crawling</li>
</ul>
</body>
</html>
"""
soup = BeautifulSoup(html, "html.parser")

BeautifulSoup analiza una cadena HTML y la convierte en un objeto con estructura de árbol. En este objeto, se pueden localizar las partes deseadas mediante etiquetas, clases, id, entre otros.


Paso 3 — Extracción de datos

Búsqueda por etiqueta

python
# Primera etiqueta h1
title = soup.find("h1")
print(title.text) # "Título de la noticia"
print(title["class"]) # ["title"]
# Todas las etiquetas p
paragraphs = soup.find_all("p")
for p in paragraphs:
print(p.text)
# "Este es el primer párrafo."
# "Este es el segundo párrafo."

Búsqueda mediante selectores CSS

python
# select: usa selectores CSS, igual que querySelectorAll
items = soup.select("ul#tags li")
for item in items:
print(item.text)
# "Python"
# "Datos"
# "Crawling"
# Selección por clase
content = soup.select_one("div.content")
print(content.text.strip())

select/select_one es más intuitivo que find/find_all para quienes están familiarizados con CSS.


Ejemplo práctico — Extracción de datos de tablas

python
table_html = """
<table>
<tr><th>Nombre</th><th>Puntuación</th></tr>
<tr><td>Carlos</td><td>85</td></tr>
<tr><td>Ana</td><td>92</td></tr>
<tr><td>Luis</td><td>78</td></tr>
</table>
"""
soup = BeautifulSoup(table_html, "html.parser")
rows = soup.select("tr")
data = []
for row in rows[1:]: # Excluye la cabecera
cols = row.find_all("td")
data.append({
"nombre": cols[0].text,
"puntuación": int(cols[1].text)
})
print(data)
# [{'nombre': 'Carlos', 'puntuación': 85}, {'nombre': 'Ana', 'puntuación': 92}, {'nombre': 'Luis', 'puntuación': 78}]
python
# Convierte directamente en un DataFrame de pandas
import pandas as pd
df = pd.DataFrame(data)
print(df)

Precauciones

Intervalo entre solicitudes

python
import time
urls = ["https://example.com/page/1", "https://example.com/page/2"]
for url in urls:
response = requests.get(url)
time.sleep(1) # Espera un segundo para no sobrecargar el servidor

Realizar solicitudes rápidas y continuas puede sobrecargar el servidor o provocar el bloqueo de la IP.

Configuración del User-Agent

python
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/91.0"
}
response = requests.get(url, headers=headers)

Algunos servidores verifican el User-Agent para bloquear bots.

robots.txt

text
https://example.com/robots.txt

Este archivo especifica las rutas permitidas y prohibidas para el rastreo (crawling). Verificar primero la política de rastreo del sitio es una cuestión de cortesía y una medida de seguridad legal.


Limitaciones del rastreo

BeautifulSoup solo puede procesar HTML estático. El contenido que se carga dinámicamente mediante JavaScript (SPA, scroll infinito, etc.) no se incluye en el HTML y, por lo tanto, no puede extraerse.

SituaciónHerramienta
HTML estáticorequests + BeautifulSoup
Carga dinámica de JavaScriptSelenium, Playwright
API disponibleLlamada directa a la API con requests (más eficiente)

Si existe una API, se debe priorizar su uso sobre el rastreo. Esto permite obtener datos estructurados con precisión y reduce la carga en el servidor.


Resumen del flujo principal

text
1. requests.get(url) → texto HTML
2. BeautifulSoup(html) → árbol analizado
3. soup.select("selector CSS") → elementos deseados
4. element.text / element["attr"] → extracción de datos

Manejo de errores

En el rastreo real se producen diversos errores:

python
import requests
from bs4 import BeautifulSoup
def safe_fetch(url, retries=3):
for attempt in range(retries):
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
return response.text
except requests.exceptions.Timeout:
print(f"Tiempo de espera agotado ({attempt + 1}/{retries})")
except requests.exceptions.HTTPError as e:
print(f"Error HTTP: {e}")
return None
except requests.exceptions.ConnectionError:
print(f"Falló la conexión ({attempt + 1}/{retries})")
return None

Si no se configura timeout, el programa esperará indefinidamente ante un servidor que no responde. raise_for_status() lanza una excepción ante respuestas 4xx/5xx.


Crawl de múltiples páginas

python
import time
base_url = "https://example.com/articles?page="
all_titles = []
for page in range(1, 11):
html = safe_fetch(f"{base_url}{page}")
if html is None:
continue
soup = BeautifulSoup(html, "html.parser")
titles = soup.select("h2.article-title")
all_titles.extend([t.text.strip() for t in titles])
print(f"Página {page}: {len(titles)} elementos recopilados")
time.sleep(1)
print(f"Recopilación completada: {len(all_titles)} elementos en total")

Guardar resultados de la extracción de datos


python
import json
import csv
# Guarda como JSON
with open("articles.json", "w", encoding="utf-8") as f:
json.dump(all_titles, f, ensure_ascii=False, indent=2)
# Guarda como CSV
with open("articles.csv", "w", encoding="utf-8", newline="") as f:
writer = csv.writer(f)
writer.writerow(["número", "título"])
for i, title in enumerate(all_titles, 1):
writer.writerow([i, title])

Los datos recopilados se guardan en formato JSON o CSV para permitir su análisis directo con pandas.



Extracción de enlaces — atributo href

python
# Extrae todos los enlaces de la página
links = soup.select("a[href]")
for link in links:
url = link["href"]
text = link.text.strip()
print(f"{text}: {url}")
# Solo enlaces con un patrón concreto
article_links = [
a["href"] for a in soup.select("a[href]")
if "/article/" in a.get("href", "")
]

El crawling es el primer paso de la "técnica para obtener datos". La recopilación de datos es el punto de partida en todos los ámbitos, como el análisis de datos, el aprendizaje automático y el monitoreo.

💬 Preguntas y comentarios

0 comentarios

Puedes publicar sin iniciar sesión. Los comentarios de invitados no pueden editarse ni eliminarse después.

0/2000

Cargando...