Conhecimento

Normalizando preços, números e datas entre localidades

1.234,56 € e $1,234.56 são ambos preços, e 03/04/2026 são duas datas diferentes. Como analisar corretamente valores extraídos por localidade, com código testado.

James Meadow

James Meadow

30 de setembro de 2026 · 9 min de leitura

Colete preços de mais de um país e você encontrará o mesmo número escrito de uma dúzia de formas diferentes. Um site alemão mostra 1.234,56 €. Um site francês mostra 1 234,56 €, com um espaço que você talvez nem consiga ver. Um site suíço mostra CHF 1’234.50. Um site indiano agrupa os dígitos como 12,34,567. E uma data como 03/04/2026 significa 4 de março nos Estados Unidos e 3 de abril em quase todo o resto do mundo.

Errar qualquer um desses casos raramente é óbvio: um preço errado por um fator de mil, uma data um mês fora do lugar, uma moeda confundida com outra que compartilha o mesmo símbolo. Este guia cobre os formatos que você vai encontrar, as regras que os definem e um código de parsing que se recusa a adivinhar quando um valor não se encaixa.

Principais conclusões

  • Faça o parsing de cada valor com o locale da página de onde ele veio. Os mesmos caracteres significam números diferentes em locales diferentes.
  • Não remova separadores torcendo para dar certo. Decida qual símbolo é o separador decimal a partir do locale, e rejeite valores que não se encaixam.
  • Trate símbolos de moeda como ambíguos. “$10.00” é dólar canadense em uma página canadense, peso em uma página mexicana e dólar australiano em uma página australiana. Armazene os códigos ISO 4217.
  • Armazene valores monetários em unidades menores com a precisão correta. O iene não tem casas decimais; os dinares do Bahrein e do Kuwait têm três.
  • Datas numéricas precisam de um locale, ou melhor ainda, de uma fonte legível por máquina. Prefira o ISO 8601 vindo de dados estruturados quando a página o fornecer.

De onde vêm as regras

As convenções de locale não são folclore. Elas são publicadas no Unicode Common Locale Data Repository, CLDR, que sistemas operacionais, navegadores e a maioria das bibliotecas de internacionalização usam. Os exemplos abaixo vêm diretamente dos dados do CLDR, via a biblioteca Python Babel, formatando o número 1234567.891 e o valor de 1.234,50 euros:

LocaleDecimalAgrupamento1234567.891€1,234.50
Inglês, Estados Unidos.,1,234,567.891€1,234.50
Alemão, Alemanha,.1.234.567,8911.234,50 €
Francês, França,espaço estreito sem quebra1 234 567,8911 234,50 €
Alemão, Suíça.’1’234’567.891EUR 1’234.50
Inglês, Índia.,12,34,567.891€1,234.50
Português, Brasil,.1.234.567,891€ 1.234,50

Três detalhes costumam pegar as pessoas de surpresa. O francês agrupa os milhares com um espaço estreito sem quebra, um caractere que parece um espaço e não é. O alemão suíço usa um caractere parecido com um apóstrofo. E o inglês indiano agrupa os três primeiros dígitos e depois pares, então 1.234.567 é escrito como 12,34,567.

Faça o parsing com o locale, e rejeite o que não se encaixa

A abordagem tentadora é remover tudo, exceto os dígitos e um separador, e depois tentar adivinhar qual separador é o ponto decimal. Isso funciona até encontrar “1.234”, que é mil duzentos e trinta e quatro na Alemanha e um vírgula dois três quatro nos Estados Unidos. Nenhuma quantidade de esperteza recupera a resposta certa a partir da string sozinha. O locale sim.

import re

from babel.dates import parse_date
from babel.numbers import NumberFormatError, get_currency_precision, get_group_symbol, parse_decimal

# Mantém dígitos, separadores e as variantes de espaço que os sites usam entre milhares.
NUMBER_CHARS = re.compile("[^0-9.,'\u2019 \u00a0\u2009\u202f-]")
SPACES = re.compile("[ \u00a0\u2009\u202f]")


def parse_amount(text, locale):
    """Faz o parsing de um valor exibido usando o locale da página. Rejeita entradas que não se encaixam nele."""
    number = NUMBER_CHARS.sub("", text).strip()
    group = get_group_symbol(locale)
    if SPACES.fullmatch(group):
        # Os sites misturam espaços comuns, sem quebra e estreitos sem quebra; usa o próprio do locale.
        number = SPACES.sub(group, number)
    try:
        return parse_decimal(number, locale=locale, strict=True)
    except NumberFormatError as e:
        raise ValueError(f"{text!r} does not parse as a {locale} number: {e}") from None


def to_minor_units(amount, currency):
    """Armazena o valor monetário como um inteiro na menor unidade da moeda (centavos, iene, fils)."""
    return int((amount * 10 ** get_currency_precision(currency)).to_integral_value())


def parse_display_date(text, locale):
    """Datas numéricas são ambíguas sem um locale: 03/04/2026 é março ou abril."""
    return parse_date(text.strip(), locale=locale)

Testado em formatos do mundo real:

ExibidoLocaleResultado
$1,234.56Inglês, Estados Unidos1234.56
1.234,56 €Alemão, Alemanha1234.56
1 234,56 € (qualquer um dos três caracteres de espaço)Francês, França1234.56
CHF 1’234.50Alemão, Suíça1234.50
₹12,34,567.00Inglês, Índia1234567.00
R$ 1.234,56Português, Brasil1234.56
1.234,56 €Inglês, Estados UnidosRejeitado
9,99Inglês, Estados UnidosRejeitado

As duas últimas linhas são o ponto central. Um preço formatado no padrão alemão em uma página que você acreditava ser americana é um sinal de que algo está errado: a variante errada de locale foi servida, a página mudou, ou sua configuração está incorreta. Um erro que você consegue ver é melhor do que um preço silenciosamente errado por um fator de mil.

O tratamento de espaços conquistou seu lugar durante os testes. Nossa primeira versão rejeitava um preço em francês escrito com um espaço comum, porque o separador francês do CLDR é o espaço estreito sem quebra. Sites reais misturam os três tipos de caractere de espaço, então o código agora mapeia qualquer um deles para o do próprio locale antes de fazer o parsing.

Moeda: nunca confie apenas no símbolo

Muitas moedas compartilham um símbolo. O CLDR formata dez unidades da moeda local como “$10.00” no Canadá, no México e na Austrália igualmente, e mostra dólares americanos como “US$10.00” em uma página canadense. Um scraper que mapeia ”$” para USD estará errado em uma grande parte dos sites do mundo.

Extraia a moeda de algo inequívoco: um código ISO 4217 nos dados estruturados da página, o valor de um seletor de moeda, ou a moeda padrão conhecida do site e do mercado de onde você coletou. Armazene o código de três letras junto com cada preço, e trate um preço que tenha apenas o símbolo, sem outra evidência, como incompleto em vez de adivinhar.

Unidades menores e precisão

Depois de feito o parsing, armazene valores monetários como inteiros na menor unidade da moeda, porque a aritmética de ponto flutuante em dinheiro acumula erros de arredondamento. O número de casas decimais nem sempre é dois:

MoedaCasas decimais1.234 exibido se torna
Euro, dólar americano2123400
Iene japonês, won sul-coreano, peso chileno01234
Dinar do Bahrein, dinar do Kuwait31234000

Registre a unidade junto com o valor. Um pipeline que mistura unidades maiores e menores, como no preço que chegou como 10000 para um produto de $100 descrito em desvio de esquema em dados coletados por scraping, produz erros de exatamente um fator de cem que parecem inteiramente plausíveis.

Datas: prefira fontes legíveis por máquina

A mesma data numérica é interpretada de forma diferente conforme o locale:

ExibidoLocaleResultado
03/04/2026Inglês, Estados Unidos4 de março de 2026
03/04/2026Inglês, Reino Unido3 de abril de 2026
03/04/2026Alemão, Alemanha3 de abril de 2026

Quando possível, evite fazer o parsing de datas exibidas. Muitas páginas publicam datas legíveis por máquina em dados estruturados ou no atributo datetime de um elemento <time>, geralmente em ISO 8601, que é inequívoco; extraí-las é abordado em pare de fazer parsing de HTML. Quando for necessário fazer o parsing de uma data exibida, use o locale da página explicitamente, armazene o resultado em ISO 8601 com fuso horário, e sinalize valores em que tanto o dia quanto o mês sejam doze ou menos, para sites cujo locale você não confirmou.

Acerte o locale desde o início

Cada regra acima depende de saber qual locale uma página usou, e isso é em parte uma decisão de coleta. A mesma página de produto pode ser renderizada em um idioma, moeda e formato numérico diferentes dependendo do país do visitante, e é por isso que um preço coletado por meio de uma saída na Alemanha e um coletado por meio de uma saída nos Estados Unidos não são diretamente comparáveis até que ambos sejam normalizados. Registre o mercado de onde cada valor foi coletado, e mantenha as sessões consistentes em idioma e região, conforme abordado em combinando geolocalização, fuso horário e locale de proxy residencial.

Conclusão

Números, preços e datas parecem universais e não são. Os mesmos caracteres significam valores diferentes em locales diferentes, símbolos correspondem a várias moedas, a precisão varia conforme a moeda, e datas numéricas são ambíguas por design.

Faça o parsing de cada valor com o locale em que foi escrito, rejeite o que não se encaixa em vez de adivinhar, armazene a moeda como um código ISO e o valor monetário em unidades menores com a precisão correta, e prefira datas legíveis por máquina sempre que a página as fornecer. Cada uma dessas regras transforma um erro silencioso e plausível em um erro visível.

Fontes e referências

Pronto para começar?

Experimente os proxies residenciais da Shifter, mais de 205M IPs, mais de 195 países, a partir de $ 0,75/GB.

Começar