Expressões regulares com Python
Esse é um tópico que sempre patinei bastante, ainda tenho alguma dificuldade pra ler e interpretar expressões regulares no python, então estou escrevendo esse artigo principalmente para me ajudar 🙂
O python tem uma biblioteca bem poderosa para expressões regulares:
import re
A função match serve para casar uma string em um texto, por exemplo:
In [2]: re.match("The", "The book is on the table") Out[2]: <_sre.SRE_Match object; span=(0, 3), match='The'>
Note que a saída é um objeto que tem duas propriedades interessantes, span que devolve onde a string foi encontrada, no caso na posição 0 até 3, e a propriedade match que mostra o que foi encontrado. Mas a função match tem uma limitação, ela só funciona se a string buscada estiver no começo do texto.
Para encontrar strings ao longo do texto devemos usar a função search:
In [8]: re.search("book", "The book is on the table") Out[8]: <_sre.SRE_Match object; span=(4, 8), match='book'>
Mas o search se limita a retornar apenas uma ocorrência, para encontrar todas as ocorrências da string no texto usamos a função findall:
In [11]: re.findall("book", "The book is on the book table") Out[11]: ['book', 'book']
Nesse caso ele retornou uma lista com as strings encontradas.
Em expressão regular existem os metacaracteres, são caracteres especiais que usamos para dar match em determinados padrões, por exemplo o ‘. ‘ (ponto ) , que da match em qualquer caracter, exceto quebra de linha, por exemplo:
In [12]: re.match(".", "The book is on the book table") Out[12]: <_sre.SRE_Match object; span=(0, 1), match='T'> In [13]: re.match(".", "12341234") Out[13]: <_sre.SRE_Match object; span=(0, 1), match='1'> In [14]: re.match(".", "\t\t\t") Out[14]: <_sre.SRE_Match object; span=(0, 1), match='\t'> In [18]: print(re.match(".", "\n\t\t")) None
Com o search o funcionamento é similar:
In [33]: re.search('.', '\n\t\t') Out[33]: <_sre.SRE_Match object; span=(1, 2), match='\t'>
Note que nesse caso ele deu match no \t mesmo com o texto começando com \n, o search ignorou o \n e partiu para processar o próximo caractere.
O “.” (ponto) quando usado com findall tem um comportamento interessante, ele retorna uma lista de cada caractere da sequencia:
In [35]: re.findall(".", "The book") Out[35]: ['T', 'h', 'e', ' ', 'b', 'o', 'o', 'k']
Outros dois metacaracteres importantes são os do tipo âncora como o ^ e o $ , eles delimitam o início e o fim da string, por exemplo:
In [39]: re.findall("^.", "The book\n is on the\n book table") Out[39]: ['T']
Mas é possível pegar os caracteres de todas as linhas do texto:
In [41]: re.findall("^.", "The book\nis on the\nbook table", re.MULTILINE) Out[41]: ['T', 'i', 'b']
A âncora de fim de linha ( $ ) funciona de forma similar a ^:
In [47]: re.findall(".$", "The book\nis on the\nbook table", re.MULTILINE) Out[47]: ['k', 'e', 'e']
Podemos combinar as duas âncoras:
In [48]: re.match("^.$", "The") In [49]: re.match("^.$", "T") Out[49]: <_sre.SRE_Match object; span=(0, 1), match='T'> In [50]: re.match("^.$", "")
O “.” é muito útil porém muito abrangente , podemos limitar os caracters buscados, os colchetes “[ ]” funcionam como conjunto, por exemplo:
In [52]: re.findall("[abcdef]", "The book\nis on the\nbook table", re.MULTILINE) Out[52]: ['e', 'b', 'e', 'b', 'a', 'b', 'e']
Ele entrou dentro do conjunto de caracteres e fez uma busca para cada ocorrência bem sucedida.
Podemos também buscar pelos caracteres que não estão dentro da sequencia declarada dentro dos colchetes:
In [56]: re.findall("[^abcdef]", "The book\n", re.MULTILINE) Out[56]: ['T', 'h', ' ', 'o', 'o', 'k', '\n']
Existem também os ranges, declaramos eles com o ‘-‘ hifen, por exemplo:
In [57]: re.findall("[a-c]", "The book\n", re.MULTILINE) Out[57]: ['b'] In [58]: re.findall("[a-h]", "The book\n", re.MULTILINE) Out[58]: ['h', 'e', 'b'] In [60]: re.findall("[a-zA-Z]", "The book\n", re.MULTILINE) Out[60]: ['T', 'h', 'e', 'b', 'o', 'o', 'k']
Podemos usar ranges para números e caracteres, segue um exemplo de um range que abrange todos os caracteresque formam palavras:
In [61]: re.findall("[a-zA-Z0-9_]", "The book\n", re.MULTILINE) Out[61]: ['T', 'h', 'e', 'b', 'o', 'o', 'k']
Esse range é bem útil para filtrar campos de formulário em sites, e por isso usamos bastante esse padrão, por isso existe atalho para abreviar a digitação, o \w :
In [63]: re.findall("\w", "The book @\n", re.MULTILINE) Out[63]: ['T', 'h', 'e', 'b', 'o', 'o', 'k']
Existem outras abreviaturas para ranges, são elas:
- \d == [0-9]
- \D == [^0-9]
- \s == [\t\n\r\f\v]
- \S == [^\t\n\r\f\v]
- \w == [a-zA-Z0-9_]
- \W == [^a-zA-Z0-9_]
Todas sequências especiais começam com “\”. Isso gera problemas já que nosso texto pode vir com \ espalhadas no seu conteúdo, para isso temos que escapar a \, por exemplo:
Antes: olá \n Correto: olá \\n
Para evitar dores de cabeça e tornar o código mais legível podemos usar as Raw strings, alertando o python de que aquela string não contem caracteres especiais, por exemplo:
In [88]: re.match(r'\\www', r'\www.otimo.com.gr') Out[88]: <_sre.SRE_Match object; span=(0, 4), match='\\www'>
Podemos usar o metacaractere | para expressões do tipo OU:
In [91]: re.match('ww|ss', r'www.otimo.com.gr') Out[91]: <_sre.SRE_Match object; span=(0, 2), match='ww'>
Expressões regulares também suportam repetições:
In [96]: re.match(r'\w{5}', 'abcdef') Out[96]: <_sre.SRE_Match object; span=(0, 5), match='abcde'> In [97]: re.match(r'\w{5}', 'abcdefg') Out[97]: <_sre.SRE_Match object; span=(0, 5), match='abcde'> In [98]: re.match(r'\w{5}', 'abcdefg df') Out[98]: <_sre.SRE_Match object; span=(0, 5), match='abcde'> In [99]: re.match(r'\w{5}', 'abcd') In [100]:
Note que ele pegou apenas textos com mais de 4 caracteres, os textos que excederam esse número foram processado mas retornaram apenas o limite de 5 caracteres.
Essa repetição pode ser configurada para retornar os caracteres excedentes, bastando colocar uma vírgula:
In [100]: re.match(r'\w{5,}', 'abcdefg df') Out[100]: <_sre.SRE_Match object; span=(0, 7), match='abcdefg'>
Note que ele não pegou o “df”
Como você já suspeita, é possível configurar o mínimo e o máximo nas expressões:
In [108]: re.match(r'\w{2,6}', 'abcdefg df') Out[108]: <_sre.SRE_Match object; span=(0, 6), match='abcdef'>
O metacaractere “?” é usado de duas formas, para limitar o mínimo de repetições (equivalente a {,1}) e para transformar expressões greed em expressões lazy, por exemplo:
# Transformando a expressão greed em lazy In [111]: re.match(r'\w{2,}', 'abcdefg df') Out[111]: <_sre.SRE_Match object; span=(0, 7), match='abcdefg'> In [112]: re.match(r'\w{2,}?', 'abcdefg df') Out[112]: <_sre.SRE_Match object; span=(0, 2), match='ab'>
Já o metacaractere “*” é usado para pegar zero ou mais ocorrências ( equivalente a {,} ) :
In [120]: re.match(r'\w', 'abcdefg df') Out[120]: <_sre.SRE_Match object; span=(0, 1), match='a'> In [121]: re.match(r'\w*', 'abcdefg df') Out[121]: <_sre.SRE_Match object; span=(0, 7), match='abcdefg'>
O metacaractere “+” é usado para pegar 1 ou mais ocorrências ( equivale a { 1 , } ):
In [128]: re.match(r'\w+', '') In [129]: re.match(r'\w+', 'abcdefg df') Out[129]: <_sre.SRE_Match object; span=(0, 7), match='abcdefg'>
Um exemplo prático do uso do caractere “?” em uma expressão:
In [132]: re.findall(r'".+"?', 'src="blablabla" alt="altaltalt"') Out[132]: ['"blablabla" alt="altaltalt"']
Note que queriamos apenas os valores dos atributos de forma separada, mas como o “+” é greedy ele trouxe o resto da expressão, para resolver:
In [133]: re.findall(r'".+?"', 'src="bla bla bla" alt="altaltalt"') Out[133]: ['"bla bla bla"', '"altaltalt"']
Para buscar valores vazios nos atributos temos que substituir o + por * :
In [134]: re.findall(r'".+?"', 'src="" alt=""') Out[134]: ['"" alt="'] In [135]: re.findall(r'".*?"', 'src="" alt=""') Out[135]: ['""', '""']
Um exemplo fazendo parsing de uma tag HTML:
In [136]: html = '<input type="email" id="id_email" name="user mail">' In [137]: padrao = r'<(.+?) type="(.+?)" id="(.+?)" name="(.+?)"' In [138]: re.match(padrao, html).groups() Out[138]: ('input', 'email', 'id_email', 'user mail')
É possível criar um dicionário com os resultados da expressão regular:
In [143]: padrao = r'<(?P<tag>.+?) (?:(?:type="(?P<type>.+?)"|id="(?P<id>.+?)"|name="(P<name>.+?)") ?)*' In [144]: re.match(padrao, html).groups() Out[144]: ('input', 'email', 'id_email', None) In [145]: re.match(padrao, html).groupdict() Out[145]: {'id': 'id_email', 'tag': 'input', 'type': 'email'}
Espero que seja útil 😀
Inspiração: http://henriquebastos.net/
Share this content: