ERROR_WRONG_FILE_EXTENSION, obwohl sich dieselbe PNG-Datei im Bildbetrachter tadellos öffnet: Die Ursache liegt dann fast immer im Kodierungsschritt davor, nicht im Bild selbst. Drei Ursachen decken den Großteil der Fälle ab – ein mitgesendetes data:-Präfix, ein bereits kodierter String, der ein zweites Mal durch Base64 läuft, und eine Binärdatei, die im Textmodus eingelesen wurde. Dieser Leitfaden zeigt, wie Sie Bild-CAPTCHAs korrekt kodieren, vor dem Absenden validieren und jeden Fehlercode der API seiner tatsächlichen Ursache zuordnen.
Anforderungen auf einen Blick
method=base64erwartet rohes Base64 – ohnedata:image/png;base64,-Präfix und ohne eingestreute Zeilenumbrüche.- Die dekodierte Bilddatei bleibt unter 600 KB. Base64 selbst vergrößert die Nutzdaten um rund 33 %; maßgeblich ist die Größe des Bildes, nicht die Länge des Strings.
- Übermittelt wird an
https://ocr.captchaai.com/in.php, das Ergebnis holen Sie anschließend überres.phpab. Mitjson=1antwortet die API strukturiert statt als Klartext. - Als Bildformate werden PNG, JPEG, GIF und WEBP angenommen, SVG nicht – Vektorgrafiken müssen vorher gerastert werden.
- Der API-Schlüssel gehört in eine Umgebungsvariable (
CAPTCHAAI_API_KEY), nie in den Quelltext oder ins Repository.
Bildformat wählen: PNG, JPEG, GIF oder WEBP
| Format | Geeignet für | Größe | Qualität |
|---|---|---|---|
| PNG | Text-CAPTCHAs, Screenshots | Größer | Verlustfrei |
| JPEG | Fotobasierte CAPTCHAs | Kleiner | Verlustbehaftet (Qualität >= 85 verwenden) |
| GIF | Animierte CAPTCHAs | Variabel | Begrenzte Farben |
| WEBP | Moderne Browser | Kleinste | Gute Qualität |
Empfehlung: PNG für Text-CAPTCHAs. Die verlustfreie Komprimierung erhält die Zeichenkanten, und genau diese Kanten wertet die OCR aus. JPEG erzeugt an starken Hell-Dunkel-Übergängen Kompressionsartefakte – bei einem sechsstelligen, stark verzerrten Code wird daraus schnell aus einer 8 eine B. Fotobasierte Abfragen vertragen JPEG dagegen gut, solange die Qualitätsstufe bei 85 oder darüber liegt.
Das Übermittlungsformat der API
Bild-CAPTCHAs gehen als Formularfeld body an den Endpunkt, der Parameter method=base64 kennzeichnet den Inhalt:
import requests
import base64
import os
def submit_image_captcha(image_base64):
"""Submit base64-encoded image to CaptchaAI."""
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": os.environ["CAPTCHAAI_API_KEY"],
"method": "base64",
"body": image_base64,
"json": 1,
}, timeout=30)
return resp.json()
Die Antwort enthält die ID des Auftrags, mit der Sie das Ergebnis anschließend abfragen. Das Timeout von 30 Sekunden deckt die Übertragung des Bildes ab, nicht die Lösungszeit.
Aus einer lokalen Datei kodieren
Der Standardfall – ein Bild liegt auf der Platte, etwa aus einem vorherigen Download-Schritt:
# from_file.py
import base64
def encode_from_file(filepath):
"""Read an image file and return base64 string."""
with open(filepath, "rb") as f:
raw = f.read()
return base64.b64encode(raw).decode("ascii")
# Usage
b64 = encode_from_file("captcha.png")
print(f"Encoded length: {len(b64)} chars")
Entscheidend ist der Modus "rb" und das abschließende .decode("ascii"): Ohne den zweiten Schritt hätten Sie ein bytes-Objekt, das manche HTTP-Bibliotheken stillschweigend mit einem b'…'-Wrapper serialisieren.
Direkt aus einer URL kodieren
Wenn das CAPTCHA-Bild über eine eigene URL ausgeliefert wird, sparen Sie sich die Zwischendatei komplett:
# from_url.py
import requests
import base64
def encode_from_url(image_url):
"""Download image and return base64 string."""
resp = requests.get(image_url, timeout=15)
resp.raise_for_status()
# Verify it's actually an image
content_type = resp.headers.get("Content-Type", "")
if not content_type.startswith("image/"):
raise ValueError(f"Not an image: {content_type}")
return base64.b64encode(resp.content).decode("ascii")
# Usage
b64 = encode_from_url("https://example.com/captcha.png")
Die Prüfung des Content-Type ist kein Luxus: Läuft die Sitzung ab, liefern viele Formulare eine HTML-Seite mit Status 200 statt des Bildes – kodiert wird dann einwandfreies Base64, nur eben von einer Fehlerseite. Ebenso wichtig: Das Bild ist in der Regel an die Sitzung gebunden. Verwenden Sie dieselbe requests.Session wie für das Formular, sonst erhalten Sie ein anderes CAPTCHA, als der Server später erwartet.
Screenshots aus Selenium kodieren
Ist das CAPTCHA als Canvas oder Inline-Grafik eingebettet, führt der Weg über einen Screenshot:
# from_selenium.py
import base64
from selenium.webdriver.common.by import By
def encode_from_element(driver, selector):
"""Screenshot a specific element and return base64."""
element = driver.find_element(By.CSS_SELECTOR, selector)
screenshot_b64 = element.screenshot_as_base64
return screenshot_b64
def encode_from_page_crop(driver, selector):
"""Crop a specific region from the page screenshot."""
from PIL import Image
import io
element = driver.find_element(By.CSS_SELECTOR, selector)
location = element.location
size = element.size
# Full page screenshot
png = driver.get_screenshot_as_png()
img = Image.open(io.BytesIO(png))
# Crop to element bounds
left = location["x"]
top = location["y"]
right = left + size["width"]
bottom = top + size["height"]
cropped = img.crop((left, top, right, bottom))
# Encode
buffer = io.BytesIO()
cropped.save(buffer, format="PNG")
return base64.b64encode(buffer.getvalue()).decode("ascii")
screenshot_as_base64 liefert den String bereits fertig kodiert – das ist der bequemste Weg und die häufigste Quelle für doppelte Kodierung (siehe unten). Die Crop-Variante brauchen Sie, wenn das Element selbst nicht direkt aufgenommen werden kann, etwa innerhalb eines iframes. Achten Sie dabei auf die Pixeldichte: Bei devicePixelRatio über 1 – Standard auf HiDPI-Displays und in vielen Headless-Konfigurationen – liefert der Seiten-Screenshot mehr Pixel, als location und size in CSS-Pixeln angeben. Ohne Skalierung der Koordinaten schneiden Sie am Motiv vorbei.
Drei Kodierungsfehler, die fast jede Integration einmal trifft
Fehler 1: Das Data-URI-Präfix bleibt im String
Browser und viele Frontend-Bibliotheken liefern Bilder als vollständige Data-URI. Alles vor dem Komma ist Metadatenrauschen für die API:
# WRONG — includes data URI prefix
bad = "data:image/png;base64,iVBORw0KGgo..."
# RIGHT — raw base64 only
good = "iVBORw0KGgo..."
# Fix: Strip the prefix
def clean_base64(b64_string):
if "," in b64_string:
return b64_string.split(",", 1)[1]
return b64_string
Fehler 2: Doppelte Kodierung
Ein bereits kodierter String durchläuft ein zweites Mal b64encode. Das Ergebnis ist formal gültiges Base64 – nur dekodiert es zu Text statt zu einem Bild:
# WRONG — encoding an already-encoded string
already_b64 = element.screenshot_as_base64
double_encoded = base64.b64encode(already_b64.encode()).decode() # BAD
# RIGHT — use as-is
correct = element.screenshot_as_base64 # Already base64
Fehler 3: Binärdatei im Textmodus gelesen
Unter Linux fällt dieser Fehler manchmal wochenlang nicht auf, unter Windows sofort: Der Textmodus interpretiert Zeilenenden und zerstört die Bytefolge.
# WRONG — reading as text
with open("captcha.png", "r") as f: # Text mode
content = f.read() # Corrupted binary data
# RIGHT — reading as bytes
with open("captcha.png", "rb") as f: # Binary mode
content = f.read()
encoded = base64.b64encode(content).decode("ascii")
Validieren, bevor Sie absenden
Eine Prüffunktion direkt vor dem Absenden spart pro Fehlerfall eine Runde durch die API und macht den Log-Eintrag eindeutig:
# validate.py
import base64
import io
def validate_captcha_image(b64_string):
"""Validate base64 image before submitting to CaptchaAI."""
errors = []
# Check for data URI prefix
if b64_string.startswith("data:"):
errors.append("Contains data URI prefix — strip it")
b64_string = b64_string.split(",", 1)[1]
# Try decoding
try:
decoded = base64.b64decode(b64_string)
except Exception as e:
return {"valid": False, "errors": [f"Invalid base64: {e}"]}
# Check size
size_kb = len(decoded) / 1024
if size_kb < 1:
errors.append(f"Image too small ({size_kb:.1f} KB) — likely corrupt")
if size_kb > 500:
errors.append(f"Image large ({size_kb:.1f} KB) — consider resizing")
# Check image format
if decoded[:8] == b'\x89PNG\r\n\x1a\n':
fmt = "PNG"
elif decoded[:3] == b'\xff\xd8\xff':
fmt = "JPEG"
elif decoded[:4] == b'GIF8':
fmt = "GIF"
elif decoded[:4] == b'RIFF':
fmt = "WEBP"
else:
errors.append("Unknown image format")
fmt = "unknown"
return {
"valid": len(errors) == 0,
"format": fmt,
"size_kb": round(size_kb, 1),
"errors": errors,
}
# Usage
result = validate_captcha_image(b64_string)
if not result["valid"]:
print(f"Issues: {result['errors']}")
else:
print(f"Valid {result['format']}, {result['size_kb']} KB")
Die Funktion prüft die Signaturbytes am Dateianfang und erkennt so auch Bilder, deren Endung nicht zum Inhalt passt. Loggen Sie im Fehlerfall Format, Größe und die ersten zwanzig Zeichen – nie den kompletten String, sonst laufen Ihnen die Logs voll.
Fehlercodes richtig zuordnen
| Problem | Ursache | Lösung |
|---|---|---|
ERROR_WRONG_FILE_EXTENSION |
Ungültige Base64-Daten | Mit validate_captcha_image() prüfen |
ERROR_TOO_BIG_CAPTCHA_FILESIZE |
Bild über 600 KB | Vor dem Kodieren skalieren oder komprimieren |
ERROR_ZERO_CAPTCHA_FILESIZE |
Leeres oder beschädigtes Bild | Prüfen, ob der Download erfolgreich war |
| Falsches Lösungsergebnis | Überkomprimiertes JPEG | PNG verwenden oder JPEG-Qualität >= 85 |
Die ersten drei Codes betreffen die Kodierung, der vierte Fall ist eine Qualitätsfrage: Die API antwortet, nur eben mit der falschen Zeichenfolge. Trennen Sie beides im Monitoring – das zeigt sofort, ob ein Deployment die Kodierung zerlegt hat.
Durchsatz und Betrieb im DACH-Alltag
Ein Beispiel aus der Praxis: Ein Preisbeobachtungs-Job läuft nachts auf einem Hetzner-Server in Falkenstein und arbeitet rund 40.000 Formularseiten ab; bei etwa 8 % erscheint ein Bild-CAPTCHA. Die Kodierung selbst kostet dabei Millisekunden – der Engpass ist die Parallelität. CaptchaAI rechnet pro gleichzeitigem Thread ab, nicht pro Lösung: BASIC (15 $/Monat, 5 Threads) genügt für kleine Jobs, ADVANCE (90 $/Monat, 50 Threads) bedient eine breit verteilte Worker-Matrix in GitLab CI. Preise in US-Dollar. Für Bild-CAPTCHAs nennt CaptchaAI eine Lösungszeit von unter 0,5 Sekunden – ein einzelner Thread schafft über eine Nacht Tausende Abfragen.
Ein zweiter Punkt, der in DACH-Projekten regelmäßig auftaucht: Vollbild-Screenshots eingeloggter Seiten enthalten oft personenbezogene Daten – Namen, E-Mail-Adressen, Kundennummern. Nehmen Sie deshalb das CAPTCHA-Element gezielt auf statt der ganzen Seite, und behalten Sie kodierte Bilder nicht länger als nötig in Logs oder Artefakt-Speichern. Die Prüfung der eigenen Rechtsgrundlage nach DSGVO bleibt Aufgabe des Betreibers und ist mit einem Element-Screenshot deutlich einfacher zu begründen.
FAQ
Muss ich das Präfix data:image/png;base64, mitsenden?
Nein. Die API erwartet ausschließlich den Teil nach dem Komma. Schneiden Sie das Präfix vor dem Absenden ab – am besten zentral in einer Hilfsfunktion.
Wie groß darf das Bild maximal sein?
Bis 600 KB nach dem Dekodieren. Größere Screenshots skalieren Sie vorher herunter; bei Text-CAPTCHAs reicht die native Elementgröße völlig aus, ein hochskaliertes Bild bringt keinen Genauigkeitsgewinn.
Kann ich Screenshots aus einem Headless-Browser ohne Zwischendatei übermitteln?
Ja. element.screenshot_as_base64 liefert den fertigen String direkt aus dem Speicher. Der Umweg über eine temporäre Datei ist nur nötig, wenn Sie das Bild ohnehin für die Fehlersuche aufbewahren wollen.
Warum kommen bei hochskalierten Screenshots falsche Antworten zurück?
Weil Interpolation weiche Kanten erzeugt. Die OCR wertet Zeichenkanten aus; ein von 120 auf 480 Pixel Breite gestrecktes Bild liefert unschärfere Kanten als das Original. Nehmen Sie das Element in seiner tatsächlichen Auflösung auf.
Kostet jedes einzelne Bild-CAPTCHA extra?
Nein. Die Abrechnung erfolgt Thread-basiert, jeder Tarif enthält unbegrenzte Lösungen pro Thread im Abrechnungsmonat. Sie planen also nach gewünschter Parallelität, nicht nach Bildmenge.
Verwandte Leitfäden
Sauber kodiert, sofort gelöst – starten Sie mit CaptchaAI.