Regular expressions

Explora el poder y la precisión de las expresiones regulares, herramientas esenciales en la programación que permiten describir patrones en cadenas de texto para una búsqueda, extracción y manipulación eficiente. Este post desentraña desde los fundamentos hasta técnicas avanzadas de regex, ofreciendo una guía práctica con ejemplos claros para dominar su uso en el análisis y manejo de datos. Prepárate para transformar tu enfoque hacia el procesamiento de texto, desbloqueando nuevas posibilidades en tus proyectos de desarrollo

Open In Colab

Code:

import re

Con el método findall() podemos encontrar todas las coincidencias de un patrón en un string

Code:

string = "Hola, soy un string"

print(re.findall("Hola, soy", string))

Output:

['Hola, soy']

Pero si queremos encontrar la posición donde se encuentra un patrón, podemos usar el método search() para buscar un patrón en un string. Este método devuelve un objeto de tipo Match si encuentra una coincidencia, y si no devuelve None.

Code:

print(re.search("soy", string))

Output:

<re.Match object; span=(6, 9), match='soy'>

También podemos usar el método match() que busca el patrón al principio del string.

Code:

print(re.match("Hola", string))

print(re.match("soy", string))

Output:

<re.Match object; span=(0, 4), match='Hola'>

None

Si queremos obtener la posición de la coincidencia, podemos usar el método span() que devuelve una tupla con la posición inicial y final de la coincidencia.

Code:

print(re.match("Hola", string).span())

Output:

(0, 4)

Sabiendo la posición de la coincidencia, podemos usar el método group() para obtener la subcadena que coincide con el patrón.

Code:

print(re.match("Hola", string).group())

Output:

Hola

También podríamos usar el inicio y el final de la coincidencia para hacer un slice del string.

Code:

start, end = re.match("Hola", string).span()

print(string[start:end])

Output:

Hola

Con el método split() podemos dividir un string en una lista de subcadenas usando un patrón como separador.

Code:

split = re.split("soy", string)

print(split)

Output:

['Hola, ', ' un string']

Se ha dividido la frase en dos strings usando «soy» como separador.

Con el método sub() podemos reemplazar todas las coincidencias de un patrón por otra subcadena.

Code:

sub = re.sub("soy", "eres", string)

print(sub)

Output:

Hola, eres un string

Ha reemplazado todas las coincidencias de «soy» por «eres».

Con el caracter . podemos buscar cualquier caracter, cualquier caracter que haya en nuestro string será encontrado

Code:

string = "Hola, soy un string"

print(re.findall(".", string))

Output:

['H', 'o', 'l', 'a', ',', ' ', 's', 'o', 'y', ' ', 'u', 'n', ' ', 's', 't', 'r', 'i', 'n', 'g']

Si por ejemplo queremos secuencias de dos caracteres buscaríamos con dos .s seguidos

Code:

string1 = "Hola, soy un string"

string2 = "Hola, soy un string2"

print(re.findall("..", string1))

print(re.findall("..", string2))

Output:

['Ho', 'la', ', ', 'so', 'y ', 'un', ' s', 'tr', 'in']

['Ho', 'la', ', ', 'so', 'y ', 'un', ' s', 'tr', 'in', 'g2']

Como podemos ver string1 tiene un numero impar de caracteres, por lo que la última g no la coge, si embargo string2 tiene un número par de caracteres, por lo que coge todos los caracteres

Vamos a ver esto de otra forma, vamos a cambiar cada secuencia de tres caracteres por un símbolo de $

Code:

print(string1)

print(re.sub("...", "$ ", string1))

Output:

Hola, soy un string

$ $ $ $ $ $ g

He impreso dos espacios después de cada $ para que se vea el cambio, se puede ver como el último caracter no lo convierte

Si queremos encontrar los dígitos necesitamos usar \d

Code:

string = "Hola, soy un string con 123 digitos"

print(re.findall("\d", string))

Output:

['1', '2', '3']

Al igual que antes, si por ejemplo queremos dos dígitos, ponemos \d dos veces

Code:

print(re.findall("\d\d", string))

Output:

['12']

Si queremos encontrar letras necesitamos usar \w. Se entiende por word todas las letras de la a a la z, de la A a la Z, los números de 0 al 9 y el _

Code:

string = "Hola, soy un_string con, 123 digitos"

print(re.findall("\w", string))

Output:

['H', 'o', 'l', 'a', 's', 'o', 'y', 'u', 'n', '_', 's', 't', 'r', 'i', 'n', 'g', 'c', 'o', 'n', '1', '2', '3', 'd', 'i', 'g', 'i', 't', 'o', 's']

Como vemos coge todo menos los espacios y la coma

Si queremos encontrar espacios necesitamos \s

Code:

string = "Hola, soy un_string con, 123 digitos"

print(re.sub("\s", "*", string))

Output:

Hola,*soy*un_string*con,*123*digitos

Las expresiones regulares considera los saltos de línea como espacios

Code:

string = """Hola, soy un string

con un salto de línea"""

print(re.sub("\s", "*", string))

Output:

Hola,*soy*un*string**con*un*salto*de*línea

Si queremos buscar un rango usamos [], por ejemplo, si queremos los números del 4 al 8 usamos

Code:

string = "1234567890"

print(re.findall("[4-8]", string))

Output:

['4', '5', '6', '7', '8']

Podemos ampliar el rango de búsqueda

Code:

string = "1234567890"

print(re.findall("[2-57-9]", string))

Output:

['2', '3', '4', '5', '7', '8', '9']

Si además queremos encontrar un caracter en concreto ponemos el carcacter seguido de \

Code:

string = "1234567890."

print(re.findall("[2-57-9\.]", string))

Output:

['2', '3', '4', '5', '7', '8', '9', '.']

Como hemos visto, si queremos encontrar rangos usamos [], pero ¿qué pasa si queremos encontrar solo el [ o el ]? Para ello tenemos que usar \[ y \]

Code:

string = "[1234567890]"

print(re.findall("\[", string))

print(re.findall("\]", string))

Output:

['[']

[']']

Con el delimitador * se indica que quieres que te busque ninguno o todos, no uno a uno como antes

Code:

string = "Hola, soy un string con 12 123 digitos"

print(re.findall("\d", string))

print(re.findall("\d*", string))

Output:

['1', '2', '1', '2', '3']

['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '12', '', '123', '', '', '', '', '', '', '', '', '']

Como se puede ver, al poner el * ha encontrado todas las posiciones en las que hay cero caracteres o todos los caracteres

Con el delimitador + se indica que quieres que te busque uno o más

Code:

string = "Hola, soy un string con 1 12 123 digitos"

print(re.findall("\d+", string))

Output:

['1', '12', '123']

Con el delimitador ? se indica que quieres que te busque cero o uno

Code:

string = "Hola, soy un string con 1 12 123 digitos"

print(re.sub("\d?", "-", string))

Output:

-H-o-l-a-,- -s-o-y- -u-n- -s-t-r-i-n-g- -c-o-n- -- --- ---- -d-i-g-i-t-o-s-

Cuando queremos encontrar algo que aparezca x veces usamos los contadores mediante las llaves {}. Por ejemplo, si queremos encontrar una secuencia en la que al menos haya dos dígitos

Code:

string = "Hola, soy un string con 1 12 123 1234 1234digitos"

print(re.findall("\d{2}", string))

Output:

['12', '12', '12', '34', '12', '34']

Como se puede ver ha encontrado las secuencias 12 y 34

Los contadores aceptan una cota superior e inferior {inf, sup}

Code:

string = "Hola, soy un string con 1 12 123 1234 1234digitos"

print(re.findall("\d{2,5}", string))

Output:

['12', '123', '1234', '1234']

Si no se define la cota superior, significa que se quiere como mínimo la cantidad de elementos que se ha indicado, pero sin límite superior

Code:

string = "Hola, soy un string con 1 12 123 1234 12345464168415641646451563416 digitos"

print(re.findall("\d{2,}", string))

Output:

['12', '123', '1234', '12345464168415641646451563416']

Si queremos usar la notación de cota superior e inferior, pero queremos un número fijo, se tiene que poner dicho número en las dos cotas

Code:

string = "Hola, soy un string con 1 12 123 1234 12345464168415641646451563416 digitos"

print(re.findall("\d{2,3}", string))

Output:

['12', '123', '123', '123', '454', '641', '684', '156', '416', '464', '515', '634', '16']

Se pueden crear clases mediante corchetes []. En realidad vimos que este servía para los rangos, pero, una vez que se define lo que se quiere que haya dentro, se puede considerar como una clase y operar con el

Por ejemplo, supongamos que tenemos un número de teléfono, que puede darse de las siguientes maneras

  • 666-66-66-66
  • 666-666-666
  • 666 666 666
  • 666 66 66 66
  • 666666666

Hay muchas maneras de dar un número, así que vamos a ver cómo crear una clase para definir el delimitador

Primero vamos a decir que busque todas las secuencias de números en las que haya como mínimo dos números

Code:

string1 = "666-66-66-66"

string2 = "666-666-666"

string3 = "666 66 66 66"

string4 = "666 666 666"

string5 = "666666666"

print(f"string1: {string1} -->", re.findall("\d{2,}", string1))

print(f"string2: {string2} -->", re.findall("\d{2,}", string2))

print(f"string3: {string3} -->", re.findall("\d{2,}", string3))

print(f"string4: {string4} -->", re.findall("\d{2,}", string4))

print(f"string5: {string5} -->", re.findall("\d{2,}", string5))

Output:

string1: 666-66-66-66 --> ['666', '66', '66', '66']

string2: 666-666-666 --> ['666', '666', '666']

string3: 666 66 66 66 --> ['666', '66', '66', '66']

string4: 666 666 666 --> ['666', '666', '666']

string5: 666666666 --> ['666666666']

Ahora definimos que encuentre el separador como un - o un espacio

Code:

string1 = "666-66-66-66"

string2 = "666-666-666"

string3 = "666 66 66 66"

string4 = "666 666 666"

string5 = "666666666"

print(f"string1: {string1} -->", re.findall("[-\s]", string1))

print(f"string2: {string2} -->", re.findall("[-\s]", string2))

print(f"string3: {string3} -->", re.findall("[-\s]", string3))

print(f"string4: {string4} -->", re.findall("[-\s]", string4))

print(f"string5: {string5} -->", re.findall("[-\s]", string5))

Output:

string1: 666-66-66-66 --> ['-', '-', '-']

string2: 666-666-666 --> ['-', '-']

string3: 666 66 66 66 --> [' ', ' ', ' ']

string4: 666 666 666 --> [' ', ' ']

string5: 666666666 --> []

Como se ve en el último string no ha encontrado, por lo que añadimos un ? para que encuentre cuando haya cero o uno

Code:

string1 = "666-66-66-66"

string2 = "666-666-666"

string3 = "666 66 66 66"

string4 = "666 666 666"

string5 = "666666666"

print(f"string1: {string1} -->", re.findall("[-\s]?", string1))

print(f"string2: {string2} -->", re.findall("[-\s]?", string2))

print(f"string3: {string3} -->", re.findall("[-\s]?", string3))

print(f"string4: {string4} -->", re.findall("[-\s]?", string4))

print(f"string5: {string5} -->", re.findall("[-\s]?", string5))

Output:

string1: 666-66-66-66 --> ['', '', '', '-', '', '', '-', '', '', '-', '', '', '']

string2: 666-666-666 --> ['', '', '', '-', '', '', '', '-', '', '', '', '']

string3: 666 66 66 66 --> ['', '', '', ' ', '', '', ' ', '', '', ' ', '', '', '']

string4: 666 666 666 --> ['', '', '', ' ', '', '', '', ' ', '', '', '', '']

string5: 666666666 --> ['', '', '', '', '', '', '', '', '', '']

Ahora buscamos que esté todo junto

Code:

string1 = "666-66-66-66"

string2 = "666-666-666"

string3 = "666 66 66 66"

string4 = "666 666 666"

string5 = "666666666"

print(f"string1: {string1} -->", re.findall("\d{2,}[-\s]?\d{2,}[-\s]?\d{2,}[-\s]?\d{2,}[-\s]?", string1))

print(f"string2: {string2} -->", re.findall("\d{2,}[-\s]?\d{2,}[-\s]?\d{2,}[-\s]?\d{2,}[-\s]?", string2))

print(f"string3: {string3} -->", re.findall("\d{2,}[-\s]?\d{2,}[-\s]?\d{2,}[-\s]?\d{2,}[-\s]?", string3))

print(f"string4: {string4} -->", re.findall("\d{2,}[-\s]?\d{2,}[-\s]?\d{2,}[-\s]?\d{2,}[-\s]?", string4))

print(f"string5: {string5} -->", re.findall("\d{2,}[-\s]?\d{2,}[-\s]?\d{2,}[-\s]?\d{2,}[-\s]?", string5))

Output:

string1: 666-66-66-66 --> ['666-66-66-66']

string2: 666-666-666 --> []

string3: 666 66 66 66 --> ['666 66 66 66']

string4: 666 666 666 --> []

string5: 666666666 --> ['666666666']

Como vemos en el string2 y string4, no encuentra nada. Hemos puesto el filtro \d{2,}[\-\s]? 4 veces, es decir queremos una secuencia de al menos dos números, seguido de cero o un separador de tipo guion o espacio que se repita 4 veces. Pero en la última secuencia no hace falta el [\-\s]?, ya que nunca va a terminar un número con un espacio o un guión

Code:

string1 = "666-66-66-66"

string2 = "666-666-666"

string3 = "666 66 66 66"

string4 = "666 666 666"

string5 = "666666666"

print(f"string1: {string1} -->", re.findall("\d{2,}[-\s]?\d{2,}[-\s]?\d{2,}[-\s]?\d{2,}", string1))

print(f"string2: {string2} -->", re.findall("\d{2,}[-\s]?\d{2,}[-\s]?\d{2,}[-\s]?\d{2,}", string2))

print(f"string3: {string3} -->", re.findall("\d{2,}[-\s]?\d{2,}[-\s]?\d{2,}[-\s]?\d{2,}", string3))

print(f"string4: {string4} -->", re.findall("\d{2,}[-\s]?\d{2,}[-\s]?\d{2,}[-\s]?\d{2,}", string4))

print(f"string5: {string5} -->", re.findall("\d{2,}[-\s]?\d{2,}[-\s]?\d{2,}[-\s]?\d{2,}", string5))

Output:

string1: 666-66-66-66 --> ['666-66-66-66']

string2: 666-666-666 --> []

string3: 666 66 66 66 --> ['666 66 66 66']

string4: 666 666 666 --> []

string5: 666666666 --> ['666666666']

Sigue sin encontrar para string2 y string4. Esto es porque lo último que hay en el filtro es un d{2,}, es decir, después del tercer separador estamos esperando al menos 2 números, pero eso en string2 y string4 no pasa, así que ponemos lo siguiente

Code:

string1 = "666-66-66-66"

string2 = "666-666-666"

string3 = "666 66 66 66"

string4 = "666 666 666"

string5 = "666666666"

print(f"string1: {string1} -->", re.findall("\d{2,}[-\s]?\d{2,}[-\s]?\d{2,}[-\s]?\d*", string1))

print(f"string2: {string2} -->", re.findall("\d{2,}[-\s]?\d{2,}[-\s]?\d{2,}[-\s]?\d*", string2))

print(f"string3: {string3} -->", re.findall("\d{2,}[-\s]?\d{2,}[-\s]?\d{2,}[-\s]?\d*", string3))

print(f"string4: {string4} -->", re.findall("\d{2,}[-\s]?\d{2,}[-\s]?\d{2,}[-\s]?\d*", string4))

print(f"string5: {string5} -->", re.findall("\d{2,}[-\s]?\d{2,}[-\s]?\d{2,}[-\s]?\d*", string5))

Output:

string1: 666-66-66-66 --> ['666-66-66-66']

string2: 666-666-666 --> ['666-666-666']

string3: 666 66 66 66 --> ['666 66 66 66']

string4: 666 666 666 --> ['666 666 666']

string5: 666666666 --> ['666666666']

El ejemplo anterior lo podemos filtrar mediante \d+?[- ]

Code:

string1 = "666-66-66-66"

string2 = "666-666-666"

string3 = "666 66 66 66"

string4 = "666 666 666"

string5 = "666666666"

print(f"string1: {string1} -->", re.findall("\d+?[- ]", string1))

print(f"string2: {string2} -->", re.findall("\d+?[- ]", string2))

print(f"string3: {string3} -->", re.findall("\d+?[- ]", string3))

print(f"string4: {string4} -->", re.findall("\d+?[- ]", string4))

print(f"string5: {string5} -->", re.findall("\d+?[- ]", string5))

Output:

string1: 666-66-66-66 --> ['666-', '66-', '66-']

string2: 666-666-666 --> ['666-', '666-']

string3: 666 66 66 66 --> ['666 ', '66 ', '66 ']

string4: 666 666 666 --> ['666 ', '666 ']

string5: 666666666 --> []

Si no estuviese el delimitador ? tendríamos \d+[- ], lo que quiere decir una secuencia de uno o más números seguidos de un espacio o un guión. Pero lo que hace el delimitador ? es hacer esta búsqueda más rápido

Antes hemos visto que con \d encotrábamos dígitos, pues con \D encontramos todo lo que no sean dígitos

Code:

string1 = "E3s4t6e e1s2t3r5i6n7g8 t9i0e4n2e1 d4i5g7i9t0o5s2"

print(re.findall("\D", string1))

Output:

['E', 's', 't', 'e', ' ', 'e', 's', 't', 'r', 'i', 'n', 'g', ' ', 't', 'i', 'e', 'n', 'e', ' ', 'd', 'i', 'g', 'i', 't', 'o', 's']

Lo mismo ocurre con las letras, si escribimos \W encontrará todo lo que no sean letras

Code:

string1 = "Letras ab27_ no letras ,.:;´ç"

print(re.findall("\W", string1))

Output:

[' ', ' ', ' ', ' ', ',', '.', ':', ';', '´']

Si ponemos \S encontraremos todo lo que no sean espacios

Code:

print(re.findall("\S", string1))

Output:

['L', 'e', 't', 'r', 'a', 's', 'a', 'b', '2', '7', '_', 'n', 'o', 'l', 'e', 't', 'r', 'a', 's', ',', '.', ':', ';', '´', 'ç']

Pero en caso que tengamos una clase o quialquier otra cosa, podemos negar mediante ^

Code:

string1 = "1234567890"

print(re.findall("[^5-9]", string1))

Output:

['1', '2', '3', '4', '0']

Volviendo al ejemplo de los números de teléfono de antes, podemos filtrarlos mediante los siguiente

Code:

string1 = "666-66-66-66"

string2 = "666-666-666"

string3 = "666 66 66 66"

string4 = "666 666 666"

string5 = "666666666"

print(f"string1: {string1} -->", re.findall("\d{2,}\D?\d{2,}\D?\d{2,}\D?\d*", string1))

print(f"string2: {string2} -->", re.findall("\d{2,}\D?\d{2,}\D?\d{2,}\D?\d*", string2))

print(f"string3: {string3} -->", re.findall("\d{2,}\D?\d{2,}\D?\d{2,}\D?\d*", string3))

print(f"string4: {string4} -->", re.findall("\d{2,}\D?\d{2,}\D?\d{2,}\D?\d*", string4))

print(f"string5: {string5} -->", re.findall("\d{2,}\D?\d{2,}\D?\d{2,}\D?\d*", string5))

string5 = "666 666 666"

Output:

string1: 666-66-66-66 --> ['666-66-66-66']

string2: 666-666-666 --> ['666-666-666']

string3: 666 66 66 66 --> ['666 66 66 66']

string4: 666 666 666 --> ['666 666 666']

string5: 666666666 --> ['666666666']

Lo que estamos haciendo es pedir secuencias de al menos dos dígitos seguido de uno o ningún no dígito

Con ^ podemos buscar el inicio de línea, por ejemplo, si queremos encontrar un dígito que solo esté al inicio de una línea

Code:

string1 = "linea 1"

string2 = "2ª linea"

print(re.findall("^\d", string1))

print(re.findall("^\d", string2))

Output:

[]

['2']

Como se puede ver solo hay un dígito al inicio de linea en string2

Al igual, el final de línea se puede encontrar con $. Si queremos encontrar un dígito solo al final de una línea

Code:

string1 = "linea 1"

string2 = "2ª linea"

print(re.findall("\d$", string1))

print(re.findall("\d$", string2))

Output:

['1']

[]

Esto solo ocurre en el string1

Si en el siguiente log queremos encontrar solo los WARNs

Code:

log = """[LOG ENTRY] [ERROR] The system is unstable

[LOG ENTRY] [WARN] The system may be down

[LOG ENTRY] [WARN] Microsoft just bought Github

[LOG DATA] [LOG] Everything is OK

[LOG ENTRY] [LOG] [user:@beco] Logged in

[LOG ENTRY] [LOG] [user:@beco] Clicked here

[LOG DATA] [LOG] [user:@celismx] Did something

[LOG ENTRY] [LOG] [user:@beco] Rated the app

[LOG ENTRY] [LOG] [user:@beco] Logged out

[LOG LINE] [LOG] [user:@celismx] Logged in"""

result = re.findall("\[LOG.*\[WARN\].*", log)

result

Output:

['[LOG ENTRY] [WARN] The system may be down',

'[LOG ENTRY] [WARN] Microsoft just bought Github']

Dentro de un número podemos encontrarnos letras como la e de extensión, # también para la extensión, o la p para que si llama un ordenador haga una pausa. También podemos encontrar el + para indicar un prefijo de pais y separadores como espacios, -, .

Code:

tel = """555658

56-58-11

56.58.11

56.78-98

65 09 87

76y87r98

45y78-56

78.87 65

78 54-56

+521565811

58-11-11#246

55256048p123

55256048e123"""

result = re.findall("\+?\d{2,3}[^\da-zA-Z\n]?\d{2,3}[^\da-zA-Z\n]?\d{2,3}[#pe]?\d*", tel)

result

Output:

['555658',

'56-58-11',

'56.58.11',

'56.78-98',

'65 09 87',

'78.87 65',

'78 54-56',

'+521565811',

'58-11-11#246',

'55256048p123',

'55256048e123']

Pasamos a explicarlo

  • \+?: Que empiece con el caracter + y que haya cero o uno
  • \d{2,3}: Que siga con entre 2 y 3 dígitos
  • [^\da-zA-Z\n]?: A continuación puede haber cero o un caracter que no sea ni un dígito, ni una letra de la a la z, ni una letra de la A a la Z, ni un salto de línea
  • \d{2,3}: Que siga con entre 2 y 3 dígitos
  • [^\da-zA-Z\n]?: A continuación puede haber cero o un caracter que no sea ni un dígito, ni una letra de la a la z, ni una letra de la A a la Z, ni un salto de línea
  • \d{2,3}: Que siga con entre 2 y 3 dígitos
  • [#pe]?: A continuación puede haber cero o un caracter tanto #, como p, como e

\d: Por último que haya cero o todos los números

Code:

urls = """url: https://www.instagram.com/p/BXB4zsUlW5Z/?taken-by=beco.mx

url: http://instagram.com/p/blablablah

url: http://itam.mx/test

http://instagram.com/p/blablablah

https://www.vanguarsoft.com.ve

http://platzi.com

https://traetelo.net

https://traetelo.net/images archivo.jsp

url: https://subdominio.traetelo.net

url: https://www.instagram.com/p/BXB4zsUlW5Z/?taken-by=beco.mx

url: http://instagram.com/p/blablablah

url: http://itam.mx/test

http://instagram.com/p/blablablah

https://www.google.com.co/

https://sub.dominio.de.alguien.com/archivo.html

https://en.wikipedia.org/wiki/.org

https://cdn-microsoft.org/image/seixo2t9sjl_22.jpg

https://hola.pizza

https://platzi.com/clases/1301-expresiones-regulares/11860-urls9102/ clase

https://api.giphy.com/v1/gifs/search?q=Rick and Morty&limit=10&api_key=DG3hItPp5HIRNC0nit3AOR7eQZAe

http://localhost:3000/something?color1=red&color2=blue

http://localhost:3000/display/post?size=small

http://localhost:3000/?name=satyam

http://localhost:3000/scanned?orderid=234

http://localhost:3000/getUsers?userId=12354411&name=Billy

http://localhost:3000/getUsers?userId=12354411

http://localhost:3000/search?city=Barcelona

www.sitiodeejemplo.net/pagina.php?nombredevalor1=valor1&nombredevalor2=valor2"""

result = re.findall("https?:\/\/[\w\-\.]+\.\w{2,6}\/?\S*", urls)

result

Output:

['https://www.instagram.com/p/BXB4zsUlW5Z/?taken-by=beco.mx',

'http://instagram.com/p/blablablah',

'http://itam.mx/test',

'http://instagram.com/p/blablablah',

'https://www.vanguarsoft.com.ve',

'http://platzi.com',

'https://traetelo.net',

'https://traetelo.net/images',

'https://subdominio.traetelo.net',

'https://www.instagram.com/p/BXB4zsUlW5Z/?taken-by=beco.mx',

'http://instagram.com/p/blablablah',

'http://itam.mx/test',

'http://instagram.com/p/blablablah',

'https://www.google.com.co/',

'https://sub.dominio.de.alguien.com/archivo.html',

'https://en.wikipedia.org/wiki/.org',

'https://cdn-microsoft.org/image/seixo2t9sjl_22.jpg',

'https://hola.pizza',

'https://platzi.com/clases/1301-expresiones-regulares/11860-urls9102/',

'https://api.giphy.com/v1/gifs/search?q=Rick']

Pasamos a explicarlo

  • http: Queremos que empiece por http
  • s?: A continuación puede haber o no una s
  • :\/\/: Seguido de ://
  • [\w\-\.]+: Seguido de uno o más letras, giones o puntos
  • \.: A continuación un punto
  • \w{2,6}: Entre 2 y 6 letras para el tld
  • \/?: Seguido de cero o un /

\S: Ninguno o todo lo que no sea un espacio

Code:

mails = """esto.es_un.mail@mail.com

esto.es_un.mail+complejo@mail.com

dominio.com

rodrigo.jimenez@yahoo.com.mx

ruben@starbucks.com

esto_no$es_email@dominio.com

no_se_de_internet3@hotmail.com"""

result = re.findall("[\w\._]{5,30}\+?[\w\._]{0,10}@[\w\.-]{2,}\.\w{2,6}", mails)

result

Output:

['esto.es_un.mail@mail.com',

'esto.es_un.mail+complejo@mail.com',

'rodrigo.jimenez@yahoo.com.mx',

'ruben@starbucks.com',

'es_email@dominio.com',

'no_se_de_internet3@hotmail.com']

Pasamos a explicarlo

  • [\w\._]{5,30}: Queremos que empiece por entre 5 y 30 (que es lo mínimo y máximo que admite gmail) letras, puntos o barras bajas
  • \+?: Seguido de cero o un +
  • [\w\._]{0,10}: A continuación entre 0 y 10 letras, puntos o barras bajas
  • @: La @
  • [\w\.-]{2,}: Entre 2 e infinitas letras, puntos y guiones (dominio)
  • \.: Seguido de un .
  • \w{2,6}: Y por último entre 2 y 6 letras para el tld

Hay dos posibles manera de dar localizaciones, por lo que analizamos las dos

Code:

loc = """-99.205646,19.429707,2275.10

-99.205581, 19.429652,2275.10

-99.204654,19.428952,2275.58"""

result = re.findall("\-?\d{1,3}\.\d{1,6},\s?\-?\d{1,3}\.\d{1,6},.*", loc)

result

Output:

['-99.205646,19.429707,2275.10',

'-99.205581, 19.429652,2275.10',

'-99.204654,19.428952,2275.58']

Pasamos a explicarlo

  • \-?: Queremos que empiece con cero o un signo menos
  • \d{1,3}: Seguido de entre uno y tres números
  • \.: A continuación un punto
  • \d{1,6}: Después entre uno y seis números
  • ,: A continuación una ,
  • \s?: Después cero o un espacio
  • \-?: Cero o un signo menos
  • \d{1,3}: A continuación entre uno y tres números
  • \.: A continuación un punto
  • \d{1,6}: Seguido de entre uno y seis números
  • ,: Luego una coma

.: Por último ninguno o todo tipo de caracteres

Code:

loc = """-99 12' 34.08"W, 19 34' 56.98"N

-34 54' 32.00"E, -3 21' 67.00"S"""

result = re.findall("\-?\d{1,3}\s\d{1,2}'\s\d{1,2}\.\d{2,2}\"[WE],\s?\-?\d{1,3}\s\d{1,2}'\s\d{1,2}\.\d{2,2}\"[SN]", loc)

result

Output:

['-99 12\' 34.08"W, 19 34\' 56.98"N', '-34 54\' 32.00"E, -3 21\' 67.00"S']

Code:

print(result[0])

print(result[1])

Output:

-99 12' 34.08"W, 19 34' 56.98"N

-34 54' 32.00"E, -3 21' 67.00"S

Pasamos a explicarlo

  • \-?: Queremos que empiece con cero o un signo menos
  • \d{1,3}: Seguido de entre uno y tres números
  • \s: Después un espacio
  • \d{1,2}: Segiodo de entre uno y dos números
  • ': A continuación un '
  • \s: Seguido de un espacio
  • \d{1,2}: A continuación entre uno y dos números
  • \.: Después un punto
  • \d{2,2}: Seguido de dos números
  • \": Después un "
  • [WE]: A continuación la letra W o la letra E
  • ,: Después una coma
  • \s?: Seguido de cero o un espacio
  • \-?: Después cero o un signo menos
  • \d{1,3}: A continuación entre uno y tres números
  • \s: Seguido de un espacio
  • \d{1,2}: A continuación entre uno y dos números
  • ': Después un '
  • \s: Después un espacio
  • \d{1,2}: A continuación entre uno y dos númeors
  • \.: Seguido de un punto
  • \d{2,2}: Después dos números
  • \": Segido de "
  • [SN]: Y por último la letra S o la letra N

Code:

nombres = """Camilo Sarmiento Gálvez

Alejandro Pliego Abasto

Milagros Reyes Japón

Samuel París Arrabal

Juan Pablo Tafalla

Axel Gálvez Velázquez

Óscar Montreal Aparicio

Jacobo Pozo Tassis

Guillermo Ordóñez Espiga

Eduardo Pousa Curbelo

Ivanna Bienvenida Kevin

Ada Tasis López

Luciana Sáenz García

Florencia Sainz Márquz

Catarina Cazalla Lombarda

Paloma Gallo Perro

Margarita Quesada Florez

Vicente Fox Quesada

Iris Graciani

Asunción Carballar

Constanza Muñoz

Manuel Andres García Márquez"""

result = re.findall("[A-ZÁÉÍÓÚ][a-záéíóú]+\s[A-ZÁÉÍÓÚ][a-záéíóú]+\s[A-ZÁÉÍÓÚ][a-záéíóú]+", nombres)

result

Output:

['Camilo Sarmiento Gálvez',

'Alejandro Pliego Abasto',

'Milagros Reyes Japón',

'Samuel París Arrabal',

'Juan Pablo Tafalla',

'Axel Gálvez Velázquez',

'Óscar Montreal Aparicio',

'Jacobo Pozo Tassis',

'Espiga\nEduardo Pousa',

'Curbelo\nIvanna Bienvenida',

'Kevin\nAda Tasis',

'López\nLuciana Sáenz',

'García\nFlorencia Sainz',

'Márquz\nCatarina Cazalla',

'Lombarda\nPaloma Gallo',

'Perro\nMargarita Quesada',

'Florez\nVicente Fox',

'Quesada\nIris Graciani',

'Asunción Carballar\nConstanza',

'Manuel Andres García']

Pasamos a explicarlo

  • [A-ZÁÉÍÓÚ]: Queremos que empiece con una letra mayúscula, incluidas con acentos
  • [a-záéíóú]+: Seguido de una o mas letras minúsculas, incluidas con espacios
  • \s: Segido de un espacio
  • [A-ZÁÉÍÓÚ]: A continuación una letra mayúscula, incluidas con acentos
  • [a-záéíóú]+: Seguido de una o mas letras minúsculas, incluidas con espacios
  • \s: Segido de un espacio
  • [A-ZÁÉÍÓÚ]: A continuación una letra mayúscula, incluidas con acentos
  • [a-záéíóú]+: Seguido de una o mas letras minúsculas, incluidas con espacios

Vamos a descargarnos un archivo con un montón de películas históricas

Code:

# download file from url

import urllib.request

url = "https://static.platzi.com/media/tmp/class-files/github/moviedemo/moviedemo-master/movies.dat"

urllib.request.urlretrieve(url, "movies.dat")

Output:

---------------------------------------------------------------------------

HTTPError Traceback (most recent call last)

Cell In[43], line 4

2 import urllib.request

3 url = "https://static.platzi.com/media/tmp/class-files/github/moviedemo/moviedemo-master/movies.dat"

----> 4 urllib.request.urlretrieve(url, "movies.dat")

File ~/miniconda3/envs/mybase/lib/python3.11/urllib/request.py:241, in urlretrieve(url, filename, reporthook, data)

224 """

225 Retrieve a URL into a temporary location on disk.

226

(...)

237 data file as well as the resulting HTTPMessage object.

238 """

239 url_type, path = _splittype(url)

--> 241 with contextlib.closing(urlopen(url, data)) as fp:

242 headers = fp.info()

244 # Just return the local path and the "headers" for file://

245 # URLs. No sense in performing a copy unless requested.

File ~/miniconda3/envs/mybase/lib/python3.11/urllib/request.py:216, in urlopen(url, data, timeout, cafile, capath, cadefault, context)

214 else:

215 opener = _opener

--> 216 return opener.open(url, data, timeout)

File ~/miniconda3/envs/mybase/lib/python3.11/urllib/request.py:525, in OpenerDirector.open(self, fullurl, data, timeout)

523 for processor in self.process_response.get(protocol, []):

524 meth = getattr(processor, meth_name)

--> 525 response = meth(req, response)

527 return response

File ~/miniconda3/envs/mybase/lib/python3.11/urllib/request.py:634, in HTTPErrorProcessor.http_response(self, request, response)

631 # According to RFC 2616, "2xx" code indicates that the client's

632 # request was successfully received, understood, and accepted.

633 if not (200 <= code < 300):

--> 634 response = self.parent.error(

635 'http', request, response, code, msg, hdrs)

637 return response

File ~/miniconda3/envs/mybase/lib/python3.11/urllib/request.py:563, in OpenerDirector.error(self, proto, *args)

561 if http_err:

562 args = (dict, 'default', 'http_error_default') + orig_args

--> 563 return self._call_chain(*args)

File ~/miniconda3/envs/mybase/lib/python3.11/urllib/request.py:496, in OpenerDirector._call_chain(self, chain, kind, meth_name, *args)

494 for handler in handlers:

495 func = getattr(handler, meth_name)

--> 496 result = func(*args)

497 if result is not None:

498 return result

File ~/miniconda3/envs/mybase/lib/python3.11/urllib/request.py:643, in HTTPDefaultErrorHandler.http_error_default(self, req, fp, code, msg, hdrs)

642 def http_error_default(self, req, fp, code, msg, hdrs):

--> 643 raise HTTPError(req.full_url, code, msg, hdrs, fp)

HTTPError: HTTP Error 403: Forbidden

Vamos a imprimir las primeras 10 lineas para analizarlo

Code:

file = open("movies.dat", "r")

for i, line in enumerate(file):

print(line, end="")

if i == 10:

break

file.close()

Output:

1::Toy Story (1995)::Adventure|Animation|Children|Comedy|Fantasy

2::Jumanji (1995)::Adventure|Children|Fantasy

3::Grumpier Old Men (1995)::Comedy|Romance

4::Waiting to Exhale (1995)::Comedy|Drama|Romance

5::Father of the Bride Part II (1995)::Comedy

6::Heat (1995)::Action|Crime|Thriller

7::Sabrina (1995)::Comedy|Romance

8::Tom and Huck (1995)::Adventure|Children

9::Sudden Death (1995)::Action

10::GoldenEye (1995)::Action|Adventure|Thriller

11::American President, The (1995)::Comedy|Drama|Romance

Como se puede ver, tenemos un ID, seguido de ::, a continuación el nombre de la película, entre paréntesis el año, sguido de :: y después géneros separados por |

Podemos hacer una limpieza del archivo muy fácil mediante las expresiones regulares, las funciones compile y match y el uso de agrupaciones con paréntesis. Al hacer agurpaciones, seleccionamos qué zonas del texto queremos guardar para luego trabajar con ellas como queramos, vamos e verlo con un ejemplo

Code:

pattern = re.compile(r"^\d+::([\w\s:,\(\)\.\-'&¡!/¿?ÁÉÍÓÚáéíóú\+*\$#°\'\"\[\]@·]+)\s\((\d{4,4})\)::(.*)$")

file = open("movies.dat", "r")

file_filtered = open("movies.csv", "w")

file_filtered.write("title,year,genders\n")

sep = ";;"

for line in file:

result = re.match(pattern, line)

if result:

file_filtered.write(f"{result.group(1)}{sep}{result.group(2)}{sep}{result.group(3)}\n")

else:

print(line, end="")

file.close()

file_filtered.close()

Vamos a ver qué hemos hecho, primero hemos definido un patrón con lo siguiente:

  • ^: Queremos que empiece con el inicio de línea
  • \d+: A continuación uno o más números
  • ::: Seguido de ::

([\w\s:,\(\)\.\-'&¡!/¿?ÁÉÍÓÚáéíóú\+\$#°\'\"\[\]@·]+): Esta es la primera agrupación, buscamos cualquier palabra, espacio o caracter de entre los corchetes que aparezca una o más veces

  • \s: A continuación un espacio
  • \(: La apretura de un paréntesis
  • (\d{4,4}): Aquí esta la segunda agrupación, buscamos cuatro números
  • \): Después el cierre de un paréntesis
  • ::: A continuación ::

(.): La tercera agrupación, cualquier caracter que aparezca ninguna o todas las veces

  • $: Por último el final de línea

Dentro del for analizamos línea a línea si se encuentra el patrón que hemos definido, y si se encuentra se escriben los tres patrones en el csv separados por sep, que en nuestro caso lo hemos definido como ;;. Se ha definido ese separador, porque hay títulos de películas que tienen ,s.

Leemos el csv con Pandas

Code:

import pandas as pd

df = pd.read_csv("movies.csv", sep=";;", engine="python")

df.head()

Output:

title,year,genders

Toy Story 1995 Adventure|Animation|Children|Comedy|Fantasy

Jumanji 1995 Adventure|Children|Fantasy

Grumpier Old Men 1995 Comedy|Romance

Waiting to Exhale 1995 Comedy|Drama|Romance

Father of the Bride Part II 1995 Comedy

Aquí tienes un cheatsheet con un montón de patrones

davechild_regular-expressions

Continuar leyendo
DoLa-thumbnail

DoLa – Decoding by Contrasting Layers Improves Factuality in Large Language Models

¿Alguna vez has hablado con un LLM y te ha respondido algo que suena como si hubiera estado bebiendo café …
QLoRA_thumbnail_ES

QLoRA: Efficient Finetuning of Quantized LLMs

¡Hola a todos! 🤗 Hoy vamos a hablar de QLoRA, la técnica que te permitirá hacer que tus modelos de …
GPTQ-thumbnail-shot

GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers

¡Atención, desarrolladores! 🚨 ¿Tienes un modelo de lenguaje que es demasiado grande y pesado para tu aplicación? 🤯 ¡No te …
llm.int8()-thumbnail

llm.int8() – 8-bit Matrix Multiplication for Transformers at Scale

¡Prepárate para ahorrar espacio y acelerar tus modelos! 💥 En este post, voy a explorar el método llm.int8(), una técnica …
quantization-thumbnail

LLMs quantization

¡Imagina que tienes un modelo de lenguaje gigante que puede responder a cualquier pregunta, desde la capital de Francia hasta …
LoRA_thumbnail_ES

LoRA – low rank adaptation of large language models

¡Prepárate para llevar la adaptación de tus modelos al siguiente nivel con LoRA! 🚀 Esta técnica de adaptación de baja …
Resumen
Regular expressions
Nombre del artículo
Regular expressions
Descripción
Explora el poder y la precisión de las expresiones regulares, herramientas esenciales en la programación que permiten describir patrones en cadenas de texto para una búsqueda, extracción y manipulación eficiente. Este post desentraña desde los fundamentos hasta técnicas avanzadas de regex, ofreciendo una guía práctica con ejemplos claros para dominar su uso en el análisis y manejo de datos. Prepárate para transformar tu enfoque hacia el procesamiento de texto, desbloqueando nuevas posibilidades en tus proyectos de desarrollo
MaximoFN
MaximoFN
MaximoFN
Publisher Logo