Web Scraping con Python: Requests, BeautifulSoup y XPath

Anatomía de las peticiones web

Para capturar datos renderizados por el cliente, se pueden usar herramientas de captura de paquetes:

F12 (o clic derecho → Inspeccionar) → Network → aquí se pueden encontrar tanto la estrutcura como los datos. Usa Headers y Preview para examinarlos.

Protocolo HTTP

Un protocolo es un acuerdo formal entre dos computadores para la transmisión de datos. HTTP (HyperText Transfer Protocol) se utiliza para transferir HTML y otros contenidos hipermediales.

Componentes principales:

1. Petición (Request):

  • Línea de petición: método (GET/POST), URL, protocolo
  • Encabezados: información adicional requerida por el servidor (usado para anti-scraping)
  • Cuerpo: parámetros de la petición (como términos de búsqueda)

2. Respuesta (Response):

  • Línea de estado: protocolo, código de estado (200, 404, etc.)
  • Encabezados: información adicional para el cliente
  • Cuerpo: contenido real devuelto (HTML, JSON, etc.)

Encabezados de petición importantes para scraping:

  • User-Agent: identificador del cliente (navegador, móvil, desktop)
  • Referer: prevención de hotlinking (de dónde proviene la petición)
  • Cookie: datos de sesión del usuario (tokens, info de login)

Métodos de petición:

  • GET: envío visible de datos, para consultas
  • POST: envío oculto, para modificar datos del servidor o subir archivos

Librería Requests

Requests permite a Python enviar peticiones HTTP fácilmente y interactuar con servidores web. Se puede usar para obtener contenido de páginas, enviar formularios, descargar archivos, consumir APIs, etc.

Métodos principales

respuesta = requests.get(direccion_url, headers=cabeceras, verify=True/False)

Realiza una petición GET y devuelve un objeto de respuesta. Sus propiedades más útiles son:

Propiedad Descripción
encoding Consultar o establecer la codificación de caracteres
status_code Código HTTP de respuesta
url URL consultada
headers Encabezados de respuesta
cookies Información de cookies
text Contenido como cadena de texto (código fuente HTML)
content Contenido como bytes (para descargar imágenes/archivos)

Peticiones GET

Para evitar bloqueos básicos, se debe simular ser un navegador:

import requests

cabeceras = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
}

enlace = "https://www.ejemplo.com"
resultado = requests.get(enlace, headers=cabeceras)
print(resultado.status_code)
print(resultado.text)

Peticiones POST

Por ejemplo, para consultar un servicio de traducción:

import requests

palabra = input("Introduce una palabra: ")
datos_form = {"query": palabra}
url_api = "https://api.ejemplo-traduccion.com/translate"

cabeceros = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
}

respuesta = requests.post(url_api, data=datos_form, headers=cabeceros)
print(respuesta.json())
respuesta.close()

Es importante cerrar la respuesta con respuesta.close() para evitar problemas cuando se realizan muchas peticiones.

Parámetros en URL (Query String)

En peticiones GET, los parámetros se envían en la URL después de ?. Requests permite encapsularlos en un diccionario:

import requests

parametros_busqueda = {
    "categoria": "comedia",
    "rango": "100:90",
    "accion": "",
    "inicio": 0,
    "limite": 20
}

cabeceros = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
}

url_base = "https://api.ejemplo-peliculas.com/lista"
respuesta = requests.get(url_base, params=parametros_busqueda, headers=cabeceros)
print(respuesta.json())

Codificación de caracteres y problemas de codificación

Python utiliza Unicode internamente, pero para almacenamiento y transmisión se necesita codificar (UTF-8, GBK, etc.). El charset por defecto depende del sistema operativo: macOS usa UTF-8, Windows usa GBK.

Es crucial revisar el charset del sitio web (en los encabezados) y especificar el mismo al abrir archivos:

with open("datos.html", "w", encoding="utf-8") as f:
    f.write(respuesta.text)

Análisis de datos

Expresiones regulares (Re)

Las expresiones regulares permiten buscar patrones en cadenas de texto.

Metacaracteres básicos:

  • . - Cualquier carácter excepto salto de línea
  • \w - Letras, números, guion bajo
  • \s - Espacios en blanco
  • \d - Dígitos
  • ^ - Inicio de cadena
  • $ - Fin de cadena
  • [...] - Conjunto de caracteres
  • [^...] - Negación del conjunto

Cuantificadores:

  • * - 0 o más veces
  • + - 1 o más veces
  • ? - 0 o 1 vez
  • {n} - Exactamente n veces
  • {n,} - n o más veces
  • {n,m} - Entre n y m veces

Modo greedy vs lazy:

  • .* - Greedy: captura la cadena más larga posible
  • .*? - Lazy: captura la cadena más corta posible

Módulo re de Python:

import re

# findall: devuelve lista con todas las coincidencias
coincidencias = re.findall(r"\d+", "Mi teléfono es 5551234, el suyo 9876543")

# finditer: devuelve iterador de objetos Match
iterador_matches = re.finditer(r"\d+", "Códigos: 100, 200, 300")
for match in iterador_matches:
    print(match.group())

# search: encuentra la primera coincidencia
primer_match = re.search(r"\d+", "Hay 42 gatos y 7 perros")
if primer_match:
    print(primer_match.group())

# match: debe coincidir desde el inicio
match_inicio = re.match(r"\d+", "12345 empieza aquí")
if match_inicio:
    print(match_inicio.group())

# compile: precompila el patrón para reutilizarlo
patron = re.compile(r"\d{4}")
resultados = patron.finditer("Años: 2020, 2021, 2022")
for r in resultados:
    print(r.group())

BeautifulSoup4 (bs4)

BeautifulSoup analiza documentos HTML y XML, creando un árbol de elementos navegable.

Sintaxis HTML: <etiqueta atributo="valor">contenido</etiqueta>

Un elemento puede tener múltiples atributos, lo que permite localizar contenido específico.

Ejemplo de uso:

from bs4 import BeautifulSoup
import requests

cabeceros = {"User-Agent": "Mozilla/5.0"}
respuesta = requests.get("https://ejemplo.com", headers=cabeceros)

sopa = BeautifulSoup(respuesta.text, "html.parser")

# Encontrar por etiqueta
titulos = sopa.find_all("h2")
for titulo in titulos:
    print(titulo.text)

# Encontrar por clase CSS
articulos = sopa.find_all("div", class_="producto")
for articulo in articulos:
    nombre = articulo.find("span", class_="nombre").text
    precio = articulo.find("span", class_="precio").text
    print(f"{nombre}: {precio}")

XPath

XPath es un lenguaje para navegar y buscar contenido en documentos XML. Como HTML es un subconjunto de XML, XPath funciona también para HTML.

En XML, cada etiqueta es un nodo. Los nodos pueden ser: padre, hijo, o hermano según su jerarquía.

Instalación de lxml:

pip install lxml

Sintaxis básica de XPath:

  • / - Selecciona desde el nodo raíz
  • // - Selecciona nodos en cualquier posición
  • @ - Selecciona atributos
  • [ ] - Predicados para filtrar

Ejemplo con lxml:

from lxml import etree
import requests

cabeceros = {"User-Agent": "Mozilla/5.0"}
respuesta = requests.get("https://ejemplo.com", headers=cabeceros)

arbol = etree.HTML(respuesta.text)

# Seleccionar todos los enlaces
enlaces = arbol.xpath("//a/@href")
for enlace in enlaces:
    print(enlace)

# Seleccionar texto dentro de un div con clase específica
titulos = arbol.xpath('//div[@class="titulo"]/text()')
for titulo in titulos:
    print(titulo.strip())

# Usar predicados para filtrar
primer_parrafo = arbol.xpath("//p[1]/text()")
print(primer_parrafo)

Requests avanzado

Manejo de Cookies y Sesiones

Para sitios que requieren autenticación, es necesario gestionar cookies. Una sesión mantiene las cookies entre múltiples peticiones:

import requests

# Crear sesión que mantiene cookies automáticamente
sesion = requests.Session()

cabeceros = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
}

# 1. Login: obtener cookie de sesión
datos_login = {
    "usuario": "mi_usuario",
    "clave": "mi_password"
}
respuesta_login = sesion.post("https://ejemplo.com/login", data=datos_login, headers=cabeceros)

# 2. La cookie se mantiene automáticamente en la sesión
respuesta_datos = sesion.get("https://ejemplo.com/perfil", headers=cabeceros)
print(respuesta_datos.text)

sesion.close()

Prevención de hotlinking (Referer)

Algunos sitios verifican de dónde proviene la petición usando el encabezado Referer:

cabeceros = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
    "Referer": "https://ejemplo.com/pagina-origen"
}
respuesta = requests.get("https://ejemplo.com/video.mp4", headers=cabeceros)
with open("video.mp4", "wb") as f:
    f.write(respuesta.content)

Uso de proxies

Para evitar bloqueos de IP:

proxies = {
    "http": "http://usuario:password@proxy.ejemplo.com:8080",
    "https": "https://usuario:password@proxy.ejemplo.com:8080"
}
respuesta = requests.get("https://ejemplo.com", proxies=proxies, headers=cabeceros)
print(respuesta.text)

Etiquetas: Python requests BeautifulSoup XPath Web Scraping

Publicado el 7-1 00:13