Tecnologías y protecciones 8 min de lectura

Expresiones regulares para el parseo: sintaxis, patrones y límites

Expresiones regulares en el parseo de datos: sintaxis básica, patrones útiles, límites de aplicación y por qué el HTML requiere un parser y no regex.

EW
Equipo Web-Scraping.es
Recopilación de datos para las necesidades del negocio
Publicado: 27 abril 2025

Este es el tercer artículo de la serie. En «Parser del DOM» aprendimos a convertir la página en un árbol de nodos, y en el artículo sobre selectores CSS — a direccionar con precisión los elementos necesarios de ese árbol. Pero cuando el nodo ya está localizado, dentro suele haber texto «sucio»: «Precio: 12 990 € (antes 15 990)», un teléfono con caracteres de más, una referencia mezclada con la descripción. Para extraer de esa cadena justo el número o el código están las expresiones regulares.

Una expresión regular (regex) — es un patrón que describe un conjunto de cadenas. El motor de regex recorre el texto y encuentra los fragmentos que encajan con el patrón. En el scraping de sitios web es una herramienta imprescindible de limpieza final y extracción de datos.

Cuándo hacen falta las regex — y cuándo no

Primero, una advertencia importante. Con expresiones regulares no se parsea el HTML completo. El HTML es una estructura anidada y no regular; intentar desmontarla con un único patrón conduce a un código frágil e ilegible. Para navegar por el marcado existen los parsers del DOM y los selectores CSS de los artículos anteriores.

Las regex brillan en otro papel — procesar un texto corto ya extraído:

  • sacar la cifra del precio de una cadena con moneda y espacios;
  • normalizar un teléfono o un email;
  • extraer la referencia, el SKU, una fecha, el importe del descuento;
  • desmenuzar parámetros de una URL o del texto de un <script>.

La regla es simple: la estructura se toma con selectores; el valor dentro del nodo — con una regex.

Sintaxis básica

La mayoría de los caracteres de un patrón se representan a sí mismos: el patrón precio encontrará la subcadena «precio». La potencia llega con los caracteres especiales (metacaracteres).

Símbolo Significado
. cualquier carácter (salvo el salto de línea)
\d dígito 0–9
\D no dígito
\w letra, dígito o _
\W lo contrario de \w
\s carácter de espacio (espacio, tabulador, salto)
\S carácter que no es de espacio
\b límite de palabra

Para usar un metacarácter como carácter normal (por ejemplo, el punto de un número), se escapa con barra invertida: \., \$, \(.

Clases de caracteres

Los corchetes definen un conjunto de caracteres admisibles en una posición:

code
[aeiou]                  una de las vocales enumeradas
[0-9]                    cualquier dígito (lo mismo que \d)
[a-zA-Z]                 cualquier letra latina básica
[a-zA-ZáéíóúüñÁÉÍÓÚÜÑ]   cualquier letra española, con tildes y eñe
[^0-9]                   cualquier carácter SALVO un dígito (^ dentro de corchetes = negación)
[\d.,]                   un dígito, un punto o una coma

Los rangos (a-z, 0-9) y las enumeraciones se combinan. ^ al comienzo de la clase invierte el conjunto.

Cuantificadores: cuántas repeticiones

El cuantificador indica cuántas veces se repite el elemento anterior.

Cuantificador Cuántas veces
* 0 o más
+ 1 o más
? 0 o 1 (opcional)
{3} exactamente 3
{2,5} de 2 a 5
{2,} 2 o más
code
\d+               uno o más dígitos: «12990»
\d{1,3}           de uno a tres dígitos (un grupo de millares)
colou?r           «color» y «colour»

Voracidad y pereza

Por defecto, los cuantificadores son voraces — capturan todo lo posible. Un ? añadido los vuelve perezosos — capturan lo mínimo:

code
".*"              voraz: de la primera comilla a la ÚLTIMA de la cadena
".*?"             perezoso: de la primera comilla a la MÁS PRÓXIMA

Es una de las trampas más frecuentes: el patrón voraz «se come» texto de más. En la práctica, para extraer valores cortos casi siempre conviene la variante perezosa o una clase de caracteres más estrecha en lugar de ..

Anclas y límites

Las anclas no coinciden con caracteres — fijan el patrón a una posición:

code
^inicio            ^ — comienzo de la cadena
fin$               $ — final de la cadena
^\d+$              la cadena entera se compone de dígitos
\bSKU\b            «SKU» como palabra independiente, no como parte de otra

Las anclas protegen de falsos positivos: ^\d+$ garantiza que en la celda hay solo un número, y no «12 uds».

Grupos

Los paréntesis ( ) agrupan una parte del patrón y capturan el fragmento coincidente para recuperarlo después.

code
(\d+)[.,](\d+)        la parte entera y la decimal del número por separado

Los grupos se numeran de izquierda a derecha: el grupo 1 — (\d+) antes del separador; el grupo 2 — el de después.

Grupos sin captura

Si los paréntesis solo se necesitan para agrupar (por ejemplo, bajo un cuantificador) pero el resultado no interesa — se usa (?: ):

code
(?:https?://)?(\S+)   «http://» o «https://» opcional, sin guardarlo

Grupos con nombre

Para no contar números, al grupo se le da un nombre (?P<name>...) (en Python) o (?<name>...):

code
(?P<eur>\d+)[.,](?P<cent>\d{2})

Después se accede con match.group("eur") — legible y resistente a la reordenación de los grupos.

Referencias hacia atrás

Dentro del patrón se puede hacer referencia a un grupo ya capturado mediante \1, \2:

code
<(\w+)>.*?</\1>       etiqueta emparejada: la de apertura y la de cierre coinciden en el nombre
(["']).*?\1           cadena entre comillas simples o dobles

\1 exige que en la segunda posición aparezca el mismo carácter que coincidió en el primer grupo.

Comprobaciones hacia delante y hacia atrás

Las construcciones lookaround comprueban el contexto sin incluirlo en el resultado — muy útiles para «engancharse» a una etiqueta y devolver solo el valor.

Construcción Significado
(?=...) delante hay... (positive lookahead)
(?!...) delante NO hay... (negative lookahead)
(?<=...) detrás hay... (positive lookbehind)
(?<!...) detrás NO hay... (negative lookbehind)
code
\d+(?=\s*€)           número al que SIGUE el símbolo del euro — se devuelve solo el número
(?<=Precio:\s)\d+     número al que PRECEDE «Precio: » — se devuelve solo el número
\d+(?!\d)             el último dígito del número

El lookbehind resulta especialmente cómodo cuando hay que extraer el valor situado tras una etiqueta fija sin arrastrar la propia etiqueta al resultado.

Flags (modificadores)

Los flags cambian el comportamiento de todo el patrón:

Flag (Python) Efecto
re.I / (?i) sin distinguir mayúsculas y minúsculas
re.S / (?s) . coincide también con el salto de línea
re.M / (?m) ^ y $ actúan en cada línea
re.X / (?x) modo «verboso» — permite comentar el patrón
python
re.findall(r"precio", text, re.I)   # «Precio», «PRECIO», «precio»

Ejemplos prácticos para el scraping

Reunamos los patrones típicos en Python (módulo re). Funciones útiles: re.search (primera coincidencia), re.findall (todas, como lista), re.sub (sustitución), re.finditer (iterador con grupos).

El precio desde una cadena «sucia»:

python
import re

text = "Precio: 12 990,50 €  (antes 15 990 €)"

# Extraemos el primer número ignorando los espacios que separan los millares
m = re.search(r"(\d[\d\s]*\d|\d)(?:[.,](\d{2}))?", text)
eur = re.sub(r"\s", "", m.group(1))     # «12990»
cent = m.group(2) or "00"               # «50»
price = float(f"{eur}.{cent}")          # 12990.5

Todos los precios del texto (el nuevo y el antiguo):

python
prices = re.findall(r"\d[\d\s]*\d(?=\s*€)", text)
# ['12 990', '15 990']  → después los limpiamos de espacios
clean = [int(re.sub(r"\D", "", p)) for p in prices]   # [12990, 15990]

Referencia / SKU:

python
sku = re.search(r"\bReferencia[:\s]+([A-Z0-9\-]+)", text)

Teléfono (normalización a dígitos):

python
digits = re.sub(r"\D", "", "+34 912 345 678")   # «34912345678»

Email:

python
emails = re.findall(r"[\w.+-]+@[\w-]+\.[\w.-]+", text)

Extraer un valor JSON de un script inline:

python
# En la página de producto, el precio suele estar en un <script> como window.__DATA__ = {...}
m = re.search(r'"price"\s*:\s*"?(\d+(?:\.\d+)?)"?', script_text)

Las regex en combinación con el DOM y CSS

Donde más rinde la regex no es en lugar de las herramientas de los artículos anteriores, sino junto a ellas:

python
from bs4 import BeautifulSoup
import re

soup = BeautifulSoup(html, "html.parser")

# 1) El selector CSS obtiene el nodo necesario
raw = soup.select_one("span.price").get_text(strip=True)

# 2) La regex limpia el texto hasta dejar el número
price = int(re.sub(r"\D", "", raw))

El selector responde al «dónde»; la regex — al «qué exactamente de ese texto». Esta separación hace que el scraper sea a la vez preciso y legible.

Aplicación en la monitorización de precios

En la monitorización de precios de tiendas online, las expresiones regulares cubren la «última milla» de la extracción de datos:

  • Normalización del precio a un formato numérico único: quitar los espacios separadores y la moneda, y convertir la coma en punto — de lo contrario es imposible comparar los valores de distintas plataformas.
  • Desglose del descuento en cadenas como «−23 %» o «ahorro de 3000 €».
  • Limpieza de la disponibilidad: de «quedan 4 uds.» extraer la cantidad \d+.
  • Identificadores de producto — SKU, EAN/código de barras (\b\d{13}\b), referencia — para el emparejamiento de un mismo producto entre distintos vendedores.
  • Datos desde los scripts: muchas tiendas guardan el precio en un JSON dentro de <script type="application/ld+json"> o en variables inline — la regex extrae el campo necesario sin un parseo completo del JS.

La combinación «selector → regex» es la base operativa del motor de scraping de sitios web: recorre las fichas de producto según un calendario, extrae los nodos por CSS y convierte su contenido en números limpios mediante expresiones regulares.

Rendimiento y escollos

  • Compile los patrones de uso frecuentere.compile(...) una sola vez, en lugar de volver a analizar el patrón en un bucle sobre miles de productos.
  • Evite el backtracking catastrófico. Los cuantificadores ambiguos anidados como (\d+)+ o (.*)* pueden «colgarse» con una cadena elegida a propósito (o por casualidad). Mantenga estrechas las clases de caracteres y prefiera los cuantificadores perezosos a los voraces cuando proceda.
  • No complique de más. Si el patrón se ha vuelto ilegible, divida la tarea en dos regex simples o vuelva a los selectores: quizá el valor buscado esté en un atributo data- aparte que resulta más fácil tomar directamente (vea el artículo sobre selectores CSS).
  • Pruebe con ejemplos reales. Los precios se escriben de mil maneras: 1 990, 1.990, 1990,00, desde 1990. Pase el patrón por un conjunto de cadenas reales de distintas tiendas.

Conclusión

Las expresiones regulares son una herramienta para extraer valores del texto, no para desmontar estructuras. Los metacaracteres y las clases describen qué caracteres se esperan; los cuantificadores — cuántos; los grupos y el lookaround — cómo aislar el fragmento buscado en su contexto. En pareja con el parseo del DOM y los selectores CSS forman la cadena completa del scraping de sitios web: encontrar el nodo, extraer el texto, convertirlo en datos limpios. Esa combinación es justamente la que sostiene una monitorización de precios de tiendas online fiable, donde de la maquetación dispar de los competidores hay que obtener con regularidad cifras comparables.

Con esto queda cerrada la serie básica sobre herramientas: el DOM aporta el árbol; los selectores CSS — el direccionamiento; las expresiones regulares — la limpieza final de los datos.