Las expresiones regulares o regex son caracteres que definen la búsqueda de un patrón en un texto. Cada caracter dentro de una expresión regular tiene un significado especial, y a simple vista puede parecer algo complicado. En este pequeño tutorial intentaremos explicar los regex paso a paso de modo que se puedan comenzar a entender.
En python, si queremos que nos lo lea como regex, tenemos que ponerle una ‘r’ delante. De no hacerlo, python lo leerá de manera distinta.
## Tab
## \tTab
Para nuestros ejemplos usaremos la extensión compile
y finditer
de re. Esto nos dará un iterador donde estarán las coincidencian que buscamos.
Supongamos que queremos buscar la cadena de texto 'abc'
. Lo haríamos de la siguiente forma:
text = 'texto de prueba abc ABC final'
pattern = re.compile(r'abc')
matches = pattern.finditer(text)
for match in matches:
print(match)
## <_sre.SRE_Match object; span=(16, 19), match='abc'>
La posición en la que se encuentra nuestro match dentro del texto la indica dentro del span
. Si hacemos un slice con esos índices, veremos que encuentra el patrón que le pedimos.
## abc
A la hora de buscar patrones, hay caracteres especiales que tenemos que tener en cuenta. Por ejemplo, si buscamos un punto en realidad le estamos diciendo que busque cualquier carácter distinto de un salto de línea.
text = '''Texto.
Una frase.'''
pattern = re.compile(r'.')
matches = pattern.finditer(text)
for match in matches:
print(match)
## <_sre.SRE_Match object; span=(0, 1), match='T'>
## <_sre.SRE_Match object; span=(1, 2), match='e'>
## <_sre.SRE_Match object; span=(2, 3), match='x'>
## <_sre.SRE_Match object; span=(3, 4), match='t'>
## <_sre.SRE_Match object; span=(4, 5), match='o'>
## <_sre.SRE_Match object; span=(5, 6), match='.'>
## <_sre.SRE_Match object; span=(7, 8), match='U'>
## <_sre.SRE_Match object; span=(8, 9), match='n'>
## <_sre.SRE_Match object; span=(9, 10), match='a'>
## <_sre.SRE_Match object; span=(10, 11), match=' '>
## <_sre.SRE_Match object; span=(11, 12), match='f'>
## <_sre.SRE_Match object; span=(12, 13), match='r'>
## <_sre.SRE_Match object; span=(13, 14), match='a'>
## <_sre.SRE_Match object; span=(14, 15), match='s'>
## <_sre.SRE_Match object; span=(15, 16), match='e'>
## <_sre.SRE_Match object; span=(16, 17), match='.'>
Si lo que queremos buscar es la posición de los puntos debemos usar \.
text = '''Texto de prueba.
Una frase.'''
pattern = re.compile(r'\.')
matches = pattern.finditer(text)
for match in matches:
print(match)
## <_sre.SRE_Match object; span=(15, 16), match='.'>
## <_sre.SRE_Match object; span=(26, 27), match='.'>
Expresión regular | Patrón |
---|---|
. | Cualquier carácter excepto una nueva línea |
\d | Número del 0 al 9 |
\D | Cualquier carácter que no sea un número del 0 al 9 |
\w | Carácter de una palabra (a-z, A-Z, 0-9) |
\W | Cualquier carácter que no sea el de una palabra (a-z, A-Z, 0-9) |
\s | Espacios en blanco (espacio, tabulación, salto de línea) |
\S | Cualquier carácter que no sea un espacio en blanco |
Como vemos, las expresiones regulares en mayúsculas suelen ser la negación de su versión en minúsculas
Vamos a ver cómo funciona cada uno.
text = '''Texto 012 texto 0.'''
# \d busca números del 0 al 9
pattern = re.compile(r'\d')
matches = pattern.finditer(text)
for match in matches:
print(match)
## <_sre.SRE_Match object; span=(6, 7), match='0'>
## <_sre.SRE_Match object; span=(7, 8), match='1'>
## <_sre.SRE_Match object; span=(8, 9), match='2'>
## <_sre.SRE_Match object; span=(16, 17), match='0'>
text = '''Texto 012 texto 0.'''
# \D es la negación de \d
pattern = re.compile(r'\D')
matches = pattern.finditer(text)
for match in matches:
print(match)
## <_sre.SRE_Match object; span=(0, 1), match='T'>
## <_sre.SRE_Match object; span=(1, 2), match='e'>
## <_sre.SRE_Match object; span=(2, 3), match='x'>
## <_sre.SRE_Match object; span=(3, 4), match='t'>
## <_sre.SRE_Match object; span=(4, 5), match='o'>
## <_sre.SRE_Match object; span=(5, 6), match=' '>
## <_sre.SRE_Match object; span=(9, 10), match=' '>
## <_sre.SRE_Match object; span=(10, 11), match='t'>
## <_sre.SRE_Match object; span=(11, 12), match='e'>
## <_sre.SRE_Match object; span=(12, 13), match='x'>
## <_sre.SRE_Match object; span=(13, 14), match='t'>
## <_sre.SRE_Match object; span=(14, 15), match='o'>
## <_sre.SRE_Match object; span=(15, 16), match=' '>
## <_sre.SRE_Match object; span=(17, 18), match='.'>
text = '''
Texto %
texto 0.
'''
# \w busca cualquier carácter de una palabra dentro del intervalo (a-z, A-Z, 0-9)
pattern = re.compile(r'\w')
matches = pattern.finditer(text)
for match in matches:
print(match)
## <_sre.SRE_Match object; span=(1, 2), match='T'>
## <_sre.SRE_Match object; span=(2, 3), match='e'>
## <_sre.SRE_Match object; span=(3, 4), match='x'>
## <_sre.SRE_Match object; span=(4, 5), match='t'>
## <_sre.SRE_Match object; span=(5, 6), match='o'>
## <_sre.SRE_Match object; span=(9, 10), match='t'>
## <_sre.SRE_Match object; span=(10, 11), match='e'>
## <_sre.SRE_Match object; span=(11, 12), match='x'>
## <_sre.SRE_Match object; span=(12, 13), match='t'>
## <_sre.SRE_Match object; span=(13, 14), match='o'>
## <_sre.SRE_Match object; span=(15, 16), match='0'>
text = '''
Texto %
texto 0.
'''
# \W es la negación de \w
pattern = re.compile(r'\W')
matches = pattern.finditer(text)
for match in matches:
print(match)
## <_sre.SRE_Match object; span=(0, 1), match='\n'>
## <_sre.SRE_Match object; span=(6, 7), match=' '>
## <_sre.SRE_Match object; span=(7, 8), match='%'>
## <_sre.SRE_Match object; span=(8, 9), match='\n'>
## <_sre.SRE_Match object; span=(14, 15), match=' '>
## <_sre.SRE_Match object; span=(16, 17), match='.'>
## <_sre.SRE_Match object; span=(17, 18), match='\n'>
text = '''
Texto 012
\ttexto
0.'''
# \s busca espacios en blanco (espacio, tabulación, saltos de línea)
pattern = re.compile(r'\s')
matches = pattern.finditer(text)
for match in matches:
print(match)
## <_sre.SRE_Match object; span=(0, 1), match='\n'>
## <_sre.SRE_Match object; span=(6, 7), match=' '>
## <_sre.SRE_Match object; span=(10, 11), match='\n'>
## <_sre.SRE_Match object; span=(11, 12), match='\t'>
## <_sre.SRE_Match object; span=(17, 18), match='\n'>
## <_sre.SRE_Match object; span=(18, 19), match=' '>
## <_sre.SRE_Match object; span=(19, 20), match=' '>
text = '''
Texto 012
\ttexto
0.'''
# \S es la negación de \s
pattern = re.compile(r'\S')
matches = pattern.finditer(text)
for match in matches:
print(match)
## <_sre.SRE_Match object; span=(1, 2), match='T'>
## <_sre.SRE_Match object; span=(2, 3), match='e'>
## <_sre.SRE_Match object; span=(3, 4), match='x'>
## <_sre.SRE_Match object; span=(4, 5), match='t'>
## <_sre.SRE_Match object; span=(5, 6), match='o'>
## <_sre.SRE_Match object; span=(7, 8), match='0'>
## <_sre.SRE_Match object; span=(8, 9), match='1'>
## <_sre.SRE_Match object; span=(9, 10), match='2'>
## <_sre.SRE_Match object; span=(12, 13), match='t'>
## <_sre.SRE_Match object; span=(13, 14), match='e'>
## <_sre.SRE_Match object; span=(14, 15), match='x'>
## <_sre.SRE_Match object; span=(15, 16), match='t'>
## <_sre.SRE_Match object; span=(16, 17), match='o'>
## <_sre.SRE_Match object; span=(20, 21), match='0'>
## <_sre.SRE_Match object; span=(21, 22), match='.'>
También tenemos los anchors (anclas), que no buscan un carácter en especial, sino que buscan las posiciones que cumplan cierta condición
Expresión regular | Patrón |
---|---|
\b | Busca un patrón que comience con un espacio en blanco o un carácter no alfanumérico |
\B | Cualquier patrón que no comience con un espacio en blanco o un caracter no alfanumérico |
^ | El inicio de un string |
$ | El final de un string |
text = '''Ja JaJa
Ja'''
# \b busca un patrón que comience con un espacio en blanco o un carácter no alfanumérico
pattern = re.compile(r'\bJa')
matches = pattern.finditer(text)
for match in matches:
print(match)
## <_sre.SRE_Match object; span=(0, 2), match='Ja'>
## <_sre.SRE_Match object; span=(3, 5), match='Ja'>
## <_sre.SRE_Match object; span=(8, 10), match='Ja'>
Vemos que encuentra los ‘Ja’ que comienzan en el salto de línea o con un espacio en blanco, pero no el que tiene letras delante.
text = '''Ja JaJa
Ja'''
# \B es la negación de \b
pattern = re.compile(r'\BJa')
matches = pattern.finditer(text)
# Vemos que ahora encuentra el Ja que antes no encontraba
for match in matches:
print(match)
## <_sre.SRE_Match object; span=(5, 7), match='Ja'>
text = '''Primera línea
Primera línea'''
# ^ busca el inicio de un string
pattern = re.compile(r'^Prim')
matches = pattern.finditer(text)
for match in matches:
print(match)
## <_sre.SRE_Match object; span=(0, 4), match='Prim'>
text = '''Primera línea
Primera línea'''
# $ busca el final de una línea
pattern = re.compile(r'nea$')
matches = pattern.finditer(text)
for match in matches:
print(match)
## <_sre.SRE_Match object; span=(24, 27), match='nea'>
Expresión regular | Patrón |
---|---|
[ ] | Busca los carácteres incluidos entre corchetes, alguno de ellos, en la posición indicada |
[^ ] | Busca los caracteres no incluidos entre corchetes para la posición indicada |
Si queremos buscar grupos de números podemos hacer ésto. Recordemos que el punto busca cualquier caracter.
text = '''Gustavo Vargas
615-784-8201
121 Calle de las Águilas , Madrid RI 14088
ge.vargasn@gmail.com
Carlos Hernández
902-202-122
69 Avenida del Imperio, Santiago AV 28055
carloshernandez@protonmail.com'''
# fijémonos en que no encuentra el segundo de la segunda persona porque tiene un número menos
pattern = re.compile(r'\d\d\d.\d\d\d.\d\d\d\d')
matches = pattern.finditer(text)
for match in matches:
print(match)
## <_sre.SRE_Match object; span=(15, 27), match='615-784-8201'>
Si queremos que pille solo un guión o un punto, podemos usar los corchetes.
text = '''615-784-8201
902.202.1220
902*202*1220
'''
# lo que está en paréntesis indica que se busca un carácter u otro
pattern = re.compile(r'\d\d\d[-.]\d\d\d[-.]\d\d\d\d')
matches = pattern.finditer(text)
for match in matches:
print(match)
## <_sre.SRE_Match object; span=(0, 12), match='615-784-8201'>
## <_sre.SRE_Match object; span=(13, 25), match='902.202.1220'>
text = '''600-666-6412
800.555.1988
100*504*1488
100-504-1489
200-504-1490
'''
# Si queremos buscar los que comiencen por 100 0 200
pattern = re.compile(r'[12]00[-.*]\d\d\d[-.*]\d\d\d\d')
matches = pattern.finditer(text)
for match in matches:
print(match)
## <_sre.SRE_Match object; span=(26, 38), match='100*504*1488'>
## <_sre.SRE_Match object; span=(39, 51), match='100-504-1489'>
## <_sre.SRE_Match object; span=(52, 64), match='200-504-1490'>
text = '''600-666-6412
800.555.1988
100*504*1488
100-504-1489
200-504-1490
'''
pattern = re.compile(r'[1-6]00[-.*]\d\d\d[-.*]\d\d\d\d')
matches = pattern.finditer(text)
for match in matches:
print(match)
## <_sre.SRE_Match object; span=(0, 12), match='600-666-6412'>
## <_sre.SRE_Match object; span=(26, 38), match='100*504*1488'>
## <_sre.SRE_Match object; span=(39, 51), match='100-504-1489'>
## <_sre.SRE_Match object; span=(52, 64), match='200-504-1490'>
Recordemos que si usamos el caret (^
) dentro de los corchetes, estamos seleccionando la negación.
text = '''600-666-6412
800.555.1988
100*504*1488
100-504-1489
200-504-1490
'''
pattern = re.compile(r'[^1-6]\d\d[-.*]\d\d\d[-.*]\d\d\d\d')
matches = pattern.finditer(text)
for match in matches:
print(match)
## <_sre.SRE_Match object; span=(13, 25), match='800.555.1988'>
text = '''rio
pio
tio
mio
'''
pattern = re.compile(r'[^pt]io')
matches = pattern.finditer(text)
for match in matches:
print(match)
## <_sre.SRE_Match object; span=(0, 3), match='rio'>
## <_sre.SRE_Match object; span=(12, 15), match='mio'>
Expresión regular | Patrón |
---|---|
| | Signo para unir la búsqueda de patrones. Equivalente a un booleano “o”. |
( ) | Grupo |
* | 0 o más |
+ | 1 o más |
? | 0 o 1 |
{3} | Exactamente 3 |
{3,4} | Rango. Un mínimo de tres y un máximo de cuatro. |
text = '''Gustavo Vargas
615-784-8201
121 Calle de las Águilas , Madrid RI 14088
ge.vargasn@gmail.com
Carlos Hernández
902-202-1220
69 Avenida del Imperio, Santiago AV 28055
carloshernandez@protonmail.com'''
# es lo mismo que hicimos ejemplos anteriores, pero simplificado
pattern = re.compile(r'\d{3}.\d{3}.\d{4}')
matches = pattern.finditer(text)
for match in matches:
print(match)
## <_sre.SRE_Match object; span=(15, 27), match='615-784-8201'>
## <_sre.SRE_Match object; span=(110, 122), match='902-202-1220'>
text = '''Sr. Vargas
Sr Wonderful
Srta Nina
Sra. Rushmore
Sr. G'''
pattern = re.compile(r'Sr\.')
matches = pattern.finditer(text)
for match in matches:
print(match)
## <_sre.SRE_Match object; span=(0, 3), match='Sr.'>
## <_sre.SRE_Match object; span=(48, 51), match='Sr.'>
text = '''Sr. Vargas
Sr Wonderful
Sta. Nina
Sra. Rushmore
Sr. G'''
# añadiendo el ? hacemos opcional el último carácter
pattern = re.compile(r'Sr\.?')
matches = pattern.finditer(text)
for match in matches:
print(match)
## <_sre.SRE_Match object; span=(0, 3), match='Sr.'>
## <_sre.SRE_Match object; span=(11, 13), match='Sr'>
## <_sre.SRE_Match object; span=(34, 36), match='Sr'>
## <_sre.SRE_Match object; span=(48, 51), match='Sr.'>
text = '''Sr. Vargas
Sr Wonderful
Srta Nina
Sra. Rushmore
Sr. G'''
# los corchetes sirven para indicar que se busca un solo carácter
# recordemos que los | son para indicar el booleano 'o', el ? indica que es opcional
# \s indica espacios en blanco y * indica que aparezca cero o más veces
pattern = re.compile(r'Sr[a]?\.?\s[A-Z]\w*')
matches = pattern.finditer(text)
for match in matches:
print(match)
## <_sre.SRE_Match object; span=(0, 10), match='Sr. Vargas'>
## <_sre.SRE_Match object; span=(11, 23), match='Sr Wonderful'>
## <_sre.SRE_Match object; span=(34, 47), match='Sra. Rushmore'>
## <_sre.SRE_Match object; span=(48, 53), match='Sr. G'>
text = '''Sr. Vargas
Sr Wonderful
Srta Nina
Sra. Rushmore
Sr. G'''
# los paréntesis indican que se busca un grupo de caracteres
pattern = re.compile(r'Sr(a|ta|\.)?\.?\s[A-Z]\w*')
matches = pattern.finditer(text)
for match in matches:
print(match)
## <_sre.SRE_Match object; span=(0, 10), match='Sr. Vargas'>
## <_sre.SRE_Match object; span=(11, 23), match='Sr Wonderful'>
## <_sre.SRE_Match object; span=(24, 33), match='Srta Nina'>
## <_sre.SRE_Match object; span=(34, 47), match='Sra. Rushmore'>
## <_sre.SRE_Match object; span=(48, 53), match='Sr. G'>
En el siguiente ejemplo intentamos buscar correos.
Comenzamos buscando una letra mayúscula o minúscula con [a-zA-Z], añadimos un + para indicar que busque una o más hasta el siguiente símbolo, que será un ‘@’. Volvemos a poner lo mismo hasta el ‘.com’.
text = '''
gustavoEVargas@gmail.com
gustavo.vargas@university.edu
gustavo-14-vargas@mi-casa.net
'''
pattern = re.compile(r'[a-zA-Z]+@[a-zA-Z]+\.com')
matches = pattern.finditer(text)
for match in matches:
print(match)
## <_sre.SRE_Match object; span=(1, 25), match='gustavoEVargas@gmail.com'>
text = '''
gustavoEVargas@gmail.com
gustavo.vargas@university.edu
gustavo-14-vargas@mi-casa.net
'''
pattern = re.compile(r'[a-zA-Z0-9.-]+@[a-zA-Z-]+\.(com|edu|net)')
matches = pattern.finditer(text)
for match in matches:
print(match)
## <_sre.SRE_Match object; span=(1, 25), match='gustavoEVargas@gmail.com'>
## <_sre.SRE_Match object; span=(26, 55), match='gustavo.vargas@university.edu'>
## <_sre.SRE_Match object; span=(56, 85), match='gustavo-14-vargas@mi-casa.net'>
Visto lo anterior, podemos ver que la expresión:
[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+
es más general, ya que pillar los guiones y generaliza más extensiones finales en el correo.
text = '''
https://www.google.com
http://gustavovargas.github.io
https://youtube.com
'''
pattern = re.compile(r'https?://(www\.)?\w+(\.\w+)?(\.\w+)?')
matches = pattern.finditer(text)
for match in matches:
print(match)
## <_sre.SRE_Match object; span=(1, 23), match='https://www.google.com'>
## <_sre.SRE_Match object; span=(24, 54), match='http://gustavovargas.github.io'>
## <_sre.SRE_Match object; span=(55, 74), match='https://youtube.com'>
text = '''
https://www.google.com
http://gustavovargas.github.io
https://youtube.com
'''
# si lo ponemos todo en grupos, podemos usar la función .group dentro del iterador
pattern = re.compile(r'https?://(www\.)?(\w+)(\.\w+)(\.\w+)?')
matches = pattern.finditer(text)
for match in matches:
print(match.group(0))
## https://www.google.com
## http://gustavovargas.github.io
## https://youtube.com
text = '''
https://www.google.com
http://gustavovargas.github.io
https://youtube.com
'''
# si lo ponemos todo en grupos, podemos usar la función .group dentro del iterador
pattern = re.compile(r'https?://(www\.)?(\w+)(\.\w+)(\.\w+)?')
matches = pattern.finditer(text)
for match in matches:
print(match.group(1))
## www.
## None
## None
text = '''
https://www.google.com
http://gustavovargas.github.io
https://youtube.com
'''
# si lo ponemos todo en grupos, podemos usar la función .group dentro del iterador
pattern = re.compile(r'https?://(www\.)?(\w+)(\.\w+)(\.\w+)?')
matches = pattern.finditer(text)
for match in matches:
print(match.group(2))
## google
## gustavovargas
## youtube
Si queremos sustituir toda la dirección url con solo el nombre y extensión, podemos hacerlo con la función sub
.
text = '''
https://www.google.com
http://gustavovargas.github.io
https://youtube.com
'''
# podemos eliminar el grupo 1 usando la función sub.
pattern = re.compile(r'https?://(www\.)?(\w+)(\.\w+)(\.\w+)?')
matches = pattern.sub(r'\2\3\4', text)
print(matches)
##
## google.com
## gustavovargas.github.io
## youtube.com
Hasta ahora hemos usado la función ‘finditer’, que nos generaba un objeto sobre el que iterar. También podemos usar la función ‘findall’ que nos devuelve una lista de strings. Pero hemos de tener en cuenta que si hay algún grupo, solo nos devolverá grupos
text = '''Sr. Vargas
Sr Wonderful
Srta Nina
Sra. Rushmore
Sr. G'''
pattern = re.compile(r'(Sr|Srta|Sra)\.?\s[A-Z]\w*')
matches = pattern.findall(text)
for match in matches:
print(match)
## Sr
## Sr
## Srta
## Sra
## Sr
También tenemos la función match
. Hemos de tener en cuenta que no devuelve un iterador, sino que solo devuelve el primer match siempre que comience con el string
text = '''Esto es una frase. Esto también'''
pattern = re.compile(r'Esto')
matches = pattern.match(text)
print(matches)
## <_sre.SRE_Match object; span=(0, 4), match='Esto'>
text = '''Esto es una frase. Esto también lo es.'''
# no devuelve coindicencias porque no está al comienzo del string
pattern = re.compile(r'frase')
matches = pattern.match(text)
print(matches)
## None
Si queréis ver más expresiones regulares, lo podéis hacer en este cheatsheet. Y para cualquier sugerencia o errata que encontréis, me podéis contactar en ge.vargasn@gmail.com.
Gracias al tutorial de Corey Schafer en el que se basa este tutorial.