▷ Regex en Python explicado — `re` con ejemplos que se entienden 2026

Regex en Python explicado — `re` con ejemplos que se entienden

Las expresiones regulares (regex) son una de esas herramientas que parecen jeroglíficos hasta que las usas. La primera vez que ves algo como r"d{4}-d{2}-d{2}" piensas que alguien estornudó sobre el teclado. Pero una vez entiendes la lógica, son la herramienta más potente que tiene cualquier programador para tratar texto: validar emails, extraer datos de logs, parsear HTML “rápido y sucio”, encontrar y reemplazar patrones complejos.

En esta entrada te enseño regex en Python con el módulo re: los conceptos clave (clases de caracteres, cuantificadores, grupos), las cinco funciones de re que cubren el 95% de los casos, ejemplos reales con código que se ejecuta tal cual, y los errores típicos que confunden cuando empiezas.

La idea en 30 segundos

Un patrón regex es una mini-fórmula de búsqueda en texto. En vez de buscar la palabra exacta "hola", puedes buscar:

  • “una cifra de 4 dígitos” → d{4}
  • “una palabra que empieza con mayúscula” → [A-Z]w*
  • “un email” → [w.-]+@[w.-]+.w+

El módulo re de Python (estándar, no instalas nada) te permite buscar, validar, extraer y reemplazar con esos patrones.

import re

texto = "Hoy es 19/04/2026 y el evento es el 25/04/2026."
fechas = re.findall(r"d{2}/d{2}/d{4}", texto)
# ['19/04/2026', '25/04/2026']

Las 5 funciones de re que necesitas

FunciónPara qué
re.search(patron, texto)Busca la primera coincidencia. Devuelve Match o None.
re.match(patron, texto)Como search pero solo desde el principio del texto.
re.findall(patron, texto)Devuelve lista con todas las coincidencias.
re.finditer(patron, texto)Como findall pero devuelve un iterador de Match (con posiciones).
re.sub(patron, reemplazo, texto)Sustituye coincidencias por otra cosa.

Una más útil: re.compile(patron) te devuelve un objeto reutilizable. Si vas a usar el mismo patrón varias veces, compila una vez y reutiliza.

Bloques básicos del lenguaje regex

Clases de caracteres

PatrónSignifica
dCualquier dígito 0-9
DCualquier carácter que NO sea dígito
wLetra, dígito o guion bajo
WLo opuesto
sEspacio en blanco (espacio, tab, newline)
SLo opuesto
.Cualquier carácter (excepto nueva línea)
[abc]“a”, “b” o “c”
[a-z]Cualquier letra minúscula
[^abc]Cualquier carácter EXCEPTO a, b o c

Cuantificadores

PatrónSignifica
*Cero o más veces
+Una o más veces
?Cero o una vez (opcional)
{N}Exactamente N veces
{N,M}Entre N y M veces
{N,}N o más veces

Anclas

PatrónSignifica
^Inicio del texto
$Final del texto
bFrontera de palabra

Agrupación

PatrónSignifica
(...)Grupo capturado (puedes recuperarlo)
(?:...)Grupo NO capturado (solo agrupa)
(?P<nombre>...)Grupo con nombre
|“O” (alternativa)

💡 Truco: usa siempre raw strings (r"...") para los patrones. Sin eso, Python interpreta los d, s como sequences de escape suyas y te tocaría escribir \d, \s, etc.

📥 Llévate el cheatsheet de Python (gratis)

PDF de 6 páginas con lo esencial: tipos, condicionales, bucles, estructuras de datos, funciones y los errores que más vas a cometer. Para tener al lado mientras programas.

Sin spam. Te apuntas a la lista, descargas el cheatsheet y recibes contenido de Python cada semana.

Validar (re.search)

Caso clásico: comprobar si una cadena cumple un patrón.

import re

email = "ana@elpythonista.com"
if re.search(r"^[w.-]+@[w.-]+.w+$", email):
    print("Email válido")
else:
    print("Email inválido")

re.search devuelve un objeto Match si encuentra (truthy) o None si no (falsy). Por eso lo puedes usar directamente en un if.

⚠️ Validar emails con regex es famoso por ser un agujero negro. El RFC 5322 oficial es brutalmente complicado. Para casos reales, usa una librería como email-validator. Para validaciones de juguete, basta con un patrón simple.

Buscar y extraer (re.findall)

Para sacar todos los matches de un texto:

texto = """
Tu pedido #12345 está confirmado. 
Total: 49.99 €. 
Cliente ID: 7890.
"""

numeros = re.findall(r"d+", texto)
# ['12345', '49', '99', '7890']

# Filtrando con grupos:
mencionados = re.findall(r"#(d+)", texto)
# ['12345']  ← solo el grupo capturado

Cuando hay grupos en el patrón, findall te devuelve solo el contenido de los grupos.

Grupos con nombre

Súper útil cuando extraes varios campos a la vez:

import re

linea = "2026-04-19 15:30:12 ERROR Conexión rechazada"

m = re.search(
    r"(?P<fecha>d{4}-d{2}-d{2}) (?P<hora>d{2}:d{2}:d{2}) (?P<nivel>w+)",
    linea,
)
print(m.group("fecha"))   # 2026-04-19
print(m.group("hora"))    # 15:30:12
print(m.group("nivel"))   # ERROR
print(m.groupdict())      # {'fecha': '2026-04-19', 'hora': '15:30:12', 'nivel': 'ERROR'}

(?P<nombre>patron) define un grupo con nombre. Lo recuperas con m.group("nombre") o todos juntos con m.groupdict(). Es muchísimo más legible que andar contando posiciones (m.group(1), m.group(2)…).

💡 ¿Quieres procesar logs reales con fechas? Combínalo con manejar fechas en Python: primero extraes con regex, luego conviertes con datetime.strptime.

Sustituir (re.sub)

Buscar y reemplazar con poder:

texto = "Llama al 600-123-456 o al 900 999 888."

# Censurar números de teléfono
censurado = re.sub(r"[d-]{6,}", "[TELÉFONO]", texto)
# 'Llama al [TELÉFONO] o al 900 999 888.'  ← falló porque hay espacios

# Mejor:
censurado = re.sub(r"d{3}[-s]?d{3}[-s]?d{3}", "[TELÉFONO]", texto)
# 'Llama al [TELÉFONO] o al [TELÉFONO].'

Con grupos, puedes referenciar partes del match en el reemplazo con 1, 2…:

# Cambiar formato de fechas: 19/04/2026 → 2026-04-19
texto = "El evento es el 19/04/2026."
nuevo = re.sub(r"(d{2})/(d{2})/(d{4})", r"3-2-1", texto)
# 'El evento es el 2026-04-19.'

Tip-friki: el reemplazo también puede ser una función. re.sub(patron, funcion, texto): la función recibe el match y devuelve el string a usar.

Compilar para reutilizar

Si vas a aplicar el mismo patrón muchas veces (procesar 10 millones de líneas, por ejemplo), compila una sola vez:

import re

PATRON_EMAIL = re.compile(r"^[w.-]+@[w.-]+.w+$")

emails_validos = [e for e in lista_emails if PATRON_EMAIL.search(e)]

Compilado una sola vez, evitas que Python recompile en cada llamada. Diferencia notable solo en bucles grandes.

Casos reales típicos

Extraer todos los emails de un texto

import re

texto = "Contacta con ana@elpythonista.com o luis@gmail.com (o info@elpythonista.com)."
emails = re.findall(r"[w.-]+@[w.-]+.w+", texto)
# ['ana@elpythonista.com', 'luis@gmail.com', 'info@elpythonista.com']

Limpiar espacios duplicados

texto = "Esto    tiene  muchos     espacios."
limpio = re.sub(r"s+", " ", texto).strip()
# 'Esto tiene muchos espacios.'

Quitar etiquetas HTML “rápido y sucio”

html = "<p>Hola <b>mundo</b></p>"
sin_tags = re.sub(r"<[^>]+>", "", html)
# 'Hola mundo'

⚠️ Para HTML real usa BeautifulSoup. Regex sirve para casos simples y controlados. HTML del mundo real tiene comentarios, scripts, atributos con > dentro de strings, etc. — fuera del alcance razonable de regex.

Validar formato de DNI español

def dni_valido(dni: str) -> bool:
    return bool(re.match(r"^d{8}[A-HJ-NP-TV-Z]$", dni.upper()))

dni_valido("12345678Z")   # True
dni_valido("12345678I")   # False (la I no es válida en DNI)
dni_valido("12345A")       # False

Capturar capítulo y minuto de un timestamp tipo 01:23:45

import re

m = re.search(r"(?P<h>d{2}):(?P<m>d{2}):(?P<s>d{2})", "Empieza a 01:23:45")
total_segundos = int(m["h"]) * 3600 + int(m["m"]) * 60 + int(m["s"])
# 5025

⚡ Desde Python 3.6 puedes usar m["nombre"] directamente, no hace falta m.group("nombre").

Flags útiles

Modifican cómo se aplica el patrón:

import re

# Case-insensitive
re.search(r"python", "PYTHON ROCKS", re.IGNORECASE)   # match

# Multilínea: ^ y $ aplican a cada línea
re.findall(r"^- (.+)$", lista_md, re.MULTILINE)

# Permitir comentarios y espacios en el patrón (legibilidad)
patron = re.compile(r"""
    d{4}     # año
    -
    d{2}     # mes
    -
    d{2}     # día
""", re.VERBOSE)

Errores típicos al usar regex

# 1. Olvidar el raw string
re.search("d+", texto)     # ❌ Python interpreta d como escape (DeprecationWarning)
re.search(r"d+", texto)    # ✓

# 2. Confundir search vs match
re.match(r"mundo", "Hola mundo")     # None — match solo busca al inicio
re.search(r"mundo", "Hola mundo")    # ✓

# 3. Olvidar escapar caracteres especiales
re.search(r".", "a.b")     # match con CUALQUIER carácter
re.search(r".", "a.b")    # match solo con punto literal

# 4. Greedy vs lazy
re.search(r"<.+>", "<b>bold</b>").group()
# '<b>bold</b>'   ← greedy: pilla todo desde < hasta >

re.search(r"<.+?>", "<b>bold</b>").group()
# '<b>'   ← lazy con ?: lo mínimo posible

# 5. Anclas mal puestas
re.search(r"^python$", "Adoro python")   # None — el ^...$ exige texto entero
re.search(r"bpythonb", "Adoro python")  # ✓ palabra entera dentro del texto

# 6. Confiar en regex para validar emails / URLs / HTML serios
# Para email: librería `email-validator`
# Para HTML: `BeautifulSoup`
# Para URL: `urllib.parse`

Resumen

Para…Función
Validarre.search(patron, texto) (truthy/falsy)
Buscar todosre.findall(patron, texto)
Iterar con posicionesre.finditer(patron, texto)
Sustituirre.sub(patron, reemplazo, texto)
Reutilizarpatron = re.compile(...)
Recordatorios claveraw strings (r"..."); grupos con nombre (?P<n>...); ojo con greedy/lazy

¿Te ha valido esto?

Si te ha resultado útil, llévate el cheatsheet de Python en PDF — 6 páginas con tipos, condicionales, bucles, estructuras de datos, funciones y los errores típicos. Para tener al lado mientras programas. Gratis.

Sin spam. Email + cheatsheet + un correo por semana con tutoriales nuevos.

Tu siguiente paso

Si has llegado hasta aquí, las regex han dejado de ser garabatos para ti. La próxima vez que tengas que validar un input, extraer datos de un log o limpiar texto sucio, sabes con qué pieza atacar el problema. Y cuando veas un patrón complicado en código ajeno, tienes las claves para descomponerlo paso a paso.

Si quieres aprender Python desde la base hasta proyectos reales con tratamiento de texto, datos y APIs, en El Pythonista lo enseño paso a paso.

Un abrazo,
Oscar

¿Quieres aprender Python en orden, no a saltos?

Esto que has leído es solo una pieza. En El Pythonista lo verás todo encadenado: 11 módulos, 37+ horas de vídeo, 734 actividades y un proyecto real (MovieTracker) que crece contigo desde la primera variable hasta el deploy a producción.

Ver el curso completo →

37+ horas · 734 actividades · Proyecto real · Acceso de por vida · 14 días de garantía

Compartir

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Publicar un comentario