Recopila precios de más de un país y te encontrarás con el mismo número escrito de una docena de maneras distintas. Un sitio alemán muestra 1.234,56 €. Uno francés muestra 1 234,56 €, con un espacio que quizá no puedas ver. Uno suizo muestra CHF 1’234.50. Uno indio agrupa los dígitos como 12,34,567. Y una fecha como 03/04/2026 significa el 4 de marzo en Estados Unidos y el 3 de abril en casi cualquier otro lugar.
Si te equivocas en alguno de estos casos, el error rara vez es evidente: un precio desviado por un factor de mil, una fecha con un mes de diferencia, una divisa confundida con otra que comparte su símbolo. Esta guía repasa los formatos que te vas a encontrar, las reglas que los determinan y un código de análisis que se niega a adivinar cuando un valor no encaja.
Puntos clave
- Analiza cada valor con la configuración regional de la página de origen. Los mismos caracteres significan números diferentes en configuraciones regionales distintas.
- No te limites a eliminar separadores esperando acertar. Decide qué símbolo es el separador decimal a partir de la configuración regional y rechaza los valores que no encajen.
- Trata los símbolos de divisa como ambiguos. “$10.00” son dólares canadienses en una página canadiense, pesos en una página mexicana y dólares australianos en una página australiana. Guarda los códigos ISO 4217.
- Guarda el dinero en unidades menores con la precisión correcta. El yen no tiene decimales; el dinar bareiní y el dinar kuwaití tienen tres.
- Las fechas numéricas necesitan una configuración regional o, mejor aún, una fuente legible por máquina. Prefiere el formato ISO 8601 a partir de datos estructurados cuando la página lo ofrezca.
De dónde vienen las reglas
Las convenciones de configuración regional no son folclore. Están publicadas en el Unicode Common Locale Data Repository, CLDR, que utilizan los sistemas operativos, los navegadores y la mayoría de las bibliotecas de internacionalización. Los ejemplos siguientes proceden directamente de los datos de CLDR, a través de la biblioteca Python Babel, dando formato al número 1234567.891 y al importe 1.234,50 euros:
| Configuración regional | Decimal | Agrupación | 1234567.891 | €1,234.50 |
|---|---|---|---|---|
| Inglés, Estados Unidos | . | , | 1,234,567.891 | €1,234.50 |
| Alemán, Alemania | , | . | 1.234.567,891 | 1.234,50 € |
| Francés, Francia | , | espacio fino de no separación | 1 234 567,891 | 1 234,50 € |
| Alemán, Suiza | . | ’ | 1’234’567.891 | EUR 1’234.50 |
| Inglés, India | . | , | 12,34,567.891 | €1,234.50 |
| Portugués, Brasil | , | . | 1.234.567,891 | € 1.234,50 |
Hay tres detalles que suelen pillar a la gente. El francés agrupa los millares con un espacio fino de no separación, un carácter que parece un espacio y no lo es. El alemán suizo utiliza un carácter parecido a un apóstrofo. Y el inglés de India agrupa los tres primeros dígitos y después por pares, de modo que 1,234,567 se escribe 12,34,567.
Analiza según la configuración regional y rechaza lo que no encaje
El enfoque tentador es eliminar todo salvo los dígitos y un separador, y después adivinar cuál es el separador decimal. Funciona hasta que aparece “1.234”, que es mil doscientos treinta y cuatro en Alemania y uno con doscientos treinta y cuatro milésimas en Estados Unidos. Ninguna astucia recupera la respuesta correcta a partir de la cadena por sí sola. La configuración regional sí lo hace.
import re
from babel.dates import parse_date
from babel.numbers import NumberFormatError, get_currency_precision, get_group_symbol, parse_decimal
# Keep digits, separators and the space variants sites use between thousands.
NUMBER_CHARS = re.compile("[^0-9.,'\u2019 \u00a0\u2009\u202f-]")
SPACES = re.compile("[ \u00a0\u2009\u202f]")
def parse_amount(text, locale):
"""Parse a displayed amount using the page's locale. Refuses input that does not fit it."""
number = NUMBER_CHARS.sub("", text).strip()
group = get_group_symbol(locale)
if SPACES.fullmatch(group):
# Sites mix ordinary, no-break and narrow no-break spaces; use the locale's own.
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):
"""Store money as an integer in the currency's smallest unit (cents, yen, fils)."""
return int((amount * 10 ** get_currency_precision(currency)).to_integral_value())
def parse_display_date(text, locale):
"""Numeric dates are ambiguous without a locale: 03/04/2026 is March or April."""
return parse_date(text.strip(), locale=locale)
Probado con formatos del mundo real:
| Mostrado | Configuración regional | Analizado |
|---|---|---|
| $1,234.56 | Inglés, Estados Unidos | 1234.56 |
| 1.234,56 € | Alemán, Alemania | 1234.56 |
| 1 234,56 € (con cualquiera de los tres caracteres de espacio) | Francés, Francia | 1234.56 |
| CHF 1’234.50 | Alemán, Suiza | 1234.50 |
| ₹12,34,567.00 | Inglés, India | 1234567.00 |
| R$ 1.234,56 | Portugués, Brasil | 1234.56 |
| 1.234,56 € | Inglés, Estados Unidos | Rechazado |
| 9,99 | Inglés, Estados Unidos | Rechazado |
Las dos últimas filas son la clave. Un precio con formato alemán en una página que creías estadounidense es una señal de que algo falla: se sirvió la configuración regional equivocada, la página cambió o tu configuración es incorrecta. Un error visible es mejor que un precio silenciosamente desviado por un factor de mil.
El manejo de los espacios se ganó su lugar durante las pruebas. Nuestra primera versión rechazaba un precio francés escrito con un espacio ordinario, porque el separador francés de CLDR es el espacio fino de no separación. Los sitios reales mezclan los tres tipos de carácter de espacio, así que el código ahora convierte cualquiera de ellos al propio de la configuración regional antes de analizar.
Divisa: nunca confíes solo en el símbolo
Muchas divisas comparten símbolo. CLDR da formato a diez unidades de la moneda local como “$10.00” tanto en Canadá como en México y Australia, y muestra los dólares estadounidenses como “US$10.00” en una página canadiense. Un scraper que asigne ”$” a USD se equivocará en una gran parte de los sitios del mundo.
Obtén la divisa de algo inequívoco: un código ISO 4217 en los datos estructurados de la página, el valor de un selector de divisa o la divisa por defecto conocida del sitio y mercado del que recopilaste. Guarda el código de tres letras junto con cada precio, y trata un precio que solo tenga símbolo, sin ninguna otra evidencia, como incompleto en lugar de adivinar.
Unidades menores y precisión
Una vez analizado, guarda el dinero como números enteros en la unidad más pequeña de la divisa, porque la aritmética en coma flotante sobre dinero acumula errores de redondeo. El número de decimales no siempre es dos:
| Divisa | Decimales | 1,234 mostrado se convierte en |
|---|---|---|
| Euro, dólar estadounidense | 2 | 123400 |
| Yen japonés, won surcoreano, peso chileno | 0 | 1234 |
| Dinar bareiní, dinar kuwaití | 3 | 1234000 |
Registra la unidad junto con el valor. Un flujo de datos que mezcla unidades mayores y menores, como el precio que llegó como 10000 para un producto de $100 descrito en la deriva de esquema en datos scrapeados, produce errores de exactamente un factor de cien que parecen totalmente plausibles.
Fechas: prefiere fuentes legibles por máquina
La misma fecha numérica se analiza de forma distinta según la configuración regional:
| Mostrado | Configuración regional | Analizado |
|---|---|---|
| 03/04/2026 | Inglés, Estados Unidos | 4 de marzo de 2026 |
| 03/04/2026 | Inglés, Reino Unido | 3 de abril de 2026 |
| 03/04/2026 | Alemán, Alemania | 3 de abril de 2026 |
Cuando puedas, evita analizar fechas mostradas por completo. Muchas páginas publican fechas legibles por máquina en datos estructurados o en el atributo datetime de un elemento <time>, normalmente en ISO 8601, que no es ambiguo; extraerlas se trata en deja de analizar HTML. Cuando debas analizar una fecha mostrada, usa explícitamente la configuración regional de la página, guarda el resultado en ISO 8601 con una zona horaria y marca los valores en los que tanto el día como el mes sean doce o menos en sitios cuya configuración regional no hayas confirmado.
Acierta con la configuración regional desde el principio
Cada regla anterior depende de saber qué configuración regional utilizó una página, y eso es en parte una decisión de recopilación. La misma página de producto puede renderizarse en un idioma, una divisa y un formato numérico distintos según el país del visitante, razón por la cual un precio recopilado a través de una salida en Alemania y uno recopilado a través de una salida en Estados Unidos no son directamente comparables hasta que ambos se normalizan. Registra el mercado del que se recopiló cada valor y mantén las sesiones coherentes en idioma y región, tal como se explica en cómo hacer coincidir la geolocalización, la zona horaria y la configuración regional del proxy residencial.
En resumen
Los números, los precios y las fechas parecen universales y no lo son. Los mismos caracteres significan valores distintos en configuraciones regionales distintas, los símbolos se corresponden con muchas divisas, la precisión varía según la divisa y las fechas numéricas son ambiguas por diseño.
Analiza cada valor con la configuración regional en la que se escribió, rechaza lo que no encaje en lugar de adivinar, guarda la divisa como un código ISO y el dinero en unidades menores con la precisión correcta, y prefiere las fechas legibles por máquina siempre que la página las proporcione. Cada una de estas reglas convierte un error silencioso y plausible en uno visible.
Fuentes y referencias
- Unicode Consortium, Common Locale Data Repository (CLDR).
- Babel, biblioteca de internacionalización de Python, versión 2.18.0, utilizada en los ejemplos anteriores.
- Códigos de divisa ISO 4217 y formato de fecha y hora ISO 8601.