“Algunas personas, cuando se enfrentan a un problema, piensan: ‘Ya sé, usaré expresiones regulares.’ Ahora tienen dos problemas.”
— Jamie Zawinski
Expresión Regular (Regex)
Una expresión regular es una secuencia de caracteres que define un patrón de búsqueda. Se utiliza para encontrar, extraer o reemplazar texto que coincida con dicho patrón.
¿Por qué aprender Regex?
Casos de uso comunes:
🔍 Buscar patrones en texto
✅ Validar formatos (email, teléfono)
🧹 Limpiar y normalizar datos
📊 Extraer información estructurada
🔄 Reemplazar texto masivamente
En NLP específicamente:
Tokenización personalizada
Extracción de entidades simples
Preprocesamiento de corpus
Normalización de texto
Filtrado de ruido
Historia Breve
Code
timeline title Historia de las Expresiones Regulares 1951 : Stephen Kleene : Álgebra de conjuntos regulares 1968 : Ken Thompson : Implementación en editor de texto 1970s : grep, sed, awk : Herramientas Unix 1980s : Perl : "Practical Extraction and Report Language" 1990s : PCRE : Perl Compatible Regular Expressions 2000s : Integración universal : Python, Java, JavaScript, etc.
timeline
title Historia de las Expresiones Regulares
1951 : Stephen Kleene
: Álgebra de conjuntos regulares
1968 : Ken Thompson
: Implementación en editor de texto
1970s : grep, sed, awk
: Herramientas Unix
1980s : Perl
: "Practical Extraction and Report Language"
1990s : PCRE
: Perl Compatible Regular Expressions
2000s : Integración universal
: Python, Java, JavaScript, etc.
Sintaxis Básica
Caracteres Literales
El caso más simple: buscar texto exacto.
import retexto ="El gato y el perro juegan en el jardín"# Buscar la palabra "gato"resultado = re.search(r"gato", texto)print(f"Texto: '{texto}'")print(f"Patrón: 'gato'")print(f"Encontrado: '{resultado.group()}' en posición {resultado.start()}")
Texto: 'El gato y el perro juegan en el jardín'
Patrón: 'gato'
Encontrado: 'gato' en posición 3
El prefijo r
En Python, usamos r"patrón" (raw string) para evitar problemas con caracteres de escape como \n o \t.
Metacaracteres Especiales
Metacaracter
Significado
Ejemplo
Coincide con
.
Cualquier carácter (excepto \n)
c.t
cat, cut, c9t
^
Inicio de línea/cadena
^Hola
“Hola mundo”
$
Fin de línea/cadena
mundo$
“Hola mundo”
\d
Dígito [0-9]
\d\d
42, 99
\w
Palabra [a-zA-Z0-9_]
\w+
palabra_123
\s
Espacio en blanco
\s+
” “,”, “”
\b
Límite de palabra
\bcat\b
“cat” pero no “category”
Ejemplo: Metacaracteres
import retexto ="Mi teléfono es 591-78945612 y mi código es A123"# \d+ encuentra secuencias de dígitosdigitos = re.findall(r"\d+", texto)print(f"Dígitos encontrados: {digitos}")# \w+ encuentra secuencias de caracteres de palabrapalabras = re.findall(r"\w+", texto)print(f"Palabras encontradas: {palabras}")# Buscar patrón específico: código alfanuméricocodigo = re.search(r"[A-Z]\d{3}", texto)print(f"Código encontrado: {codigo.group()}")
Los paréntesis crean grupos de captura que permiten:
Agrupar elementos para aplicar cuantificadores
Extraer partes específicas del match
Reutilizar el contenido capturado
import retexto ="Mi email es juan.perez@ucb.edu.bo y también tengo maria@gmail.com"# Capturar partes del emailpatron =r"(\w+[\w.]*\w+)@(\w+\.\w+(?:\.\w+)?)"emails = re.findall(patron, texto)for usuario, dominio in emails:print(f"Usuario: {usuario}, Dominio: {dominio}")
Podemos nombrar los grupos para mayor claridad con (?P<nombre>...):
import refecha ="Hoy es 04/02/2026 y mañana será 05/02/2026"# Grupos nombradospatron =r"(?P<dia>\d{2})/(?P<mes>\d{2})/(?P<año>\d{4})"matches = re.finditer(patron, fecha)for match in matches:print(f"Fecha: {match.group()}")print(f" Día: {match.group('dia')}")print(f" Mes: {match.group('mes')}")print(f" Año: {match.group('año')}")print()
A veces necesitamos agrupar sin capturar. Usamos (?:...):
import retexto ="http://ucb.edu.bo y https://google.com"# (?:...) agrupa pero NO capturapatron =r"(?:https?://)(\w+\.\w+(?:\.\w+)?)"dominios = re.findall(patron, texto)print(f"Solo dominios (sin protocolo): {dominios}")
Solo dominios (sin protocolo): ['ucb.edu.bo', 'google.com']
¿Cuándo usar (?:...)?
Cuando necesitas agrupar para aplicar un cuantificador o alternancia, pero no te interesa capturar ese grupo.
Regex en Python
El módulo re
Python incluye el módulo re para trabajar con expresiones regulares:
Función
Descripción
re.search(p, s)
Busca la primera coincidencia
re.match(p, s)
Busca solo al inicio de la cadena
re.findall(p, s)
Devuelve lista de todas las coincidencias
re.finditer(p, s)
Devuelve iterador de objetos Match
re.sub(p, r, s)
Reemplaza coincidencias
re.split(p, s)
Divide la cadena por el patrón
re.compile(p)
Compila el patrón para reutilizar
re.search() vs re.match()
import retexto ="Python es genial"# search() busca en cualquier parteresultado_search = re.search(r"es", texto)print(f"search('es'): {resultado_search.group() if resultado_search else'No encontrado'}")# match() solo busca al INICIOresultado_match = re.match(r"es", texto)print(f"match('es'): {resultado_match.group() if resultado_match else'No encontrado'}")# match() funciona si el patrón está al inicioresultado_match2 = re.match(r"Python", texto)print(f"match('Python'): {resultado_match2.group() if resultado_match2 else'No encontrado'}")
search('es'): es
match('es'): No encontrado
match('Python'): Python
re.findall() y re.finditer()
import retexto ="Precios: $100, $250, $75 y $1000"# findall() devuelve lista de stringsprecios_str = re.findall(r"\$(\d+)", texto)print(f"findall(): {precios_str}")# finditer() devuelve objetos Match con más informaciónprint("\nfinditer():")for match in re.finditer(r"\$(\d+)", texto):print(f" '{match.group()}' en posición {match.start()}-{match.end()}, valor: {match.group(1)}")
findall(): ['100', '250', '75', '1000']
finditer():
'$100' en posición 9-13, valor: 100
'$250' en posición 15-19, valor: 250
'$75' en posición 21-24, valor: 75
'$1000' en posición 27-32, valor: 1000
re.sub() - Sustitución
import retexto ="Mi teléfono es 591-78945612 y mi CI es 12345678"# Censurar números de teléfonocensurado = re.sub(r"\d{3}-\d{8}", "[TELÉFONO OCULTO]", texto)print(f"Censurado: {censurado}")# Usar grupos en el reemplazo con \1, \2, etc.fecha ="2026-02-04"fecha_formato = re.sub(r"(\d{4})-(\d{2})-(\d{2})", r"\3/\2/\1", fecha)print(f"Fecha reformateada: {fecha_formato}")
Censurado: Mi teléfono es [TELÉFONO OCULTO] y mi CI es 12345678
Fecha reformateada: 04/02/2026
re.compile() - Compilación
Cuando usamos el mismo patrón múltiples veces, es más eficiente compilarlo:
import re# Compilar el patrón UNA vezpatron_email = re.compile(r"\b[\w.]+@[\w.]+\.\w{2,}\b", re.IGNORECASE)textos = ["Contacto: admin@UCB.edu.bo","No hay email aquí","Escribe a soporte@gmail.com o ventas@empresa.com"]for texto in textos: emails = patron_email.findall(texto)print(f"En '{texto[:30]}...': {emails if emails else'ninguno'}")
En 'Contacto: admin@UCB.edu.bo...': ['admin@UCB.edu.bo']
En 'No hay email aquí...': ninguno
En 'Escribe a soporte@gmail.com o ...': ['soporte@gmail.com', 'ventas@empresa.com']
Flags (Banderas)
Flag
Abreviatura
Descripción
re.IGNORECASE
re.I
Ignora mayúsculas/minúsculas
re.MULTILINE
re.M
^ y $ aplican a cada línea
re.DOTALL
re.S
. también coincide con \n
re.VERBOSE
re.X
Permite comentarios en el patrón
import retexto ="Python\nPYTHON\npython"# Sin flag: solo encuentra exactoprint(f"Sin flag: {re.findall(r'python', texto)}")# Con IGNORECASEprint(f"IGNORECASE: {re.findall(r'python', texto, re.IGNORECASE)}")
Sin flag: ['python']
IGNORECASE: ['Python', 'PYTHON', 'python']
Aplicaciones en NLP
1. Tokenización con Regex
import retexto ="¡Hola! ¿Cómo estás? Bien, gracias :) #NLP @usuario"# Tokenización simple por espacios (NO ideal)tokens_simple = texto.split()print(f"Split simple: {tokens_simple}")# Tokenización con regex (mejor)patron_token =r"\w+|[^\w\s]"tokens_regex = re.findall(patron_token, texto)print(f"Regex tokens: {tokens_regex}")# Incluyendo hashtags y menciones como tokens únicospatron_social =r"[@#]\w+|\w+|[^\w\s]"tokens_social = re.findall(patron_social, texto)print(f"Social tokens: {tokens_social}")
Original:
¡¡¡Hola!!! ¿¿Qué tal??
Visita https://ejemplo.com para más info...
Contacto: admin@mail.com 📧
Limpio:
¡Hola! ¿Qué tal? Visita para más info. Contacto
3. Extracción de Entidades Simples
import retexto ="""El paciente Juan Pérez (CI: 12345678) tiene cita el 15/03/2026.Contactar al Dr. García al 591-71234567.Dirección: Av. 6 de Agosto #1234, La Paz."""# Extraer fechasfechas = re.findall(r"\d{2}/\d{2}/\d{4}", texto)print(f"Fechas: {fechas}")# Extraer teléfonostelefonos = re.findall(r"\d{3}-\d{8}", texto)print(f"Teléfonos: {telefonos}")# Extraer CI (Carnet de Identidad)ci = re.findall(r"CI:\s*(\d{7,8})", texto)print(f"CI: {ci}")# Extraer nombres propios (simplificado: palabra capitalizada)nombres = re.findall(r"\b[A-ZÁÉÍÓÚ][a-záéíóú]+(?:\s+[A-ZÁÉÍÓÚ][a-záéíóú]+)*\b", texto)print(f"Posibles nombres: {nombres}")
Escribe una función que extraiga todos los hashtags de un texto de red social.
Code
import redef extraer_hashtags(texto):""" Extrae hashtags de un texto. Un hashtag empieza con # seguido de letras/números/guiones bajos. """ patron =r"#\w+"return re.findall(patron, texto)# Pruebatweet ="Aprendiendo #NLP con #Python es muy #divertido! 🎉 #MachineLearning #IA"hashtags = extraer_hashtags(tweet)print(f"Texto: {tweet}")print(f"Hashtags: {hashtags}")
Texto: Aprendiendo #NLP con #Python es muy #divertido! 🎉 #MachineLearning #IA
Hashtags: ['#NLP', '#Python', '#divertido', '#MachineLearning', '#IA']
Tu turno
¿Cómo modificarías el patrón para que también capture hashtags con acentos como #ProgramaciónEnEspañol?