Expresiones regulares
import re
Métodos
Findall
Con el método findall()
podemos encontrar todas las coincidencias de un patrón en un string
import restring = "Hola, soy un string"print(re.findall("Hola, soy", string))
['Hola, soy']
Search
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.
print(re.search("soy", string))
<re.Match object; span=(6, 9), match='soy'>
Match
También podemos usar el método match()
que busca el patrón al principio del string.
print(re.match("Hola", string))print(re.match("soy", string))
<re.Match object; span=(0, 4), match='Hola'>None
Span
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.
print(re.match("Hola", string).span())
(0, 4)
Group
Sabiendo la posición de la coincidencia, podemos usar el método group()
para obtener la subcadena que coincide con el patrón.
print(re.match("Hola", string).group())
Hola
También podríamos usar el inicio y el final de la coincidencia para hacer un slice del string.
start, end = re.match("Hola", string).span()print(string[start:end])
Hola
Split
Con el método split()
podemos dividir un string en una lista de subcadenas usando un patrón como separador.
split = re.split("soy", string)print(split)
['Hola, ', ' un string']
Se ha dividido la frase en dos strings usando "soy" como separador.
Sub
Con el método sub()
podemos reemplazar todas las coincidencias de un patrón por otra subcadena.
sub = re.sub("soy", "eres", string)print(sub)
Hola, eres un string
Ha reemplazado todas las coincidencias de "soy" por "eres".
Patrones
El caracter .
Con el caracter .
podemos buscar cualquier caracter, cualquier caracter que haya en nuestro string será encontrado
string = "Hola, soy un string"print(re.findall(".", string))
['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
string1 = "Hola, soy un string"string2 = "Hola, soy un string2"print(re.findall("..", string1))print(re.findall("..", string2))
['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 $
print(string1)print(re.sub("...", "$ ", string1))
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
Las clases predefinidas y construidas
Dígito
Si queremos encontrar los dígitos necesitamos usar \d
string = "Hola, soy un string con 123 digitos"print(re.findall("d", string))
['1', '2', '3']
Al igual que antes, si por ejemplo queremos dos dígitos, ponemos \d
dos veces
print(re.findall("dd", string))
['12']
Letra
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 _
string = "Hola, soy un_string con, 123 digitos"print(re.findall("w", string))
['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
Espacios
Si queremos encontrar espacios necesitamos \s
string = "Hola, soy un_string con, 123 digitos"print(re.sub("s", "*", string))
Hola,*soy*un_string*con,*123*digitos
Las expresiones regulares considera los saltos de línea como espacios
string = """Hola, soy un stringcon un salto de línea"""print(re.sub("s", "*", string))
Hola,*soy*un*string**con*un*salto*de*línea
Rangos
Si queremos buscar un rango usamos []
, por ejemplo, si queremos los números del 4 al 8 usamos
string = "1234567890"print(re.findall("[4-8]", string))
['4', '5', '6', '7', '8']
Podemos ampliar el rango de búsqueda
string = "1234567890"print(re.findall("[2-57-9]", string))
['2', '3', '4', '5', '7', '8', '9']
Si además queremos encontrar un caracter en concreto ponemos el carcacter seguido de \
string = "1234567890."print(re.findall("[2-57-9.]", string))
['2', '3', '4', '5', '7', '8', '9', '.']
Corchete [
y corchete ]
Como hemos visto, si queremos encontrar rangos usamos []
, pero ¿qué pasa si queremos encontrar solo el [
o el ]
? Para ello tenemos que usar \[
y \]
string = "[1234567890]"print(re.findall("[", string))print(re.findall("]", string))
['['][']']
Los delimitadores +
, *
, ?
Star *
(ninguno o todos)
Con el delimitador *
se indica que quieres que te busque ninguno o todos, no uno a uno como antes
string = "Hola, soy un string con 12 123 digitos"print(re.findall("d", string))print(re.findall("d*", string))
['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
Plus +
(uno o más)
Con el delimitador +
se indica que quieres que te busque uno o más
string = "Hola, soy un string con 1 12 123 digitos"print(re.findall("d+", string))
['1', '12', '123']
Optional ?
(cero o uno)
Con el delimitador ?
se indica que quieres que te busque cero o uno
string = "Hola, soy un string con 1 12 123 digitos"print(re.sub("d?", "-", string))
-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-
Contadores
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
string = "Hola, soy un string con 1 12 123 1234 1234digitos"print(re.findall("d{2}", string))
['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}
string = "Hola, soy un string con 1 12 123 1234 1234digitos"print(re.findall("d{2,5}", string))
['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
string = "Hola, soy un string con 1 12 123 1234 12345464168415641646451563416 digitos"print(re.findall("d{2,}", string))
['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
string = "Hola, soy un string con 1 12 123 1234 12345464168415641646451563416 digitos"print(re.findall("d{2,3}", string))
['12', '123', '123', '123', '454', '641', '684', '156', '416', '464', '515', '634', '16']
Clases
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
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))
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
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))
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
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))
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
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))
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
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))
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
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))
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 delimitador ?
como delimitador rápido
El ejemplo anterior lo podemos filtrar mediante \d+?[- ]
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))
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
El negador
Antes hemos visto que con \d
encotrábamos dígitos, pues con \D
encontramos todo lo que no sean dígitos
string1 = "E3s4t6e e1s2t3r5i6n7g8 t9i0e4n2e1 d4i5g7i9t0o5s2"print(re.findall("D", string1))
['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
string1 = "Letras ab27_ no letras ,.:;´ç"print(re.findall("W", string1))
[' ', ' ', ' ', ' ', ',', '.', ':', ';', '´']
Si ponemos \S
encontraremos todo lo que no sean espacios
print(re.findall("S", string1))
['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 ^
string1 = "1234567890"print(re.findall("[^5-9]", string1))
['1', '2', '3', '4', '0']
Volviendo al ejemplo de los números de teléfono de antes, podemos filtrarlos mediante los siguiente
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"
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
El principio ^
y final de línea $
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
string1 = "linea 1"string2 = "2ª linea"print(re.findall("^d", string1))print(re.findall("^d", string2))
[]['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
string1 = "linea 1"string2 = "2ª linea"print(re.findall("d$", string1))print(re.findall("d$", string2))
['1'][]
Esto solo ocurre en el string1
Ejemplos prácticos
Logs
Si en el siguiente log queremos encontrar solo los WARN
s
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
['[LOG ENTRY] [WARN] The system may be down','[LOG ENTRY] [WARN] Microsoft just bought Github']
Número de teléfono
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, -
, .
tel = """55565856-58-1156.58.1156.78-9865 09 8776y87r9845y78-5678.87 6578 54-56+52156581158-11-11#24655256048p12355256048e123"""result = re.findall("+?d{2,3}[^da-zA-Z\n]?d{2,3}[^da-zA-Z\n]?d{2,3}[#pe]?d*", tel)result
['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 laa
laz
, ni una letra de laA
a laZ
, 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 laa
laz
, ni una letra de laA
a laZ
, 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#
, comop
, comoe
\d*
: Por último que haya cero o todos los números
URLs
urls = """url: https://www.instagram.com/p/BXB4zsUlW5Z/?taken-by=beco.mxurl: http://instagram.com/p/blablablahurl: http://itam.mx/testhttp://instagram.com/p/blablablahhttps://www.vanguarsoft.com.vehttp://platzi.comhttps://traetelo.nethttps://traetelo.net/images archivo.jspurl: https://subdominio.traetelo.neturl: https://www.instagram.com/p/BXB4zsUlW5Z/?taken-by=beco.mxurl: http://instagram.com/p/blablablahurl: http://itam.mx/testhttp://instagram.com/p/blablablahhttps://www.google.com.co/https://sub.dominio.de.alguien.com/archivo.htmlhttps://en.wikipedia.org/wiki/.orghttps://cdn-microsoft.org/image/seixo2t9sjl_22.jpghttps://hola.pizzahttps://platzi.com/clases/1301-expresiones-regulares/11860-urls9102/ clasehttps://api.giphy.com/v1/gifs/search?q=Rick and Morty&limit=10&api_key=DG3hItPp5HIRNC0nit3AOR7eQZAehttp://localhost:3000/something?color1=red&color2=bluehttp://localhost:3000/display/post?size=smallhttp://localhost:3000/?name=satyamhttp://localhost:3000/scanned?orderid=234http://localhost:3000/getUsers?userId=12354411&name=Billyhttp://localhost:3000/getUsers?userId=12354411http://localhost:3000/search?city=Barcelonawww.sitiodeejemplo.net/pagina.php?nombredevalor1=valor1&nombredevalor2=valor2"""result = re.findall("https?://[w-.]+.w{2,6}/?S*", urls)result
['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 porhttp
s?
: A continuación puede haber o no unas
:\/\/
: 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
Mails
mails = """esto.es_un.mail@mail.comesto.es_un.mail+complejo@mail.comdominio.comrodrigo.jimenez@yahoo.com.mxruben@starbucks.comesto_no$es_email@dominio.comno_se_de_internet3@hotmail.com"""result = re.findall("[w._]{5,30}+?[w._]{0,10}@[w.-]{2,}.w{2,6}", mails)result
['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
Localizaciones
Hay dos posibles manera de dar localizaciones, por lo que analizamos las dos
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
['-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
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}sd{1,2}'sd{1,2}.d{2,2}\"[WE],s?-?d{1,3}sd{1,2}'sd{1,2}.d{2,2}\"[SN]", loc)result
['-99 12' 34.08"W, 19 34' 56.98"N', '-34 54' 32.00"E, -3 21' 67.00"S']
print(result[0])print(result[1])
-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 letraW
o la letraE
,
: 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 letraS
o la letraN
Nombres
nombres = """Camilo Sarmiento GálvezAlejandro Pliego AbastoMilagros Reyes JapónSamuel París ArrabalJuan Pablo TafallaAxel Gálvez VelázquezÓscar Montreal AparicioJacobo Pozo TassisGuillermo Ordóñez EspigaEduardo Pousa CurbeloIvanna Bienvenida KevinAda Tasis LópezLuciana Sáenz GarcíaFlorencia Sainz MárquzCatarina Cazalla LombardaPaloma Gallo PerroMargarita Quesada FlorezVicente Fox QuesadaIris GracianiAsunción CarballarConstanza MuñozManuel Andres García Márquez"""result = re.findall("[A-ZÁÉÍÓÚ][a-záéíóú]+s[A-ZÁÉÍÓÚ][a-záéíóú]+s[A-ZÁÉÍÓÚ][a-záéíóú]+", nombres)result
['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 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','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
Búsqueda y remplazo
Vamos a descargarnos un archivo con un montón de películas históricas
# download file from urlimport urllib.requesturl = "https://static.platzi.com/media/tmp/class-files/github/moviedemo/moviedemo-master/movies.dat"urllib.request.urlretrieve(url, "movies.dat")
---------------------------------------------------------------------------HTTPError Traceback (most recent call last)Cell In[43], line 42 import urllib.request3 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 responseFile ~/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's632 # 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 responseFile ~/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 resultFile ~/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
file = open("movies.dat", "r")for i, line in enumerate(file):print(line, end="")if i == 10:breakfile.close()
1::Toy Story (1995)::Adventure|Animation|Children|Comedy|Fantasy2::Jumanji (1995)::Adventure|Children|Fantasy3::Grumpier Old Men (1995)::Comedy|Romance4::Waiting to Exhale (1995)::Comedy|Drama|Romance5::Father of the Bride Part II (1995)::Comedy6::Heat (1995)::Action|Crime|Thriller7::Sabrina (1995)::Comedy|Romance8::Tom and Huck (1995)::Adventure|Children9::Sudden Death (1995)::Action10::GoldenEye (1995)::Action|Adventure|Thriller11::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
pattern = re.compile(r"^d+::([ws:,().-'&¡!/¿?ÁÉÍÓÚáéíóú+*$#°'"[]@·]+)s((d{4,4}))::(.*)$")file = open("movies.dat", "r")file_filtered = open("movies.csv", "w")file_filtered.write("title,year,genders ")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)} ")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
import pandas as pd
df = pd.read_csv("movies.csv", sep=";;", engine="python")
df.head()
Cheatsheet
Aquí tienes un cheatsheet con un montón de patrones