Sammeln Sie Preise aus mehr als einem Land und Sie werden dieselbe Zahl auf ein Dutzend Arten geschrieben sehen. Eine deutsche Seite zeigt 1.234,56 €. Eine französische zeigt 1 234,56 €, mit einem Leerzeichen, das man möglicherweise nicht sehen kann. Eine Schweizer Seite zeigt CHF 1’234.50. Eine indische gruppiert Ziffern als 12,34,567. Und ein Datum wie 03/04/2026 bedeutet in den Vereinigten Staaten den 4. März und fast überall sonst den 3. April.
Liegt man bei einem dieser Fälle falsch, ist der Fehler selten offensichtlich: ein Preis, der um den Faktor tausend danebenliegt, ein Datum, das einen Monat abweicht, eine Währung, die mit einer anderen verwechselt wird, die sich das Symbol teilt. Dieser Leitfaden behandelt die Formate, denen Sie begegnen werden, die Regeln, die sie bestimmen, und Parsing-Code, der sich weigert zu raten, wenn ein Wert nicht passt.
Wichtigste Erkenntnisse
- Parsen Sie jeden Wert mit dem Gebietsschema (Locale) der Seite, von der er stammt. Dieselben Zeichen bedeuten in unterschiedlichen Locales unterschiedliche Zahlen.
- Entfernen Sie nicht einfach Trennzeichen und hoffen Sie das Beste. Entscheiden Sie anhand der Locale, welches Symbol das Dezimaltrennzeichen ist, und weisen Sie Werte zurück, die nicht passen.
- Behandeln Sie Währungssymbole als mehrdeutig. “$10.00” sind kanadische Dollar auf einer kanadischen Seite, Pesos auf einer mexikanischen Seite und australische Dollar auf einer australischen Seite. Speichern Sie ISO-4217-Codes.
- Speichern Sie Geldbeträge in kleinsten Einheiten mit der richtigen Genauigkeit. Der Yen hat keine Dezimalstellen; der bahrainische und der kuwaitische Dinar haben drei.
- Numerische Datumsangaben benötigen eine Locale oder besser eine maschinenlesbare Quelle. Bevorzugen Sie ISO 8601 aus strukturierten Daten, sofern die Seite dies bereitstellt.
Woher die Regeln kommen
Locale-Konventionen sind keine Folklore. Sie werden im Unicode Common Locale Data Repository, CLDR, veröffentlicht, das Betriebssysteme, Browser und die meisten Internationalisierungsbibliotheken verwenden. Die folgenden Beispiele stammen direkt aus CLDR-Daten, über die Python-Bibliothek Babel, bei der Formatierung der Zahl 1234567.891 und des Betrags 1.234,50 Euro:
| Locale | Dezimal | Gruppierung | 1234567.891 | €1,234.50 |
|---|---|---|---|---|
| Englisch, Vereinigte Staaten | . | , | 1,234,567.891 | €1,234.50 |
| Deutsch, Deutschland | , | . | 1.234.567,891 | 1.234,50 € |
| Französisch, Frankreich | , | schmales geschütztes Leerzeichen | 1 234 567,891 | 1 234,50 € |
| Deutsch, Schweiz | . | ’ | 1’234’567.891 | EUR 1’234.50 |
| Englisch, Indien | . | , | 12,34,567.891 | €1,234.50 |
| Portugiesisch, Brasilien | , | . | 1.234.567,891 | € 1.234,50 |
Drei Details bringen einen ins Stolpern. Französisch gruppiert Tausender mit einem schmalen geschützten Leerzeichen, einem Zeichen, das wie ein Leerzeichen aussieht und keines ist. Schweizerdeutsch verwendet ein apostrophähnliches Zeichen. Und indisches Englisch gruppiert die ersten drei Ziffern und dann paarweise, sodass 1,234,567 als 12,34,567 geschrieben wird.
Mit der Locale parsen und zurückweisen, was nicht passt
Der verlockende Ansatz besteht darin, alles außer Ziffern und einem Trennzeichen zu entfernen und dann zu raten, welches Trennzeichen der Dezimalpunkt ist. Das funktioniert, bis man auf “1.234” trifft, was in Deutschland eintausendzweihundertvierunddreißig und in den Vereinigten Staaten eins Komma zwei drei vier bedeutet. Keine noch so clevere Methode kann die richtige Antwort allein aus der Zeichenkette gewinnen. Die Locale kann es.
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)
Getestet an realen Formaten:
| Angezeigt | Locale | Geparst |
|---|---|---|
| $1,234.56 | Englisch, Vereinigte Staaten | 1234.56 |
| 1.234,56 € | Deutsch, Deutschland | 1234.56 |
| 1 234,56 € (mit einem von drei Leerzeichen-Zeichen) | Französisch, Frankreich | 1234.56 |
| CHF 1’234.50 | Deutsch, Schweiz | 1234.50 |
| ₹12,34,567.00 | Englisch, Indien | 1234567.00 |
| R$ 1.234,56 | Portugiesisch, Brasilien | 1234.56 |
| 1.234,56 € | Englisch, Vereinigte Staaten | Zurückgewiesen |
| 9,99 | Englisch, Vereinigte Staaten | Zurückgewiesen |
Die letzten beiden Zeilen sind der springende Punkt. Ein deutsch formatierter Preis auf einer Seite, von der Sie annahmen, sie sei amerikanisch, ist ein Signal, dass etwas nicht stimmt: Es wurde die falsche Locale-Variante ausgeliefert, die Seite hat sich geändert, oder Ihre Konfiguration ist fehlerhaft. Ein Fehler, den man sehen kann, ist besser als ein Preis, der still um den Faktor tausend danebenliegt.
Die Leerzeichenbehandlung hat sich beim Testen als wichtig erwiesen. Unsere erste Version wies einen französischen Preis mit einem gewöhnlichen Leerzeichen zurück, weil das französische Trennzeichen laut CLDR das schmale geschützte Leerzeichen ist. Reale Seiten mischen alle drei Leerzeichenarten, daher ordnet der Code nun jede davon der eigenen Locale zu, bevor geparst wird.
Währung: Verlassen Sie sich nicht allein auf das Symbol
Viele Währungen teilen sich ein Symbol. CLDR formatiert zehn Einheiten der lokalen Währung als “$10.00” sowohl in Kanada, Mexiko als auch in Australien, und zeigt US-Dollar als “US$10.00” auf einer kanadischen Seite. Ein Scraper, der ”$” auf USD abbildet, liegt bei einem großen Teil der Websites weltweit falsch.
Entnehmen Sie die Währung aus etwas Eindeutigem: einem ISO-4217-Code in den strukturierten Daten der Seite, dem Wert eines Währungsauswahlfelds oder der bekannten Standardwährung der Seite und des Markts, aus dem Sie gesammelt haben. Speichern Sie den dreistelligen Code zu jedem Preis, und behandeln Sie einen Preis, der nur ein Symbol und keine weiteren Belege hat, als unvollständig statt zu raten.
Kleinste Einheiten und Genauigkeit
Sobald geparst, speichern Sie Geldbeträge als Ganzzahlen in der kleinsten Einheit der Währung, da Fließkomma-Arithmetik bei Geldbeträgen Rundungsfehler anhäuft. Die Anzahl der Dezimalstellen ist nicht immer zwei:
| Währung | Dezimalstellen | 1.234 angezeigt wird zu |
|---|---|---|
| Euro, US-Dollar | 2 | 123400 |
| Japanischer Yen, koreanischer Won, chilenischer Peso | 0 | 1234 |
| Bahrainischer Dinar, kuwaitischer Dinar | 3 | 1234000 |
Erfassen Sie die Einheit zusammen mit dem Wert. Eine Pipeline, die große und kleine Einheiten vermischt, wie im Fall des Preises, der als 10000 für ein Produkt zu $100 ankam, beschrieben unter Schema-Drift in gescrapten Daten, erzeugt Fehler von genau dem Faktor hundert, die vollkommen plausibel aussehen.
Datumsangaben: maschinenlesbare Quellen bevorzugen
Dasselbe numerische Datum wird je nach Locale unterschiedlich geparst:
| Angezeigt | Locale | Geparst |
|---|---|---|
| 03/04/2026 | Englisch, Vereinigte Staaten | 4. März 2026 |
| 03/04/2026 | Englisch, Vereinigtes Königreich | 3. April 2026 |
| 03/04/2026 | Deutsch, Deutschland | 3. April 2026 |
Vermeiden Sie es wo möglich, angezeigte Datumsangaben überhaupt zu parsen. Viele Seiten veröffentlichen maschinenlesbare Daten in strukturierten Daten oder im datetime-Attribut eines <time>-Elements, meist in ISO 8601, was eindeutig ist; deren Extraktion wird behandelt unter Hören Sie auf, HTML zu parsen. Wenn Sie ein angezeigtes Datum parsen müssen, verwenden Sie explizit die Locale der Seite, speichern Sie das Ergebnis in ISO 8601 mit Zeitzone, und markieren Sie Werte, bei denen sowohl Tag als auch Monat zwölf oder kleiner sind, für Seiten, deren Locale Sie nicht bestätigt haben.
Die Locale von Anfang an richtig bestimmen
Jede der obigen Regeln hängt davon ab, zu wissen, welche Locale eine Seite verwendet hat, und das ist teilweise eine Frage der Datenerfassung. Dieselbe Produktseite kann je nach Land des Besuchers in einer anderen Sprache, Währung und Zahlenformat gerendert werden, weshalb ein Preis, der über einen Exit in Deutschland gesammelt wurde, und einer, der über einen Exit in den Vereinigten Staaten gesammelt wurde, nicht direkt vergleichbar sind, bevor beide normalisiert wurden. Erfassen Sie den Markt, aus dem jeder Wert gesammelt wurde, und halten Sie Sitzungen in Sprache und Region konsistent, wie beschrieben unter Abgleich von Geo, Zeitzone und Locale bei residential Proxys.
Fazit
Zahlen, Preise und Datumsangaben wirken universell und sind es nicht. Dieselben Zeichen bedeuten in unterschiedlichen Locales unterschiedliche Werte, Symbole lassen sich auf viele Währungen abbilden, die Genauigkeit variiert je nach Währung, und numerische Datumsangaben sind von Natur aus mehrdeutig.
Parsen Sie jeden Wert mit der Locale, in der er geschrieben wurde, weisen Sie zurück, was nicht passt, statt zu raten, speichern Sie Währungen als ISO-Code und Geldbeträge in kleinsten Einheiten mit der richtigen Genauigkeit, und bevorzugen Sie maschinenlesbare Datumsangaben, wo immer die Seite sie bereitstellt. Jede dieser Regeln verwandelt einen stillen, plausibel wirkenden Fehler in einen sichtbaren.
Quellen und Referenzen
- Unicode Consortium, Common Locale Data Repository (CLDR).
- Babel, Python-Bibliothek für Internationalisierung, Version 2.18.0, verwendet für die obigen Beispiele.
- ISO-4217-Währungscodes und ISO-8601-Datums- und Zeitformat.