Expressões Regulares (Regex) são padrões poderosos para buscar, validar e manipular texto. No Universo Python, regex é como um "scanner avançado" que encontra exatamente o que você procura em qualquer string. Neste guia completo, você dominará regex do básico ao avançado com exemplos práticos do mundo real.
🎯 O Que São Expressões Regulares?
Expressões regulares são sequências de caracteres que definem um padrão de busca. Elas permitem:
- ✅ Validar formatos (email, CPF, telefone)
- ✅ Buscar padrões em textos
- ✅ Extrair informações específicas
- ✅ Substituir texto de forma inteligente
- ✅ Dividir strings por padrões complexos
import re
# Exemplo simples: encontrar todas as palavras com 'python'
texto = "Python é incrível! Aprenda python hoje. PYTHON rocks!"
matches = re.findall(r'python', texto, re.IGNORECASE)
print(matches) # ['Python', 'python', 'PYTHON']
📚 O Módulo re
Python usa o módulo re para trabalhar com regex:
import re
# Principais funções
re.search() # Encontra primeira ocorrência
re.match() # Verifica início da string
re.findall() # Encontra todas as ocorrências
re.finditer() # Iterador de matches
re.sub() # Substitui padrões
re.split() # Divide por padrão
re.compile() # Compila regex para reutilização
🔤 Sintaxe Básica
Caracteres Literais
import re
texto = "O rato roeu a roupa do rei de Roma"
# Busca literal
print(re.findall(r'ro', texto)) # ['ro', 'ro', 'ro']
print(re.findall(r'rei', texto)) # ['rei']
Metacaracteres
| Caractere | Significado | Exemplo |
|---|---|---|
. |
Qualquer caractere (exceto \n) | c.t → cat, cot, cut |
^ |
Início da string | ^Python |
$ |
Fim da string | fim$ |
* |
Zero ou mais repetições | ab*c → ac, abc, abbc |
+ |
Uma ou mais repetições | ab+c → abc, abbc |
? |
Zero ou uma ocorrência | colou?r → color, colour |
{n} |
Exatamente n repetições | a{3} → aaa |
{n,m} |
Entre n e m repetições | a{2,4} → aa, aaa, aaaa |
[] |
Conjunto de caracteres | [aeiou] → vogais |
| |
Alternativa (ou) | gato|cachorro |
() |
Grupo de captura | (ab)+ → ab, abab |
\ |
Escape de metacaractere | \. → ponto literal |
Classes de Caracteres
| Classe | Equivalente | Descrição |
|---|---|---|
\d |
[0-9] |
Dígito |
\D |
[^0-9] |
Não-dígito |
\w |
[a-zA-Z0-9_] |
Alfanumérico |
\W |
[^a-zA-Z0-9_] |
Não-alfanumérico |
\s |
[ \t\n\r\f\v] |
Espaço em branco |
\S |
[^ \t\n\r\f\v] |
Não-espaço |
\b |
- | Limite de palavra |
import re
texto = "Meu telefone é 11-99999-8888 e nasci em 15/03/1990"
# Encontrar números
print(re.findall(r'\d+', texto)) # ['11', '99999', '8888', '15', '03', '1990']
# Encontrar palavras
print(re.findall(r'\w+', texto)) # ['Meu', 'telefone', 'é', '11', ...]
# Encontrar não-dígitos
print(re.findall(r'\D+', texto)) # ['Meu telefone é ', '-', '-', ...]
🔍 Funções Principais
re.search() - Primeira Ocorrência
import re
texto = "Python foi criado em 1991 por Guido van Rossum"
# Buscar padrão
resultado = re.search(r'\d{4}', texto)
if resultado:
print(f"Encontrado: {resultado.group()}") # 1991
print(f"Posição: {resultado.start()}-{resultado.end()}") # 21-25
print(f"Span: {resultado.span()}") # (21, 25)
else:
print("Não encontrado")
re.match() - Início da String
import re
# match() só encontra no INÍCIO da string
texto1 = "Python é legal"
texto2 = "Eu amo Python"
print(re.match(r'Python', texto1)) #
print(re.match(r'Python', texto2)) # None (não está no início)
re.findall() - Todas as Ocorrências
import re
texto = "Ana tem 25 anos, Bruno tem 30 e Carlos tem 28"
# Encontrar todas as idades
idades = re.findall(r'\d+', texto)
print(idades) # ['25', '30', '28']
# Encontrar todos os nomes (palavras que começam com maiúscula)
nomes = re.findall(r'[A-Z][a-z]+', texto)
print(nomes) # ['Ana', 'Bruno', 'Carlos']
re.finditer() - Iterador de Matches
import re
texto = "Email: [email protected], [email protected], [email protected]"
padrao = r'[\w.-]+@[\w.-]+'
for match in re.finditer(padrao, texto):
print(f"Email: {match.group()} na posição {match.span()}")
re.sub() - Substituição
import re
# Substituição simples
texto = "O preço é R$ 100,00"
novo = re.sub(r'\d+', 'XXX', texto)
print(novo) # O preço é R$ XXX,XXX
# Censurar palavras
texto = "Isso é muito ruim e péssimo!"
censurado = re.sub(r'ruim|péssimo', '***', texto, flags=re.IGNORECASE)
print(censurado) # Isso é muito *** e ***!
# Substituição com função
def dobrar(match):
numero = int(match.group())
return str(numero * 2)
texto = "Tenho 5 maçãs e 3 bananas"
resultado = re.sub(r'\d+', dobrar, texto)
print(resultado) # Tenho 10 maçãs e 6 bananas
re.split() - Dividir por Padrão
import re
# Split simples
texto = "Python,Java;JavaScript|C++"
linguagens = re.split(r'[,;|]', texto)
print(linguagens) # ['Python', 'Java', 'JavaScript', 'C++']
# Split com limite
texto = "um-dois-três-quatro-cinco"
partes = re.split(r'-', texto, maxsplit=2)
print(partes) # ['um', 'dois', 'três-quatro-cinco']
# Split mantendo delimitadores
texto = "10+20-30*40"
resultado = re.split(r'([+\-*])', texto)
print(resultado) # ['10', '+', '20', '-', '30', '*', '40']
📦 Grupos de Captura
import re
# Grupos básicos
texto = "Meu email é [email protected]"
padrao = r'(\w+)\.(\w+)@(\w+)\.(\w+)'
match = re.search(padrao, texto)
if match:
print(f"Match completo: {match.group(0)}") # [email protected]
print(f"Grupo 1: {match.group(1)}") # ana
print(f"Grupo 2: {match.group(2)}") # silva
print(f"Grupo 3: {match.group(3)}") # email
print(f"Grupo 4: {match.group(4)}") # com
print(f"Todos: {match.groups()}") # ('ana', 'silva', 'email', 'com')
# Grupos nomeados
padrao = r'(?P\w+)@(?P[\w.]+)'
texto = "Contato: [email protected]"
match = re.search(padrao, texto)
if match:
print(f"Usuário: {match.group('usuario')}") # admin
print(f"Domínio: {match.group('dominio')}") # universopython.com
🚩 Flags (Modificadores)
import re
texto = """Python é incrível
PYTHON é poderoso
python é versátil"""
# re.IGNORECASE (ou re.I) - ignora maiúsculas/minúsculas
matches = re.findall(r'python', texto, re.IGNORECASE)
print(matches) # ['Python', 'PYTHON', 'python']
# re.MULTILINE (ou re.M) - ^ e $ funcionam em cada linha
matches = re.findall(r'^python', texto, re.IGNORECASE | re.MULTILINE)
print(matches) # ['Python', 'PYTHON', 'python']
# re.DOTALL (ou re.S) - . também casa com \n
texto = "Início\nMeio\nFim"
match = re.search(r'Início.*Fim', texto, re.DOTALL)
print(match.group()) # Início\nMeio\nFim
# re.VERBOSE (ou re.X) - permite comentários no regex
padrao = re.compile(r'''
\d{3} # DDD
[-.\s]? # Separador opcional
\d{4,5} # Primeira parte
[-.\s]? # Separador opcional
\d{4} # Segunda parte
''', re.VERBOSE)
print(padrao.findall("Tel: 11-99999-8888")) # ['11-99999-8888']
⚡ re.compile() - Regex Compilado
Para regex usados múltiplas vezes, compile para melhor performance:
import re
# Compilar regex
padrao_email = re.compile(
r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
)
# Reutilizar
emails = [
"[email protected]",
"invalido@",
"[email protected]",
"[email protected]"
]
for email in emails:
if padrao_email.match(email):
print(f"✅ {email}")
else:
print(f"❌ {email}")
🎯 Projeto Prático: Validador Completo
Vamos criar um sistema de validação usando regex, aplicando conceitos de POO e tratamento de erros:
import re
from typing import Optional, List, Dict
from dataclasses import dataclass
@dataclass
class ResultadoValidacao:
"""Resultado de uma validação"""
valido: bool
valor_original: str
valor_formatado: Optional[str] = None
mensagem: str = ""
grupos: Dict[str, str] = None
class ValidadorBrasileiro:
"""Validador de dados brasileiros com regex"""
# Padrões compilados
PADRAO_CPF = re.compile(r'^(\d{3})\.?(\d{3})\.?(\d{3})-?(\d{2})$')
PADRAO_CNPJ = re.compile(r'^(\d{2})\.?(\d{3})\.?(\d{3})/?(\d{4})-?(\d{2})$')
PADRAO_TELEFONE = re.compile(r'^\(?(\d{2})\)?[\s.-]?(\d{4,5})[\s.-]?(\d{4})$')
PADRAO_CEP = re.compile(r'^(\d{5})-?(\d{3})$')
PADRAO_EMAIL = re.compile(
r'^(?P[a-zA-Z0-9._%+-]+)@(?P[a-zA-Z0-9.-]+)\.(?P[a-zA-Z]{2,})$'
)
PADRAO_DATA = re.compile(r'^(\d{2})[/.-](\d{2})[/.-](\d{4})$')
PADRAO_HORA = re.compile(r'^(\d{2}):(\d{2})(?::(\d{2}))?$')
PADRAO_PLACA = re.compile(r'^([A-Z]{3})-?(\d)([A-Z\d])(\d{2})$', re.IGNORECASE)
@classmethod
def validar_cpf(cls, cpf: str) -> ResultadoValidacao:
"""Valida CPF brasileiro"""
match = cls.PADRAO_CPF.match(cpf.strip())
if not match:
return ResultadoValidacao(False, cpf, mensagem="Formato inválido")
# Juntar dígitos
digitos = ''.join(match.groups())
# Verificar CPFs inválidos conhecidos
if digitos == digitos[0] * 11:
return ResultadoValidacao(False, cpf, mensagem="CPF inválido (dígitos repetidos)")
# Validar dígitos verificadores
def calcular_digito(parcial: str, pesos: List[int]) -> int:
soma = sum(int(d) * p for d, p in zip(parcial, pesos))
resto = soma % 11
return 0 if resto < 2 else 11 - resto
d1 = calcular_digito(digitos[:9], range(10, 1, -1))
d2 = calcular_digito(digitos[:10], range(11, 1, -1))
if digitos[9:] != f"{d1}{d2}":
return ResultadoValidacao(False, cpf, mensagem="Dígitos verificadores inválidos")
formatado = f"{digitos[:3]}.{digitos[3:6]}.{digitos[6:9]}-{digitos[9:]}"
return ResultadoValidacao(True, cpf, formatado, "CPF válido")
@classmethod
def validar_email(cls, email: str) -> ResultadoValidacao:
"""Valida endereço de email"""
match = cls.PADRAO_EMAIL.match(email.strip().lower())
if not match:
return ResultadoValidacao(False, email, mensagem="Formato de email inválido")
grupos = match.groupdict()
return ResultadoValidacao(
True, email, email.lower(),
f"Email válido (domínio: {grupos['dominio']}.{grupos['tld']})",
grupos
)
@classmethod
def validar_telefone(cls, telefone: str) -> ResultadoValidacao:
"""Valida telefone brasileiro"""
# Remover caracteres não numéricos para verificar
limpo = re.sub(r'\D', '', telefone)
match = cls.PADRAO_TELEFONE.match(telefone.strip())
if not match or len(limpo) not in [10, 11]:
return ResultadoValidacao(False, telefone, mensagem="Formato de telefone inválido")
ddd, parte1, parte2 = match.groups()
# Celular deve começar com 9
if len(parte1) == 5 and not parte1.startswith('9'):
return ResultadoValidacao(False, telefone, mensagem="Celular deve começar com 9")
formatado = f"({ddd}) {parte1}-{parte2}"
return ResultadoValidacao(True, telefone, formatado, "Telefone válido")
@classmethod
def validar_cep(cls, cep: str) -> ResultadoValidacao:
"""Valida CEP brasileiro"""
match = cls.PADRAO_CEP.match(cep.strip())
if not match:
return ResultadoValidacao(False, cep, mensagem="Formato de CEP inválido")
parte1, parte2 = match.groups()
formatado = f"{parte1}-{parte2}"
return ResultadoValidacao(True, cep, formatado, "CEP válido")
@classmethod
def validar_data(cls, data: str) -> ResultadoValidacao:
"""Valida data no formato DD/MM/AAAA"""
match = cls.PADRAO_DATA.match(data.strip())
if not match:
return ResultadoValidacao(False, data, mensagem="Formato de data inválido")
dia, mes, ano = map(int, match.groups())
# Validar valores
if not (1 <= mes <= 12):
return ResultadoValidacao(False, data, mensagem="Mês inválido")
dias_mes = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
# Ano bissexto
if ano % 4 == 0 and (ano % 100 != 0 or ano % 400 == 0):
dias_mes[1] = 29
if not (1 <= dia <= dias_mes[mes - 1]):
return ResultadoValidacao(False, data, mensagem="Dia inválido para este mês")
formatado = f"{dia:02d}/{mes:02d}/{ano}"
return ResultadoValidacao(True, data, formatado, "Data válida")
@classmethod
def validar_placa(cls, placa: str) -> ResultadoValidacao:
"""Valida placa de veículo (padrão antigo e Mercosul)"""
match = cls.PADRAO_PLACA.match(placa.strip().upper())
if not match:
return ResultadoValidacao(False, placa, mensagem="Formato de placa inválido")
letras, d1, meio, d23 = match.groups()
formatado = f"{letras}-{d1}{meio}{d23}".upper()
tipo = "Mercosul" if meio.isalpha() else "Padrão antigo"
return ResultadoValidacao(True, placa, formatado, f"Placa válida ({tipo})")
class ExtratorTexto:
"""Extrai informações de texto usando regex"""
@staticmethod
def extrair_emails(texto: str) -> List[str]:
"""Extrai todos os emails de um texto"""
padrao = r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}'
return re.findall(padrao, texto)
@staticmethod
def extrair_urls(texto: str) -> List[str]:
"""Extrai URLs de um texto"""
padrao = r'https?://[^\s<>"{}|\\^`\[\]]+'
return re.findall(padrao, texto)
@staticmethod
def extrair_telefones(texto: str) -> List[str]:
"""Extrai telefones de um texto"""
padrao = r'\(?\d{2}\)?[\s.-]?\d{4,5}[\s.-]?\d{4}'
return re.findall(padrao, texto)
@staticmethod
def extrair_hashtags(texto: str) -> List[str]:
"""Extrai hashtags de um texto"""
padrao = r'#\w+'
return re.findall(padrao, texto)
@staticmethod
def extrair_mencoes(texto: str) -> List[str]:
"""Extrai menções (@usuario) de um texto"""
padrao = r'@\w+'
return re.findall(padrao, texto)
@staticmethod
def extrair_valores_monetarios(texto: str) -> List[str]:
"""Extrai valores em reais"""
padrao = r'R\$\s*[\d.,]+'
return re.findall(padrao, texto)
# === DEMONSTRAÇÃO ===
print("=" * 60)
print("🔍 VALIDADOR BRASILEIRO")
print("=" * 60)
# Testar CPF
cpfs = ["529.982.247-25", "111.111.111-11", "12345678900"]
print("\n📋 Validação de CPF:")
for cpf in cpfs:
resultado = ValidadorBrasileiro.validar_cpf(cpf)
status = "✅" if resultado.valido else "❌"
print(f" {status} {cpf} → {resultado.mensagem}")
if resultado.valido:
print(f" Formatado: {resultado.valor_formatado}")
# Testar Email
emails = ["[email protected]", "invalido@", "[email protected]"]
print("\n📧 Validação de Email:")
for email in emails:
resultado = ValidadorBrasileiro.validar_email(email)
status = "✅" if resultado.valido else "❌"
print(f" {status} {email} → {resultado.mensagem}")
# Testar Telefone
telefones = ["11999998888", "(21) 3333-4444", "1199999888"]
print("\n📞 Validação de Telefone:")
for tel in telefones:
resultado = ValidadorBrasileiro.validar_telefone(tel)
status = "✅" if resultado.valido else "❌"
print(f" {status} {tel} → {resultado.mensagem}")
# Extrator
print("\n" + "=" * 60)
print("📄 EXTRATOR DE TEXTO")
print("=" * 60)
texto_exemplo = """
Entre em contato:
- Email: [email protected] ou [email protected]
- Tel: (11) 99999-8888 ou 21-3333-4444
- Site: https://universopython.com
Siga-nos: @universopython #Python #Programação
Valor do curso: R$ 297,00 ou R$ 497,00
"""
print(f"\n📧 Emails: {ExtratorTexto.extrair_emails(texto_exemplo)}")
print(f"🔗 URLs: {ExtratorTexto.extrair_urls(texto_exemplo)}")
print(f"📞 Telefones: {ExtratorTexto.extrair_telefones(texto_exemplo)}")
print(f"# Hashtags: {ExtratorTexto.extrair_hashtags(texto_exemplo)}")
print(f"@ Menções: {ExtratorTexto.extrair_mencoes(texto_exemplo)}")
print(f"💰 Valores: {ExtratorTexto.extrair_valores_monetarios(texto_exemplo)}")
💡 Dicas e Boas Práticas
- Use raw strings: Sempre use
r'padrão'para evitar problemas com escape - Compile regex repetidos: Use
re.compile()para performance - Seja específico: Evite padrões muito genéricos como
.* - Use grupos nomeados: Mais legíveis que
group(1) - Teste seus padrões: Use ferramentas como regex101.com
- Documente: Use
re.VERBOSEpara regex complexos
# ✅ BOM
padrao = re.compile(r'\d{3}\.\d{3}\.\d{3}-\d{2}') # CPF formatado
# ❌ RUIM - muito genérico
padrao = re.compile(r'.*')
# ✅ BOM - com comentários
padrao = re.compile(r'''
^ # Início
[\w.+-]+ # Usuário
@ # @
[\w.-]+ # Domínio
\. # Ponto
[a-zA-Z]{2,} # TLD
$ # Fim
''', re.VERBOSE)
🚀 Próximos Passos
- Strings - Base para trabalhar com texto
- Arquivos - Processar arquivos com regex
- POO - Criar classes validadoras
- Try/Except - Tratar erros de validação
- Módulos - O módulo re em detalhes
- Web Scraping - Extrair dados de páginas
- Processamento de Logs - Analisar arquivos de log
Quer dominar texto e dados? Confira nosso curso completo de Python com projetos de validação e processamento!
📝 Resumo
- ✅ O que são expressões regulares
- ✅ Módulo re e funções principais
- ✅ Metacaracteres e classes de caracteres
- ✅ search, match, findall, finditer
- ✅ sub (substituição) e split
- ✅ Grupos de captura (simples e nomeados)
- ✅ Flags: IGNORECASE, MULTILINE, VERBOSE
- ✅ re.compile() para performance
- ✅ Projeto: Validador brasileiro completo
- ✅ Extrator de emails, URLs, hashtags
- ✅ Boas práticas
Regex é uma ferramenta essencial para qualquer desenvolvedor. Com prática, você conseguirá criar padrões para validar e extrair qualquer tipo de informação de texto!