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.
Contenido
- 1 La idea en 30 segundos
- 2 Las 5 funciones de re que necesitas
- 3 Bloques básicos del lenguaje regex
- 4 Validar (re.search)
- 5 Buscar y extraer (re.findall)
- 6 Grupos con nombre
- 7 Sustituir (re.sub)
- 8 Compilar para reutilizar
- 9 Casos reales típicos
- 10 Flags útiles
- 11 Errores típicos al usar regex
- 12 Resumen
- 13 Tu siguiente paso
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ón | Para 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ón | Significa |
|---|---|
d | Cualquier dígito 0-9 |
D | Cualquier carácter que NO sea dígito |
w | Letra, dígito o guion bajo |
W | Lo opuesto |
s | Espacio en blanco (espacio, tab, newline) |
S | Lo 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ón | Significa |
|---|---|
* | 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ón | Significa |
|---|---|
^ | Inicio del texto |
$ | Final del texto |
b | Frontera de palabra |
Agrupación
| Patrón | Significa |
|---|---|
(...) | 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 losd,scomo 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 faltam.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 |
|---|---|
| Validar | re.search(patron, texto) (truthy/falsy) |
| Buscar todos | re.findall(patron, texto) |
| Iterar con posiciones | re.finditer(patron, texto) |
| Sustituir | re.sub(patron, reemplazo, texto) |
| Reutilizar | patron = re.compile(...) |
| Recordatorios clave | raw 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.
37+ horas · 734 actividades · Proyecto real · Acceso de por vida · 14 días de garantía
