Que es web scraping y para que sirve
Web scraping es la tecnica de extraer datos de paginas web de forma automatizada. En lugar de copiar informacion manualmente, escribes un script que visita la pagina, encuentra los datos que necesitas y los guarda en un formato util como CSV, JSON o una base de datos.
En mi experiencia, los casos de uso mas comunes son: monitorear precios de competidores, recopilar datos para analisis, extraer listados de productos, y automatizar la recopilacion de informacion publica. Llevo 3 anos usando Python para scraping y es, sin duda, el lenguaje mas adecuado para esta tarea gracias a su ecosistema de librerias.
Instalacion y setup
# Crear entorno virtual (recomendado)
python -m venv scraping_env
source scraping_env/bin/activate # Linux/Mac
# scraping_env\Scripts\activate # Windows
# Instalar dependencias
pip install requests beautifulsoup4 lxml pandas
# Verificar instalacion
python -c "import requests; import bs4; print('Todo instalado correctamente')"
Tu primer scraper: paso a paso
Vamos a construir un scraper que extrae titulos de noticias de un sitio web publico. Usaremos requests para descargar el HTML y BeautifulSoup para parsearlo.
import requests
from bs4 import BeautifulSoup
# Paso 1: Descargar el HTML
url = "https://news.ycombinator.com/"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
}
response = requests.get(url, headers=headers)
# Verificar que la peticion fue exitosa
if response.status_code != 200:
print(f"Error: {response.status_code}")
exit()
# Paso 2: Parsear el HTML
soup = BeautifulSoup(response.text, "lxml")
# Paso 3: Encontrar los elementos
titles = soup.find_all("span", class_="titleline")
# Paso 4: Extraer datos
for i, title in enumerate(titles[:10], 1):
link = title.find("a")
print(f"{i}. {link.text}")
print(f" URL: {link.get('href', 'N/A')}")
print()
Selectores: como encontrar elementos
Despues de trabajar con cientos de paginas, puedo decirte que el 90% del trabajo en scraping es encontrar los selectores correctos. BeautifulSoup ofrece multiples formas:
| Metodo | Uso | Ejemplo |
|---|---|---|
find() | Primer elemento que coincide | soup.find("h1") |
find_all() | Todos los elementos | soup.find_all("a", class_="link") |
select_one() | Primer match CSS selector | soup.select_one("div.product > h2") |
select() | Todos con CSS selector | soup.select("ul.menu li a") |
find(attrs={}) | Por atributos | soup.find("div", attrs={"data-id": "123"}) |
# Ejemplos practicos de selectores
soup = BeautifulSoup(html, "lxml")
# Por tag y clase
products = soup.find_all("div", class_="product-card")
# CSS selector (como en el navegador)
prices = soup.select("div.product-card span.price")
# Por atributo personalizado
items = soup.find_all("div", attrs={"data-category": "electronics"})
# Texto dentro del elemento
title = element.get_text(strip=True)
# Atributo href de un link
url = link.get("href", "")
# Navegar al padre o hermano
parent = element.parent
next_sibling = element.find_next_sibling("div")
Proyecto completo: scraper de productos con paginacion
import requests
from bs4 import BeautifulSoup
import csv
import time
def scrape_products(base_url, max_pages=5):
all_products = []
for page in range(1, max_pages + 1):
url = f"{base_url}?page={page}"
print(f"Scraping pagina {page}...")
response = requests.get(url, headers={
"User-Agent": "Mozilla/5.0 (compatible; MyScraper/1.0)"
})
if response.status_code != 200:
print(f"Error en pagina {page}: {response.status_code}")
break
soup = BeautifulSoup(response.text, "lxml")
products = soup.select("div.product-item")
if not products:
print("No mas productos encontrados")
break
for product in products:
name = product.select_one("h3.name")
price = product.select_one("span.price")
rating = product.select_one("span.rating")
all_products.append({
"name": name.get_text(strip=True) if name else "N/A",
"price": price.get_text(strip=True) if price else "N/A",
"rating": rating.get_text(strip=True) if rating else "N/A",
})
# Respetar el servidor: esperar entre peticiones
time.sleep(2)
return all_products
def save_to_csv(products, filename="productos.csv"):
if not products:
print("No hay datos para guardar")
return
with open(filename, "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=products[0].keys())
writer.writeheader()
writer.writerows(products)
print(f"Guardados {len(products)} productos en {filename}")
# Ejecutar
products = scrape_products("https://example.com/products")
save_to_csv(products)
Buenas practicas y etica del scraping
Un error que cometi al principio fue no respetar las politicas de los sitios web. Estas son las reglas que todo scraper responsable debe seguir:
1. Revisa el robots.txt. Antes de scrapear un sitio, visita sitio.com/robots.txt. Si dice Disallow: /seccion/, no scrapees esa seccion.
2. Agrega delays entre peticiones. Nunca bombardees un servidor. Un time.sleep(2) entre peticiones es lo minimo responsable.
3. Identifica tu scraper. Usa un User-Agent descriptivo con tu contacto para que el administrador pueda comunicarse contigo si hay problemas.
4. Revisa los terminos de servicio. Algunos sitios prohiben explicitamente el scraping. Respeta esos terminos.
Errores comunes y soluciones
Error 1: "Connection refused" o status 403. El servidor te esta bloqueando. Solucion: agrega headers realistas (User-Agent, Accept, Accept-Language). Si persiste, el sitio probablemente bloquea scrapers activamente.
Error 2: El scraper no encuentra elementos. La pagina puede cargar contenido con JavaScript. BeautifulSoup solo ve el HTML estatico. Solucion: usa Selenium o Playwright para paginas con JavaScript.
Error 3: Datos incompletos o None. Siempre verifica que el elemento existe antes de extraer texto: element.text if element else "N/A". Las paginas web cambian estructura frecuentemente.
Error 4: Encoding roto (caracteres raros). Usa response.encoding = "utf-8" antes de parsear, o usa response.content en lugar de response.text.