Introdução às Expressões Regulares - REGEX - no Python
Neste artigo eu vou abordar as regexes , ou "expressões regulares". É continuação do post sobre o projeto Um Futebol de Emoções. Se você já conhece o tema, talvez seja uma boa oportunidade de trazer sua contribuição nos comentários, rever conceitos, exemplos. Caso contrário, te convido a mergulhar nesta introdução ao tratamento automatizado de texto, uma ferramenta poderosa em quase todas as linguagens de programação e tarefas de Data Science (análise de textos, sentimentos, criação de variáveis para modelos, etc.). Para contextualizar, na figura abaixo o esquema "resumido" de onde está o projeto: depois de extrair os dados do Twiter, é hora de tratá-los, para depois seguir com a geração de wordclouds melhores das que já são geradas hoje e dar seguimento em análises mais avançadas.
Antes de aplicar no python, é preciso entender como uma REGEX funciona, no sentido de conhecer o que ela é capaz, os comandos principais, e por aí vai. Aí com isso você pode botar a mão na massa, aplicando as funções que ele oferece para manipulação das regexes. Então enquanto eu vou escrevendo o próximo post você já mete bronca neste que material não vai faltar.
O que é REGEX?
A maioria das referências aponta o matemático americano Stephen Cole Kleene como o criador do conceito de Linguagem Regular (é de onde vem o nome...) em 1951, dentro do campo da Ciência da Computação Teórica, que estuda, dentre outras coisas, a descrição e classificação de linguagens formais. Daí nascem as expressões regulares, como uma forma lógica para criação de padrões de busca visando a identificação dos mesmos em editores de texto na época. Com o desenvolvimento dos computadores e softwares de lá para cá, foram criados diferentes padrões de expressões regulares (sendo os mais conhecidos o POSIX e o PERL) e diferentes implementações dependendo da linguagem utilizada. Este artigo vai focar na implementação aceita pelo python visando a utilização das regexes para o tratamento e limpeza de tweets, mas elas podem ser utilizadas em qualquer corpo de texto (livros, documentos, colunas em um banco de dados, etc.).
Fica mais fácil entender com um exemplo prático que praticamente todo mundo já experimentou. Se você já se cadastrou em algum site e por acaso digitou um e-mail, um telefone ou até um cpf incorretamente, deve ter sido criticado, ou seja, o programador que fez o código da página utilizou alguma função para validar se a sua entrada estava dentro de um padrão esperado. Provavelmente ele utilizou REGEX para isso, pois essas informações de cadastro possuem um padrão bem definido e portanto passíveis de serem identificadas com as expressões regulares.
regex101.com
Como ainda não entraremos de fato no python, procurei uma forma de ilustrar como as expressões regulares se comportam, ilustrando o texto. Existem muitas ferramentas online que ajudam na construção de expressões regulares, descrevem os comandos básicos e permitem inclusive testar a expressão criada com texto que você copia e cola no campo apropriado, em tempo real. São ótimas para você testar a sua regex sem precisar do python. Mais que isso, é uma forma de "debugar" a sua regex e entender o que está acontecendo nos bastidores. Uma dessas ferramentas - que eu achei simplesmente fantástica - é a que o site regex101.com fornece. Depois posso fazer um post só para descrever essa ferramenta, mas desde já eu te digo que a possibilidade de salvar as regexes e compartilhá-las via link realmente me encantou. E é por isso que eu a utilizarei para demonstrar os resultados daqui para frente. Bora começar?
Regex Básico
A construção de uma regex é estratégica, ou seja, deve-se considerar o que realmente se quer obter dela, qual o objetivo ela deve cumprir, e entender as principais estruturas que as compõem permitirão que você realize a grande maioria das tarefas necessárias. Basicamente, as expressões regulares são compostas por caracteres literais e pelos metacaracteres. Os caracteres literais são o que o nome diz: são os caracteres utilizados para descrever os padrões que se quer identificar. São as letras, os números, etc. Se você quiser identificar um padrão de texto bem simples e que não varia, como o termo "direita", basta mencioná-lo em sua forma crua que o regex o identificará em todas as linhas:
Esse método é o caso mais simples e direto da aplicação de regex, que inclusive não envolve a utilização das estruturas especiais da regex no python. Obviamente, não seria necessário utilizar regex aqui, existem funções de texto para isso, como replace, na maioria dos editores de texto e linguagens de programação. Para começar a identificação de padrões com as expressões regulares, você deve entender os metacaracteres.
Metacaracteres
São 14 os metacaracteres das expressões regulares: . ^ $ * + ? { } [ ] \ | ( ) . Estes caracteres são chamados assim porque carregam consigo significados especiais, e por serem metacracteres, não fazem correspondência consigo mesmo. Com eles, conseguimos combinar os caracteres literais para construir o padrão de busca que precisamos. A lista abaixo os resume, antes que possamos entrar nos detalhes de cada um, e serve como referência rápida:
O metacaractere . (curinga)
O primeiro metacaractere é o ponto (.) e ele identifica qualquer caractere, exceto uma nova linha, por isso é chamado de curinga. Caso você queira que o . considere também uma nova linha (\n), basta ativar no regex101 a opção SINGLELINE no mesmo local que fizemos anteriormente para ver o comportamento. Desta forma, uma das construções possíveis para identificar a palavra futebol independentemente dela começar com F ou f seria esta:
Deve-se ter cuidado ao se utilizar este metacaractere pois ele pode facilmente se tornar um problema. Nesse caso específico, se alguém tivesse escrito "dutebol" ou "gutebol" (erros de digitação), a regex também faria correspondência, incorretamente. Outro exemplo: vamos supor que queiramos identificar datas, mas não sabemos qual separador foi utilizado pelo usuário. Você pode pensar: "Opa, boa oportunidade para usar o ponto!". O \d serve para identificar qualquer número nas expressões regulares. Vamos ver como fica? Se liga:
Legal! Ele identificou todas as datas, certo? Não! Tá errado. Na medida que utilizamos o ponto e ele fará correspondência com qualquer caractere, na última linha a expressão regular pegou uma sequência de números que só parece ser a data, mas obviamente não é porque não podemos ter números como separadores de datas! Vamos seguindo.
Metacaracteres de Ancoragem
São metacaracteres de tamanho zero, ou seja, não fazem correspondência a caracteres, e sim a determinadas posições, neste caso, início e fim da cadeia de caracteres. São extremamente úteis para validações rigorosas na entrada de informações em formulários evitando, por exemplo, que espaços sejam digitados sem querer pelo usuário antes ou depois de uma data de nascimento. Vamos agora detalhar um pouco mais esses dois metacaracteres com exemplos.
O metacaractere ^
O metacaractere ^ identifica a posição imediatamente anterior ao primeiro caractere de texto: o início da cadeia de caracteres onde se deseja identificar o padrão. É bem simples mesmo, veja o exemplo:
Percebeu que agora não temos uma correspondência mesmo com o termo sendo o primeiro da frase? Pode parecer erro, mas não é. Você lembra da definição do metacaractere? Ele identifica o termo que acontece no início da cadeia de caracteres que fornecemos. Neste segundo exemplo, o termo no início da cadeia é a letra O. A cadeia de caracteres engloba as duas frases. Se quiséssemos identificar a palavra Futebol no início de qualquer linha do texto, precisamos antes habilitar o modo MULTILINE, que veremos em momento oportuno. Como o regex101 permite simular o comportamento do python, não custa dar o exemplo. Observe que agora no canto superior direito está descrito "gim" ao invés de "gi" como nas regexes anteriores (basta clicar para habilitar a opção):
Atenção: dependendo de onde é utilizado, a função deste caractere pode mudar. Dentro de classes de caracteres, por exemplo, quando é o primeiro caractere dentro de uma classe de caracteres [^abc] este metacaractere passa a indicar negação, ou seja, "procure por todos os caracteres que não são a ou b ou c". Retornaremos nesta observação quando falarmos das classes de caracteres, mais à frente.
O metacaractere $
O metacaractere $ faz justamente o contrário de ^: identifica a posição imediatamente posterior ao último caractere de texto, ou seja, o fim da cadeia de caracteres onde se deseja identificar o padrão:
Repare que o $ deve estar localizado imediatamente após o padrão que se quer identificar ao final da cadeia de caracteres. A observação quando ao modo multiline vale para o $ também. Se esta for uma necessidade, basta ativar a opção no regex101 para ver como funciona. Se você misturar os dois metacaracteres numa expressão regular, poderá evitar entrada com espaços, como já mencionei acima. Finja que os registros abaixo são as linhas de um banco de dados. Queremos identificar se existem espaços antes ou depois da palavra futebol (não importa se começa ou não com F ou f):
Utilizando a regex ^\s+|\s+$ fomos capazes de identificar todos os registros (no regex101 o espaço é representado por uma bolinha cinza, que neste caso foi realçada com azul quando uma correspondência foi encontrada) com espaços antes ou depois da palavra futebol. Você pode estranhar: "Ei, peraí, não sei nada disso aí, não entendi nada!". Calma, te explico:
^ e $ você acabou de aprender. \s é sinônimo de espaço nas regexes do python, e o + significa "pelo menos uma vez". Logo, ^\s+ está procurando por um espaço no início da cadeia e \s+$, no final. O | é o operador OU, e se você juntar tudo dá para falar a expressão em português claríssimo: "procure um espaço no início OU no fim da cadeia de caracteres". Você pode inclusive relacionar esta regex com os comandos de busca e substituição dos principais softwares: "encontrar/find" dos editores de texto, STRIP (SAS), ARRUMAR (Excel), e por aí vai. Maneiro né? Vamos para o próximo tópico que todas essas estruturas serão abordadas ao longo do texto.
Metacaracteres de Repetição
O metacaractere *
O padrão à esquerda de * sendo procurado poderá ocorrer 0 ou mais (infinitas) vezes
Neste caso, o interpretador vai avançando da esquerda para a direita e verifica se o caractere à esquerda é ou não "E". Se for, existe uma correspondência e o padrão de busca é pintado de azul.
O metacaractere +
O padrão à esquerda de + sendo procurado poderá ocorrer 1 ou mais (infinitas) vezes.
Neste caso, o interpretador vai avançando da esquerda para a direita e verifica se o caractere à esquerda é ou não "E". Se for, existe uma correspondência e o padrão de busca é pintado de azul.
O metacaractere ?
O padrão à esquerda de ? sendo procurado poderá ocorrer 0 ou 1 vez. Na prática, está dizendo que o padrão pode ou não existir.
É muito interessante perceber como o avaliador do regex101.com exibe os resultados para nós. Como os metacaracteres * e + são chamados de gananciosos, ao encontrarem o caractere que estamos procurando, o interpretador continua procurando outras repetições em sequência, e só marca o resultado quando acaba de encontrar todas. Por isso nos dois primeiros exemplos temos uma faixa azul contínua no caso da palavra PENALTI. A diferença entre os dois é que no caso do *, como o "0" é um resultado (estamos procurando por 0 ou mais), ele marca os resultados "0" com uma linha vertical rosa tracejada. Já no terceiro exemplo, com o ?, a busca é caractere a caractere, e embora o resultado do "0" se comporte da mesma forma que o primeiro exemplo pelo fato de ser um resultado, o "1", ou seja, a correspondência para a letra "E" na palavra PENALTI é tratada com alternância do tom azul, reforçando a inspeção seguida de resultado para cada caractere, em comportamento "não ganancioso".
Os metacaracteres {} - Limitadores de repetição
O último metacaractere de repetição permite que se especifique quantas vezes o padrão à esquerda pode se repetir. Pode ter três configurações: {n},{n,} e {n,m}, com n denotando "mínimo" e m, "máximo". Se a primeira forma for utilizada, o padrão poderá ocorrer exatamente n vezes. Se a segunda forma for utilizada, o padrão poderá se repetir no mínimo n vezes, sem limitação do número máximo de ocorrênicas. E se a terceira forma for utilizada, então estaremos exigindo que o padrão ocorra no mínimo n vezes e no máximo m vezes, de forma exata. Assim, pode-se dizer que este metacaractere generaliza todos os metacaracteres de repetição, pois: {0,1} = ?, {0,} = *, e {1,} = +. Veja o caso de uma transmissão cujo evento foi um PENALTI. Para transmitir emoção, o narrador do Fluminense repetiu várias vezes o "E". Vamos ver as três configurações de {}:
{n} - O padrão sendo procurado ocorrerá exatamente “n” vezes.
Não há resultados, pois especificamos que o E deveria ocorrer exatamente duas vezes, mas na verdade ele ocorre 16 vezes. Construiríamos uma REGEX assim se suspeitássemos, por exemplo, que pode haver erro na grafia da palavra penalti (e que, especificamente, podem haver dois E) , e estaríamos respondendo à pergunta: "É possível que numa transmissão, ao narrar um penalti, o narrador erre a grafia da palavra penalti, duplicando a letra E?"
{n, } - O padrão sendo procurado poderá ocorrer pelo menos “n” vezes.
Relaxando o critério, passamos a ter resultados, pois dissemos com a REGEX que o "E" ocorreria no mínimo 2 vezes, e no máximo, infinitas vezes. O interpretador encontrou as 16 repetições e então conseguiu identificar o padrão no texto. Construiríamos uma REGEX assim se suspeitássemos que este tipo de narração é possível, e estaríamos respondendo à pergunta: "É possível que numa transmissão, ao narrar um penalti, o narrador se empolgue tanto ao ponto de repetir n vezes a letra E?"
{n, m} - O padrão sendo procurado ocorrerá exatamente entre “m” e “n” vezes.
Pode ser que queiramos saber se há casos onde houve uma repetição exagerada da letra E na palavra penalti. E, "por acaso", imaginamos que essa repetição pode ser de no mínimo 2 e no máximo 16, exatamente. O interpretador REGEX vai identificar neste caso que estamos examinando, pois o padrão de repetição da palavra E é exatamente esse. Repare que se utilizássemos uma repetição a menos {2,15}, o padrão não seria identificado:
Mais uma vez, sublinho a importância do objetivo na definição da estratégia de construção da REGEX. Se quisermos identificar a palavra PENALTI com n repetições do "E", pois entendemos que para transmitir emoção na linguagem escrita este é um recurso indispensável, faz-se como demonstramos. Agora, se o intuito for identificar apenas a repetição do caractere E (para substituição por somente uma ocorrência, ou seja, transformar PEEEEEEEEEEEEEEEENALTI em PENALTI e assim limpar o tweet, o padrão seria:
Metacaracteres para criação de Classes e Agrupamentos
O metacaractere \
É importante entender bem o significado deste metacaractere. Ele retira o significado especial de um metacaractere, “escapando-o”. Por exemplo, se na regex abaixo deixamos como está, e queremos incluir os parênteses, não teremos o resultado que queremos. Somente os números são identificados, pois o interpretador acha que queremos identificar dígitos dentro de um grupo (é isso que os parenteses fazem):
Contudo, se utilizamos o \ antes dos mesmos, os escapamos e eles perdem a propriedade de metacaracteres, tornando-se um caractere literal, e a correspondência é feita:
O metacaractere []
Nas expressões regulares, é possível definir que se deseja identificar apenas um grupo restrito de caracteres. Isso é feito colocando-se os caracteres desejados entre colchetes [ ]. O interpretador REGEX então testará cada caractere no texto e retornará um resultado positivo se achar quaisquer um dos caracteres presentes na classe de caracteres. É uma forma de fazer uma pesquisa "OU" com as expressões regulares. Acompanhe:
Identificamos a palavra substituição tendo ela sido escrita da forma correta ou não (ç ou c).
Essas classes de caracteres podem ser definidas por intervalos quando se utiliza o conjunto UNICODE de caracteres, que possui a sequência ordenada que possibilita este tipo de recurso. Basta inserir os caracteres inicial e final do intervalo que se deseja recuperar separados por hífen (-). Abaixo eu trago a parte do UNICODE que nos interessa neste momento. Marquei com cores as principais sequências que são utilizadas:
Essas sequências já são conhecidas e algumas possuem até abreviações que tornam a REGEX mais fácil de construir e validar. Para utilizar as sequências acima destacadas basta-se utilizar a seguinte notação dentro dos colchetes:
[a-z] , para caracteres minúsculos (a,b,c,d,...,z)
[A-Z] , para caracteres maiúsculos (A,B,C,D,...,Z)
[0-9] , para dígitos de 0 a 9 (0,1,2,3,...,9)
[À-ü], para caracteres acentuados (À,Á,Â,Ã,...,ü)
Se juntarmos os três primeiros intervalos e acrescentarmos o underscore _, teremos o que para as expressões regulares pode ser considerada uma palavra. Alguns desses intervalos possuem abreviação, por exemplo os dígitos 0-9 podem ser expressos pela abreviação \d, e estas listas abreviadas possuem uma representação maiúscula que significa negação. A negação de \d é \D. Um compilado dos intervalos de caracteres, suas abreviações e negações são resumidas na tabela abaixo:
Vou te apresentar agora \w, \W, \b e \B, intervalos que ajudam em muito a abreviar e facilitar a escrita de expressões regulares.
O metacaractere \w
Este metacaractere abrevia o padrão a-zA-Z0-9_ (padrão para identificar palavras válidas em expressões regulares) deixando a sua regex muito mais limpa e fácil de validar quando necessário. Não à toa, o w é do inglês WORD (palavra). Veja alguns exemplos:
Se estivéssemos substituindo tudo que pode ser identificado com \w por "" (vazio), o texto ficaria assim:
Mais fácil de entender o que \w faz, certo?
O Metacaractere \W
Tudo aquilo que não é palavra pode ser identificado de forma abreviada pelo metacarctere \W. É literalmente a negação de \w, ou seja ^a-zA-Z0-9_ . O exemplo deixa claro do que serão identificados pontos, espaços, barras, emojis, etc.:
Se estivéssemos substituindo essas correspondências, teríamos exatamente o contrário do que obteríamos com \w, ficando somente com os números, letras e o underscore, se presente:
O metacaractere \b
Este metacaractere pode ser utilizado para identificar padrões no início ou final de uma cadeia de caracteres ou mesmo uma palavra no texto.
Lembrando que uma PALAVRA em termos de REGEX é constituída pelos caracteres A-Za-z0-9_, ou seja, tudo que pode ser identificado por \w, \b como uma fronteira de palavra identificará tudo aquilo que estiver entre \w e \W. O "b" em \b não é de "blank", e sim de "boundary" (fronteira).
IMPORTANTE: Uma vez que \b tem tamanho zero ("zero length"), as posições antes das primeira e última palavras de uma linha são inspecionadas e caso as cadeias imediatamente após ou antes dessas posições sejam palavras REGEX válidas, haverá correspondência. Veja os exemplos:
Na primeira frase, há uma correspondência para "amarelo", pois ele se encontra entre dois caracteres que não são alfanuméricos, ou seja, o que está antes do "a" e depois do "o" de "amarelo". É como se o interpretador REGEX transformasse o nosso termo de interesse, mais o que vem imediatamente antes e depois em \w e \W:
"amarelo "= \W\w\w\w\w\w\w\w\W
E aí, imediatamente ele acha as correspondências que queremos com o \b em nossa REGEX:
"amarelo "= \W\w\w\w\w\w\w\w\W > \b\w\w\w\w\w\w\w\b
Repare o quão importante é a definição de "token de tamanho zero": é como se ele não avançasse pela cadeia de caracteres, somente trocando o \W\w do início pelo primeiro \b e o \w\W do fim pelo segundo \b. Quando ele faz isso, \b\w\w\w\w\w\w\w\b pode ser "transformado" para \bamarelo\b e torna-se igual à nossa REGEX. É quando ele identifica a correspondência. Por este motivo esta é uma das melhores formas para se identificar palavras separadas por espaços em um texto.
O metacaractere \b também pode ser utilizado para identificar partículas de texto menores no início e fim das palavras. Tudo dependerá de como você o configurará em sua REGEX. Vamos ver alguns exemplos para ficar mais claro.
Metacaractere \b para identificar padrões no início e fim da cadeia de caracteres
Como ele identificará como fronteira de palavra tudo que estiver entre \w e \W (e não entre \w\w ou \W\W), temos os seguintes casos:
\b vem antes do padrão a ser identificado:
Explicação:
Com esta configuração, identificaremos todos as letras "a" (maiúsculas ou minúsculas) que se encontram após um caractere que não é alfanumérico. Por isso em todas as frases não são identificados somente as letras "a" de amarelo e acréscimos, mas também aqueles que vêm após a hashtag (#), pois este não é considerado um caractere alfanumérico. A correspondência ocorrerá mesmo para palavras que iniciam a linha, pois o que antecede o primeiro caractere de uma linha não é considerado um caractere alfanumérico.
\b vem após o padrão a ser identificado:
Explicação:
Agora \b vem depois do padrão, e portanto fará correspondências quando a letra "a" (maiúscula ou minúscula) anteceder um caractere que não é alfanumérico, mesmo quando a palavra for a última na linha.
\b vem antes e após o padrão a ser identificado:
Explicação:
Esta situação foi a que explicamos no início deste bloco. Contudo chamamos atenção aqui que podem haver falsos positivos, uma vez que em algumas situações o padrão a ser identificado pode estar entre dois caracteres não alfanuméricos que não sejam os espaços, como ocorre na primeira linha, onde PAL está entre "#" e " ". Se o seu texto envolver muitas dessas possibilidades, talvez seja melhor substituir \bPAL\b por \s(PAL)\s :
O metacaractere \B
Retorna a correspondência onde os caracteres estão presentes, mas no meio da cadeia de caracteres onde são procurados, e não no início ou fim, que é função do \b. Outra forma de descrever este caractere é dizendo que ele fará correspondência onde \b não faz, afinal \B é a negação de \b. Estar posicionado na frente ou após o padrão sendo procurado faz total diferença também.
\B vem antes do padrão a ser identificado:
Primeiro o intepretador vai para onde o \b não fará correspondência. Ou seja, ele já começa a procurar no meio das cadeias de caracteres. Neste caso, ele foi um a um procurando em cada "meio de cadeia de caracteres" pelo padrão "f" que desejamos. Ele então encontra na palavra defesa e pinta de azul.
\B vem antes e após o padrão a ser identificado:
Já nesta situação, \B está após o padrão "f" e além de defesa, há correspondência com as duas ocorrências de "faz", porque este metacaractere se posiciona entre o "f" e o "a", e como está posicionado depois do "f", o "padrão f\Baz é encontrado.
O metacaractere \s
Este metacaractere vai corresponder a qualquer espaço em branco, inclusive os invisíveis tabulação (\t), uma nova linha (\n), uma quebra de linha (\r) ou de página (\f). Este inclusive é o ponto onde o \s se diferencia do . (metacaractere curinga), pois este não faz correspondência com uma nova linha (\n). Veja o exemplo:
Note que quando o tweet é extraído, a quebra de linha (que é invísivel) torna-se evidenciada pelo \n de forma textual, visível, e aí deixa de ser identificada como espaço. Falarei mais sobre isso no post sobre extração de textos do Tweeter.
O metacaractere \S
O \S é exatamente o oposto de \s, e identifica tudo que não é espaço em branco. Uma figura vale mais que mil palavras (faça a comparação com o print acima):
O metacaractere | (operador OU)
Permite identificar vários padrões na mesma REGEX. É o operador OU das regexes.
Repare que se incluirmos o padrão entre parênteses, estaremos criando um grupo, e o regex101 muda a correspondência de azul para verde de modo a indicar essa mudança.
Quando falamos sobre o | é uma bora hora para mencionarmos que o interpretador regex é "ganancioso". Assim que ele acha um dos padrões separados por |, ele para a busca e retorna o resultado. Com isso, se construímos a regex a seguir para identificar o padrão "limpa a zaga adversária" sem saber que o interpretador se comporta desta forma, não conseguiremos identificar o erro:
Portanto, para construir regexes melhores com o operador OU, devemos sempre nos atentar que caso haja padrões muito parecidos (onde só uma palavra no final muda, como mostrado acima), o maior padrão sempre deve vir primeiro, para que seja dada prioridade para o "mais completo", e só depois ele tente achar o menos completo.
Agora sim, ele identifica os dois padrões (um OU outro) corretamente. Deve-se atentar para o fato de que se usado dentro de colchetes o | torna-se um caractere como outro qualquer, deixando de ser o operador OU. Este comportamento se estende aos metacaracteres +, *, ., |, (), $,{}.
O metacaractere ()
Utilizar parênteses agrupa o que está dentro dos mesmos, permitindo por exemplo que separemos o que está dentro deles dos demais elementos da regex. Quando queremos utilizar o operador OU ( | ), também os utilizamos dentro dos parênteses para que as opções se restrinjam ao grupo que acabou de ser criado.
Ao utilizar os parênteses também criamos automaticamente um grupo numerado. Este grupo numerado guarda o padrão que que foi encontrado no texto, e essa funcionalidade pode ser utilizada em operações complexas de regex que não veremos aqui pois se trata de um tópico mais avançado.
Voltando aos grupos sem captura, vamos pegar um exemplo real do "Futebol de Emoções", no qual eu tento identificar, com regex, trechos que indiquem a narração de um "cruzamento pela direita" (esta regex é mais complexa, mas não se preocupe):
Partimos do pressuposto que os cruzamentos sempre serão descritos dessa forma: "pela direita", seguido possivelmente de um espaço, que por sua vez precede uma "," OU "e", seguido ou não por um outro espaço, terminando finalmente com duas possibilidades: "cruza" OU "cruzou". É claro que eu inseri o "pela esquerda", afinal de contas estamos falando há um tempo sobre o operador OU e parenteses. Tá tudo em casa :-). Logo:
1- (pela direita|pela esquerda)+ o lado precisa figurar no padrão
2- \s* seguido ou não de espaço
3- (,|e)* seguido ou não de "," ou "e"
4- \s* seguido ou não de espaço
5- (cruza|cruzou) e por fim, por "cruza" OU cruzou"
Quando todas as partes são unidas numa só linha temos a regex do exemplo:
(pela direita|pela esquerda)+\s*(,|e)*\s*(cruza|cruzou)
No primeiro parênteses, utilizo | para identificar os dois lados do campo, sem precisar criar duas regexes. No segundo, para flexibilizar o conector entre o lado do campo e o a ação em si, que é o cruzamento. Como dependendo do conector eu posso ter espaços antes e/ou depois, \s* envolve esse segundo parenteses pelos dois lados. E claro, no último, estamos dando conta de diversos estilos de narração, que podem acontecer citando a ação no presente ou no passado.
REGEX e Pontuação
Em português, contamos com muitos caracteres acentuados e uma tarefa muito importante em processos de limpeza de texto é a remoção dessas pontuações, que na maioria das análises não trazem valor e podem ser dispensadas:
Como você pode ver, se estivéssemos tentando identificar um padrão para substituição (remoção), teríamos todos os 32 caracteres definidos como pontuação pelo Python identificados com sucesso. Repare no "\" utilizado para escapar os metacaracteres quando necessário. O resultado ficaria:
Generalizando as REGEXES
Até aqui trabalhamos de forma bem básica, pois este era o intuito mesmo. Contudo, o poder das expressões regulares está justamente no fato de generalizarem padrões de busca. Então vamos dar alguns exemplos adicionais de generalização para que você tenha contato e os treine no regex101.com.
([Pp]ython(, )?)+
Esta regex vai corresponder a uma ou mais ocorrências da palavra python começando ou não por maiúscula e seguida ou não de uma vírgula, que também pode ou não ser seguida de um espaço:
[\w]*(mente)
Com esta, podemos fazer a correspondência com qualquer advérbio de modo. Veja como é fácil generalizar para qualquer palavra com sufixo mente:
REGEXES úteis para qualquer programador
Com esses exemplos de generalização, podemos partir para uma listinha bacana com expressões regulares úteis para qualquer programador, afinal de contas ninguém precisa reinventar a roda, não é mesmo?
CEP d{5}-d{3}
PLACAS DE VEÍCULOS ([a-z]{3}|[A-Z]{3})-d{4}
CPF \d{3}\.\d{3}\.\d{3}\-\d{2}
CNPJ \d{2}\.\d{3}\.\d{3}\/\d{4}\-\d{2}
TELEFONE (\()*[1-9]{2}(\))*(\s)*[9][\-\.]*[0-9]{4}[\-\.]*[0-9]{4}
É quando aplicamos as regexes no python com arquivos de texto e dataframes reais é que os problemas surgem, mas também o fazem as possibilidades de ver as correspondências, buscas e substituições acontecendo na sua frente. Dá para ver os grupos que os parênteses capturam, utilizar as flags e abordar outros tipos de correspondências, como as de emojis.
No projeto eu utilizo o python e as regexes para identificar eventos nas transmissões dos jogos pelo tweeter, para aumentar a precisão na extração das informações e no tratamento de palavrões, que são emoção pura!
Espero que você tenha chego até aqui e gostado, identificando valor para as suas análises. Dê feedback nos comentários, participe da discussão, pergunte, explique, melhore...esse espaço é seu!