Expressões regulares (ou regular expressions, frequentemente abreviadas como regex) são sequências de caracteres usadas para verificar se um padrão existe em um texto (string).
Elas são amplamente usadas, por exemplo, para validar formatos, extrair informações específicas ou transformar dados.
Neste material, você vai aprender os principais conceitos de expressões regulares em Python. Começaremos importando o módulo re, responsável pelo suporte a regex na linguagem. Em seguida, você verá:
como usar caracteres simples para fazer correspondências;
como funcionam os metacaracteres (caracteres especiais);
como aplicar repetições nos padrões;
como criar grupos de captura e grupos nomeados;
como utilizar as principais funções do módulo re, como search, match, findall, finditer, split e sub;
como usar flags (modificadores) para ajustar o comportamento das buscas.
Ao longo do capítulo, exemplos práticos mostram como as expressões regulares podem ser aplicadas para resolver problemas comuns de extração, validação e limpeza de dados.
7.1 O módulo re
Em Python, as expressões regulares são suportadas pelo módulo re. Isso significa que, para começar a utilizá-las em seus scripts, é necessário importar esse módulo usando import:
import re
A biblioteca re em Python oferece diversas funções que fazem dela uma habilidade que vale a pena dominar. Todas recebem como primeiro argumento um padrão, ou seja, uma string especial que descreve o que procurar.
Função
O que faz
re.search(padrão, string)
Procura o padrão em qualquer posição da string; retorna um objeto Match ou None
re.match(padrão, string)
VVerifica se o padrão ocorre no início da string
re.findall(padrão, string)
Retorna todas as ocorrências do padrão como uma lista
re.finditer(padrão, string)
Retorna um iterador de objetos Match, permitindo acessar detalhes como posição e grupos de cada ocorrência
re.split(padrão, string)
Divide a string nos pontos onde o padrão é encontrado
re.sub(padrão, substituição, string)
Substitui todas as ocorrências do padrão por outro texto
Veremos a seguir a linguagem desses padrões. Neste material, você verá algumas delas em detalhes.
7.2 Sintaxe dos padrões
A “linguagem” das expressões regulares é formada por metacaracteres — símbolos com significado especial.
7.2.1 Metacaracteres de correspondência
Símbolo
Significado
.
Qualquer caractere, exceto quebra de linha
\d
Dígito decimal ([0-9])
\D
Qualquer caractere que não é dígito
\w
Caractere alfanumérico ou _ ([a-zA-Z0-9_])
\W
Qualquer caractere que não é alfanumérico
\s
Espaço em branco (espaço, tab, \n)
\S
Qualquer caractere que não é espaço em branco
[abc]
Qualquer um dos caracteres listados
[^abc]
Qualquer caractere exceto os listados
[a-z]
Qualquer caractere dentro do intervalo
\b
Limite de palavra (início ou fim de palavra)
\t
Tabulação
\n
Nova linha
\r
Corresponde ao return
7.2.2 Metacaracteres de quantidade
Símbolo
Significado
*
0 ou mais repetições
+
1 ou mais repetições
?
0 ou 1 repetição (torna o elemento opcional)
{m}
Exatamente m repetições
{m,n}
Entre m e n repetições
7.2.3 Âncoras, grupos e controle
Símbolo
Significado
^
Início da string
$
Fim da string
\A
Início absoluto da string
\Z
Fim absoluto da string
A\|B
A ou B
(...)
Grupo de captura
(?:...)
Grupo sem captura
(?P<nome> ...)
Grupo nomeado
\
Escape: remove o significado especial do próximo caractere
DicaStrings brutas (r"...")
Em Python, o caractere \ tem significado especial em strings comuns (por exemplo, \n representa nova linha e \t representa tabulação). Para evitar conflitos com os metacaracteres das expressões regulares (como \d, \w, \s), é recomendável escrever padrões regex usando strings brutas, com o prefixo r"...". Assim, o Python não interpreta essas sequências como escapes da linguagem:
re.search(r"\d+", texto) # correto — \d é interpretado pelo rere.search("\d+", texto) # ambíguo — Python tenta interpretar \d antes
7.3re.search
A função re.search percorre toda a string em busca da primeira posição onde o padrão corresponde. Se encontrar uma correspondência, retorna um objeto Match; caso contrário, retorna None.
Diferente de re.findall, que retorna todas as ocorrências, re.search interrompe a busca ao encontrar a primeira correspondência — sendo mais eficiente quando o objetivo é apenas verificar se o padrão existe na string.
import retexto ="O IPCA de março foi de 0,83%"resultado = re.search("[0-9]+,[0-9]+", texto)resultado = re.search(r"\d+,\d+", texto)print(resultado) # objeto Matchprint(resultado.group()) # trecho encontrado
O recurso de agrupamento em expressões regulares permite capturar partes específicas do texto que corresponde ao padrão.
Trechos do padrão delimitados por parênteses () são chamados de grupos de captura. Esses parênteses não alteram o que será correspondido, mas organizam o resultado em partes que podem ser acessadas separadamente. É possível usar o método .group() para acessar essas partes:
.group() (ou .group(0)): retorna o trecho completo correspondente ao padrão
.group(n): retorna um grupo específico (o n-ésimo grupo)
.groups(): retorna todos os grupos como tupla
Considere um exemplo simples: imagine que precisamos extrair o dia, o mês e o ano de um trecho de texto. Nesse caso, faz sentido usarmos grupos de captura para isolar cada uma dessas partes, permitindo acessá-las individualmente a partir do resultado da correspondência.
texto ="A reunião do Copom em 18/03/2025 definiu a nova taxa básica de juros."resultado = re.search(r"(\d{2})/(\d{2})/(\d{4})", texto)if resultado:print(f"Data: {resultado.group()}")print(f"Dia: {resultado.group(1)}")print(f"Mês: {resultado.group(2)}")print(f"Ano: {resultado.group(3)}")print(f"Grupos: {resultado.groups()}")
Outra forma de fazer isso é utilizando a sintaxe com (?P<nome>...), que permite criar grupos nomeados.
Grupos nomeados tornam o código mais legível e fácil de entender, pois você pode acessar cada parte pelo nome, em vez de depender da posição numérica.
Vamos ver isso na prática usando o mesmo exemplo anterior.
texto ="A reunião do Copom em 18/03/2025 definiu a nova taxa básica de juros."resultado = re.search(r"(?P<dia>\d{2})/(?P<mes>\d{2})/(?P<ano>\d{4})", texto)if resultado:print(f"Data: {resultado.group()}")print(f"Dia: {resultado.group(1)}")print(f"Mês: {resultado.group(2)}")print(f"Ano: {resultado.group(3)}")print(f"Grupos: {resultado.groups()}")
Uma forma simples e elegante de refinar o código anterior é usar o operador morsa (:=), que permite atribuir e testar um valor ao mesmo tempo:
NotaO operador morsa (:=)
Permite atribuir o resultado de uma expressão a uma variável dentro de uma condição. É especialmente útil com re.search, pois evita repetir a chamada e o nome da variável:
if resultado := re.search(r"...", texto): # atribui e testa em uma linhaprint(resultado.group())
Cem o operador morsa (:=), o código anterior ficaria assim:
texto ="A reunião do Copom em 18/03/2025 definiu a nova taxa básica de juros."if resultado := re.search(r"(?P<dia>\d{2})/(?P<mes>\d{2})/(?P<ano>\d{4})", texto):print(f"Data: {resultado.group()}")print(f"Dia: {resultado.group(1)}")print(f"Mês: {resultado.group(2)}")print(f"Ano: {resultado.group(3)}")print(f"Grupos: {resultado.groups()}")# Também é possível recuperar os grupos nomeados como um dicionário usando groupdict():resultado.groupdict()
A função re.match é semelhante a re.search, mas com uma diferença importante: ela só verifica se o padrão aparece no início da string. Na prática, isso equivale a usar ^ no começo do padrão com re.search.
codigo_ibge ="Código IBGE: 3550308"# código do município de São Paulo# search procura em qualquer posição da stringprint("Usando search:", re.search(r"\d{7}", codigo_ibge).group())# match tenta encontrar o padrão apenas no inícioprint("Usando match:", re.match(r"\d{7}", codigo_ibge))print("Usando match:", re.match(r"Código", codigo_ibge))print("Usando match:", re.match(r"Código", codigo_ibge).group())
A função re.findall percorre toda a string em busca de correspondências e retorna todas as ocorrências encontradas como uma lista. Cada elemento dessa lista representa uma correspondência do padrão.
Como o re.findall não retorna objetos Match, mas sim os resultados já extraídos (strings ou tuplas), não é possível usar os métodos group() e groups(). Isso acontece porque cada elemento retornado já contém diretamente os dados capturados — ou seja, os grupos já vêm “prontos” no resultado, tornando desnecessário o uso de .group() ou .groups().
O formato dos elementos retornados depende do uso de grupos de captura () no padrão:
Sem grupos de captura: retorna uma lista de strings com os trechos completos de correspondência
Com 1 grupo de captura: retorna uma lista de strings contendo apenas o conteúdo desse grupo
Com múltiplos grupos: retorna uma lista de tuplas, onde cada tupla corresponde a uma ocorrência e contém os grupos capturados
DicaDica:
Use grupos de captura quando quiser extrair partes específicas do texto. Isso torna o resultado do re.findall mais flexível e especialmente útil para análise e processamento de dados.
texto ="""Segundo dados divulgados nesta quinta-feira, o IPCA registrou alta de 4,83% em 2025, enquanto o IGP-M ficou em 7,12%. Já o INPC teve variação de 4,61% no ano passado."""# 1) Sem grupos: retorna as correspondências completasprint("Sem grupos:")print(re.findall(r"\d+,\d+%", texto))print()# Com 1 grupo: retorna apenas o conteúdo do grupoprint("Com 1 grupo:")print(re.findall(r"(\d+,\d+%)", texto))print()# Com múltiplos grupos: retorna lista de tuplas (índice, valor)print("Com múltiplos grupos (índice + valor):")padrao =r"\b(IPCA|IGP-M|INPC)\b.{0,50}?(\d{1,3},\d+)%"resultados = re.findall(padrao, texto)print(resultados)print()for indice, valor in resultados:print(f"{indice}: {valor}%")
Sem grupos:
['4,83%', '7,12%', '4,61%']
Com 1 grupo:
['4,83%', '7,12%', '4,61%']
Com múltiplos grupos (índice + valor):
[('IPCA', '4,83'), ('IGP-M', '7,12'), ('INPC', '4,61')]
IPCA: 4,83%
IGP-M: 7,12%
INPC: 4,61%
7.7re.finditer
A função re.finditer é semelhante ao re.findall: ela encontra todas as ocorrências do padrão ao longo da string.
A diferença é que, em vez de retornar uma lista de resultados, ela retorna um iterador de objetosMatch.
Em outras palavras, re.finditer permite percorrer todas as ocorrências e acessar detalhes de cada uma, como:
o texto correspondente
os grupos de captura
a posição inicial e final na string
DicaDica:
finditer() é uma excelente escolha quando você precisa de mais informações sobre cada correspondência. Os objetos Match retornados contêm não apenas o texto encontrado, mas também sua posição na string original.
Além dos métodos .group(), .groups(), os objetos Match também possuem outros métodos úteis, como .start(), .end() e .span().
texto ="""Segundo dados divulgados nesta quinta-feira, o IPCA registrou alta de 4,83% em 2025, enquanto o IGP-M ficou em 7,12%. Já o INPC teve variação de 4,61% no ano passado."""padrao =r"\b(IPCA|IGP-M|INPC)\b.{0,50}?(\d{1,3},\d+)%"for match in re.finditer(padrao, texto):print(match)print(match.group())print(f"Grupos: {match.groups()}")print(f"Índice: {match.group(1)}")print(f"Valor: {match.group(2)}%")print(f"Posição: {match.span()}")print("-"*20)
<re.Match object; span=(48, 76), match='IPCA registrou alta de 4,83%'>
IPCA registrou alta de 4,83%
Grupos: ('IPCA', '4,83')
Índice: IPCA
Valor: 4,83%
Posição: (48, 76)
--------------------
<re.Match object; span=(97, 117), match='IGP-M ficou em 7,12%'>
IGP-M ficou em 7,12%
Grupos: ('IGP-M', '7,12')
Índice: IGP-M
Valor: 7,12%
Posição: (97, 117)
--------------------
<re.Match object; span=(124, 151), match='INPC teve variação de 4,61%'>
INPC teve variação de 4,61%
Grupos: ('INPC', '4,61')
Índice: INPC
Valor: 4,61%
Posição: (124, 151)
--------------------
7.8re.split
A função re.split divide a string sempre que o padrão é encontrado e retorna o resultado como uma lista. Se o argumento opcional maxsplit for diferente de zero, será realizado no máximo esse número de divisões.
Ela é mais poderosa que o .split() nativo do Python, pois aceita padrões complexos, como múltiplos delimitadores, espaços variáveis e combinações de caracteres.
DicaDica:
Use re.split para tratar textos “sujos” ou inconsistentes, em que os separadores não seguem um padrão fixo.
# .split() nativo: delimitador fixotexto ="São Paulo/SP: 05508-010"print("Usando .split():", texto.split(":"))print([p.strip() for p in texto.split(":")])# re.split: delimitador flexível — ponto e vírgula OU vírgula OU barratexto ="Av. Professor Luciano Gualberto, 908; Butantã, São Paulo/SP: 05508-010"print("Usando re.split():", re.split(r"[;,/]", texto))print("Usando re.split():", re.split(r"\s*[;,/]\s*", texto))print([p.strip() for p in re.split(r"[;,/]", texto)])
A função re.sub realiza substituições em uma string. Ela retorna uma nova string onde todas as ocorrências não sobrepostas do padrão são substituídas pelo valor definido em repl.
Se o padrão não for encontrado, a string original é retornada sem alterações.
DicaDica:
re.sub é indispensável para limpeza e normalização de dados, como remover caracteres indesejados, padronizar formatos ou ajustar textos antes de análises.
# Removendo formatação de valores monetários brasileirosdados = ["R$ 1.234,56", "R$9.800,00", "$ 12.345.678,90"]dados_limpos = []for d in dados: sem_simbolo = re.sub(r"[R$\s]", "", d) # remove R$ e espaços sem_milhar = re.sub(r"\.", "", sem_simbolo) # remove ponto de milhar decimal = re.sub(r",", ".", sem_milhar) # substitui vírgula por ponto dados_limpos.append(float(decimal))print(dados)print(dados_limpos)
As flags permitem modificar o comportamento das buscas com expressões regulares, tornando-as mais flexíveis e adaptáveis a diferentes situações. Elas são passadas como um terceiro argumento nas funções do módulo re, por exemplo: re.search(padrão, string, flags=re.I).
As principais flags são:
Flag
Efeito
re.IGNORECASE
Ignora a diferença entre maiúsculas e minúsculas (busca “case-insensitive”)
re.MULTILINE
Faz com que ^ e $ correspondam ao início e fim de cada linha, não só da string inteira
re.DOTALL
Faz com que o ponto . corresponda também à quebra de linha (\n)
DicaDica:
Você pode combinar várias flags usando o operador |, por exemplo: flags=re.IGNORECASE | re.MULTILINE.
texto ="A decisão do Copom de iniciar o ciclo de flexibilização monetária com um corte de 0,25 ponto percentual, levando a taxa selic de 15,00% para 14,75% ao ano.\nA expectativa do mercado é que a Taxa Selic termine o ano em 14%."print(re.findall(r"Selic", texto))print(re.findall(r"Selic", texto, re.IGNORECASE))
['Selic']
['selic', 'Selic']
texto ="""Aluno: Ana; Curso: Economia; Ingresso: 2023\naluno: Bruno; Curso: Administração; Ingresso: 2022"""# Sem MULTILINE: ^ e $ correspondem ao início e fim de toda a stringprint("MULTILINE")print(re.findall(r"\d{4}$", texto))print(re.findall(r"\d{4}$", texto, re.MULTILINE))print()# Sem DOTALL: . não casa com quebras de linhaprint("DOTALL")print(re.search(r".+", texto).group())print(re.search(r".+", texto, re.DOTALL).group())# Com IGNORECASE e MULTILINEprint("IGNORECASE + MULTILINE")print(re.findall(r"^aluno.+", texto))print(re.findall(r"^aluno.+", texto, re.IGNORECASE))print(re.findall(r"^aluno.+", texto, re.IGNORECASE | re.MULTILINE))
Escreva uma função validar_ticker(ticker) que valide o formato de um ticker de ação brasileira na B3. Tickers válidos têm entre 4 e 6 letras maiúsculas seguidas de 1 ou 2 dígitos (ex: "PETR4", "VALE3", "BBDC4", "ABEV3"). A função deve retornar True ou False.
Dado o texto abaixo, use re.findall para extrair todas as taxas percentuais mencionadas e calcule a média:
texto ="""A rentabilidade do CDB foi de 12,5% ao ano, enquanto o Tesouro Selicrendeu 10,50%. Os fundos de renda fixa tiveram retorno médio de 11,2%,e as debêntures incentivadas pagaram 13,8% ao ano. A inflação (IPCA)ficou em 4,83%, resultando em um ganho real de aproximadamente 7,3%."""
A tabela abaixo contém dados de exportações com delimitadores inconsistentes. Use re.split para separar corretamente cada linha e calcule o total exportado:
dados = ["soja;32.450,00|EUA;jan/2024","milho,12.800,50;China,fev/2024","petroleo\t8.920,75|Argentina\tfev/2024",]
Escreva uma função extrair_cnpj(texto) que use re.findall para encontrar todos os CNPJs em um texto no formato XX.XXX.XXX/XXXX-XX. Teste com um parágrafo que contenha múltiplos CNPJs misturados com texto.
Usando re.sub, escreva uma função normalizar_serie(valores) que receba uma lista de strings representando valores de uma série temporal — potencialmente com formatações inconsistentes como "1.234,56", "1234.56", "R$ 1.234,56" e "1,234.56" (formato americano) — e retorne uma lista de float com todos os valores normalizados.