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

  1. Use raw strings: Sempre use r'padrão' para evitar problemas com escape
  2. Compile regex repetidos: Use re.compile() para performance
  3. Seja específico: Evite padrões muito genéricos como .*
  4. Use grupos nomeados: Mais legíveis que group(1)
  5. Teste seus padrões: Use ferramentas como regex101.com
  6. Documente: Use re.VERBOSE para 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!