Tutorials

Python Playwright + CaptchaAI Vollständiger Integrationsleitfaden

CAPTCHAs stoppen Ihre Playwright-Automatisierung genau dort, wo es zählt – beim Login oder beim Absenden eines Formulars. Die Lösung ist unkompliziert: Sie übergeben Sitekey und Ziel-URL an CaptchaAI, erhalten ein gültiges Token zurück und tragen es in die Seite ein, ohne den asynchronen Ablauf zu unterbrechen. Dieser Leitfaden führt Schritt für Schritt durch die vollständige Integration von CaptchaAI in Python Playwright – vom asynchronen Solver-Client über reCAPTCHA v2, Cloudflare Turnstile und Bild-CAPTCHAs bis zu einer wiederverwendbaren Automatisierungsklasse.

Warum Playwright? Der native async-Support, die eingebaute Request-Interception und die soliden Standardwerte machen es für neue Automatisierungsprojekte oft zur besseren Wahl als Selenium – gerade wenn viele CAPTCHA-Lösungen parallel laufen sollen.


Installation und Voraussetzungen

Playwright und der asynchrone HTTP-Client aiohttp bilden die Grundlage. Installieren Sie beide Pakete und laden Sie den Chromium-Browser herunter:

pip install playwright aiohttp
playwright install chromium

Der asynchrone Solver-Client

Der Kern der Integration ist eine einzige async-Funktion. Sie übermittelt die CAPTCHA-Aufgabe an den Endpunkt in.php, fragt anschließend res.php im Fünf-Sekunden-Takt ab und gibt das fertige Token zurück. Weil sie vollständig non-blocking arbeitet, bremst das Polling Ihren übrigen Playwright-Code nicht aus:

import aiohttp
import asyncio

API_KEY = "YOUR_API_KEY"


async def solve_captcha(method, **params):
    """Async CaptchaAI solver for Playwright workflows."""
    async with aiohttp.ClientSession() as session:
        # Submit task
        submit_data = {
            "key": API_KEY,
            "method": method,
            "json": 1,
            **params,
        }
        async with session.post("https://ocr.captchaai.com/in.php", data=submit_data) as resp:
            data = await resp.json(content_type=None)
            if data.get("status") != 1:
                raise Exception(f"Submit error: {data.get('request')}")
            task_id = data["request"]

        # Poll for result
        for _ in range(30):
            await asyncio.sleep(5)
            async with session.get("https://ocr.captchaai.com/res.php", params={
                "key": API_KEY,
                "action": "get",
                "id": task_id,
                "json": 1,
            }) as resp:
                result = await resp.json(content_type=None)
                if result.get("status") == 1:
                    return result["request"]
                if result.get("request") == "ERROR_CAPTCHA_UNSOLVABLE":
                    raise Exception("CAPTCHA unsolvable")

        raise TimeoutError("Solve timed out")

Playwright-Browser einrichten

Konfigurieren Sie Kontext, User-Agent und Viewport so, dass die Automatisierung wie eine reguläre Browser-Sitzung auftritt. Das sorgt für stabiles, realistisches Browser-Verhalten – wichtig, damit Zielseiten das CAPTCHA überhaupt normal ausliefern:

from playwright.async_api import async_playwright


async def create_browser():
    """Launch Playwright browser for CAPTCHA automation."""
    pw = await async_playwright().start()
    browser = await pw.chromium.launch(
        headless=False,
        args=[
            "--disable-blink-features=AutomationControlled",
        ],
    )
    context = await browser.new_context(
        user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
                   "(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
        viewport={"width": 1920, "height": 1080},
        locale="en-US",
    )

    page = await context.new_page()
    return pw, browser, context, page

reCAPTCHA v2 mit Playwright lösen

Der Ablauf ist bei jedem Typ ähnlich: Sitekey aus dem DOM extrahieren, an CaptchaAI übermitteln, das zurückgegebene g-recaptcha-response-Token einfügen und – falls vorhanden – den Callback auslösen. Anschließend senden Sie das Formular ab:

import re


async def solve_recaptcha_v2_playwright(page, url):
    """Complete reCAPTCHA v2 solve in Playwright."""
    await page.goto(url, wait_until="networkidle")

    # Extract sitekey from the page
    content = await page.content()
    match = re.search(r'data-sitekey=["\']([A-Za-z0-9_-]{40})["\']', content)
    if not match:
        raise ValueError("reCAPTCHA sitekey not found")

    sitekey = match.group(1)
    print(f"Sitekey: {sitekey}")

    # Solve via CaptchaAI
    token = await solve_captcha(
        "userrecaptcha",
        googlekey=sitekey,
        pageurl=url,
    )
    print(f"Token: {token[:50]}...")

    # Inject token
    await page.evaluate(f"""() => {{
        document.getElementById('g-recaptcha-response').value = '{token}';
        document.getElementById('g-recaptcha-response').style.display = 'block';
    }}""")

    # Trigger callback if available
    await page.evaluate(f"""() => {{
        if (typeof ___grecaptcha_cfg !== 'undefined') {{
            var clients = ___grecaptcha_cfg.clients;
            for (var key in clients) {{
                var client = clients[key];
                try {{
                    Object.keys(client).forEach(function(k) {{
                        if (client[k] && client[k].callback) {{
                            client[k].callback('{token}');
                        }}
                    }});
                }} catch(e) {{}}
            }}
        }}
    }}""")

    # Submit form
    await page.click("button[type='submit'], input[type='submit']")
    await page.wait_for_load_state("networkidle")

    return token

Cloudflare Turnstile mit Playwright lösen

Turnstile folgt demselben Muster, nur mit der Methode turnstile und dem Feld cf-turnstile-response. Der Sitekey beginnt hier typischerweise mit 0x, weshalb das reguläre Ausdrucksmuster leicht abweicht:

async def solve_turnstile_playwright(page, url):
    """Complete Turnstile solve in Playwright."""
    await page.goto(url, wait_until="networkidle")

    content = await page.content()

    # Extract sitekey
    match = re.search(r'data-sitekey=["\']([0-9x][A-Za-z0-9_-]+)["\']', content)
    if not match:
        match = re.search(r"sitekey\s*:\s*['\"]([0-9x][A-Za-z0-9_-]+)['\"]", content)
    if not match:
        raise ValueError("Turnstile sitekey not found")

    sitekey = match.group(1)
    print(f"Turnstile sitekey: {sitekey}")

    # Solve via CaptchaAI
    token = await solve_captcha(
        "turnstile",
        sitekey=sitekey,
        pageurl=url,
    )

    # Inject token into hidden inputs
    await page.evaluate(f"""() => {{
        document.querySelectorAll('[name="cf-turnstile-response"]')
            .forEach(el => el.value = '{token}');
    }}""")

    # Submit
    await page.click("button[type='submit'], input[type='submit']")
    await page.wait_for_load_state("networkidle")

    return token

Bild-CAPTCHAs mit Playwright lösen

Klassische Bild-CAPTCHAs lösen Sie per Screenshot: Sie fotografieren das Element, kodieren es als Base64 und schicken es mit der Methode base64 an die API. Die Antwort tragen Sie direkt in das Eingabefeld ein:

async def solve_image_captcha_playwright(page, captcha_selector):
    """Solve image CAPTCHA visible on the page."""
    captcha_element = page.locator(captcha_selector)

    # Screenshot the CAPTCHA image
    img_bytes = await captcha_element.screenshot()
    import base64
    img_base64 = base64.b64encode(img_bytes).decode()

    # Solve via CaptchaAI
    answer = await solve_captcha("base64", body=img_base64)
    print(f"Answer: {answer}")

    # Type the answer
    captcha_input = page.locator("input[name='captcha'], input[name='code'], input.captcha-input")
    await captcha_input.fill(answer)

    return answer

Netzwerkanfragen abfangen

Playwright bringt das Abfangen von Requests von Haus aus mit. Das ist praktisch, um CAPTCHA-Parameter direkt aus den API-Aufrufen der Seite zu lesen, statt sie mühsam aus dem HTML zu parsen:

async def intercept_captcha_params(page, url):
    """Intercept network requests to find CAPTCHA parameters."""
    captcha_params = {}

    async def handle_request(route, request):
        if "recaptcha" in request.url or "turnstile" in request.url:
            from urllib.parse import urlparse, parse_qs
            parsed = urlparse(request.url)
            params = parse_qs(parsed.query)
            captcha_params.update(params)
            print(f"Intercepted: {request.url}")
        await route.continue_()

    await page.route("**/*", handle_request)
    await page.goto(url, wait_until="networkidle")
    await page.unroute("**/*")

    return captcha_params

Die komplette Automatisierungsklasse

PlaywrightCaptchaSolver bündelt den gesamten Ablauf – Browser starten, CAPTCHA-Typ erkennen, lösen, Formular ausfüllen und absenden – in einer wiederverwendbaren Klasse. So rufen Sie in der Anwendung nur noch eine Methode auf:

import re
import asyncio
import aiohttp
import base64
from playwright.async_api import async_playwright


API_KEY = "YOUR_API_KEY"


class PlaywrightCaptchaSolver:
    """Complete Playwright + CaptchaAI automation class."""

    def __init__(self, api_key, headless=False):
        self.api_key = api_key
        self.headless = headless
        self.pw = None
        self.browser = None
        self.context = None
        self.page = None

    async def start(self):
        """Initialize the browser."""
        self.pw = await async_playwright().start()
        self.browser = await self.pw.chromium.launch(
            headless=self.headless,
            args=["--disable-blink-features=AutomationControlled"],
        )
        self.context = await self.browser.new_context(
            user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
                       "(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
            viewport={"width": 1920, "height": 1080},
        )
        await self.context.add_init_script(
            "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"
        )
        self.page = await self.context.new_page()

    async def stop(self):
        """Close the browser."""
        if self.browser:
            await self.browser.close()
        if self.pw:
            await self.pw.stop()

    async def navigate(self, url):
        """Navigate and wait for page to load."""
        await self.page.goto(url, wait_until="networkidle")

    async def detect_captcha(self):
        """Detect which CAPTCHA type is present."""
        content = await self.page.content()

        if re.search(r'data-sitekey=["\'][A-Za-z0-9_-]{40}["\']', content):
            if "recaptcha" in content.lower():
                return "recaptcha_v2"

        if "cf-turnstile" in content or "challenges.cloudflare.com/turnstile" in content:
            return "turnstile"

        if re.search(r"render=[A-Za-z0-9_-]{40}", content):
            return "recaptcha_v3"

        img_count = await self.page.locator(
            "img.captcha, img[alt*='captcha'], img[src*='captcha']"
        ).count()
        if img_count > 0:
            return "image"

        return None

    async def solve_and_submit(self, url, form_data=None):
        """Full workflow: navigate, detect, solve, fill, submit."""
        await self.navigate(url)
        captcha_type = await self.detect_captcha()

        if captcha_type:
            print(f"Detected: {captcha_type}")
            await self._solve(captcha_type)

        if form_data:
            for name, value in form_data.items():
                try:
                    await self.page.fill(f"[name='{name}']", value)
                except Exception:
                    pass

        await self.page.click("button[type='submit'], input[type='submit']")
        await self.page.wait_for_load_state("networkidle")
        return self.page.url

    async def _solve(self, captcha_type):
        content = await self.page.content()
        url = self.page.url

        if captcha_type == "recaptcha_v2":
            match = re.search(r'data-sitekey=["\']([A-Za-z0-9_-]{40})["\']', content)
            token = await self._api_solve("userrecaptcha", googlekey=match.group(1), pageurl=url)
            await self.page.evaluate(f"""() => {{
                document.getElementById('g-recaptcha-response').value = '{token}';
            }}""")

        elif captcha_type == "turnstile":
            match = re.search(r'data-sitekey=["\']([0-9x][A-Za-z0-9_-]+)["\']', content)
            token = await self._api_solve("turnstile", sitekey=match.group(1), pageurl=url)
            await self.page.evaluate(f"""() => {{
                document.querySelectorAll('[name="cf-turnstile-response"]')
                    .forEach(el => el.value = '{token}');
            }}""")

        elif captcha_type == "image":
            img = self.page.locator("img.captcha, img[alt*='captcha'], img[src*='captcha']").first
            img_bytes = await img.screenshot()
            answer = await self._api_solve("base64", body=base64.b64encode(img_bytes).decode())
            await self.page.fill("input[name='captcha'], input[name='code']", answer)

    async def _api_solve(self, method, **params):
        async with aiohttp.ClientSession() as session:
            async with session.post("https://ocr.captchaai.com/in.php", data={
                "key": self.api_key, "method": method, "json": 1, **params,
            }) as resp:
                data = await resp.json(content_type=None)
                if data.get("status") != 1:
                    raise Exception(f"Submit error: {data.get('request')}")
                task_id = data["request"]

            for _ in range(30):
                await asyncio.sleep(5)
                async with session.get("https://ocr.captchaai.com/res.php", params={
                    "key": self.api_key, "action": "get", "id": task_id, "json": 1,
                }) as resp:
                    result = await resp.json(content_type=None)
                    if result.get("status") == 1:
                        return result["request"]
            raise TimeoutError("Solve timed out")


# Usage
async def main():
    solver = PlaywrightCaptchaSolver(API_KEY)
    await solver.start()
    try:
        result = await solver.solve_and_submit(
            "https://example.com/login",
            form_data={"email": "user@example.com", "password": "pass123"},
        )
        print(f"Result: {result}")
    finally:
        await solver.stop()


asyncio.run(main())

In der Praxis läuft diese Klasse selten auf dem Entwicklerrechner. Häufig hängt sie in einer CI-Pipeline – GitLab CI ist in vielen DACH-Unternehmen der Standard – und wird headless auf einem VPS betrieben, etwa bei Hetzner oder netcup. Setzen Sie headless=True, wenn Sie in einer solchen Umgebung ohne Display arbeiten. Beim Scraping personenbezogener Daten sollten Sie zudem Ihre DSGVO-Grundlage prüfen: IP-Adressen zählen als personenbezogene Daten, und die Rechtsgrundlage für die Verarbeitung liegt in Ihrer Verantwortung.


Playwright oder Selenium: der Vergleich fürs CAPTCHA-Handling

Beide Frameworks lösen CAPTCHAs zuverlässig über CaptchaAI. Für neue Projekte spricht jedoch einiges für Playwright:

Funktion Playwright Selenium
Async nativ Ja Nein (Threads nötig)
Browser-Konfiguration Bessere Standardwerte Mehr Konfiguration nötig
Geschwindigkeit Schneller Langsamere Seitenladezeiten
Request-Interception Eingebaut Proxy/Erweiterung nötig
Multi-Browser Chromium, Firefox, WebKit Chrome, Firefox, Edge, Safari
API-Stil Promise-basiert, modern Imperativ, klassisch

Fehlerbehebung

Die meisten Probleme lassen sich auf Timing oder falsche Selektoren zurückführen:

Symptom Ursache Lösung
page.evaluate schlägt fehl Inhalt noch nicht geladen wait_until="networkidle" verwenden
Token-Einfügen wirkt nicht Falscher Element-Selektor Mit page.content() das echte Element prüfen
Playwright wird erkannt Init-Skript fehlt Webdriver-Override in add_init_script ergänzen
Timeout bei networkidle Endlose Polling-Skripte Stattdessen wait_until="domcontentloaded"
Bild-Screenshot ist leer Element ausgeblendet In den Sichtbereich scrollen: await element.scroll_into_view_if_needed()

Häufige Fragen

Welche CAPTCHA-Typen löst CaptchaAI mit Playwright?

reCAPTCHA v2 und v3 (inklusive Enterprise), Cloudflare Turnstile und Challenge, GeeTest v3 sowie Bild-, OCR-, Raster- und BLS-CAPTCHAs. CaptchaFox, Friendly Captcha und Lemin sind zusätzlich in der Beta verfügbar. hCaptcha und FunCaptcha werden derzeit nicht unterstützt; GeeTest v4 ist bald verfügbar.

Was kostet das Lösen von CAPTCHAs mit CaptchaAI?

CaptchaAI rechnet pro Thread ab, nicht pro Lösung. Die Tarife reichen von BASIC (15 $/Monat, 5 Threads) bis VIP-3 (7.500 $/Monat, 5.000 Threads) – jeweils mit unbegrenzten Lösungen pro Thread. Ein Thread ist ein gleichzeitig laufendes CAPTCHA; sobald es gelöst ist, übernimmt der Thread das nächste. Aktuelle Preise finden Sie auf captchaai.com/pricing.

Wie lange ist ein gelöstes Token gültig?

Meist etwa 120 Sekunden. Lösen Sie das CAPTCHA deshalb erst unmittelbar vor dem Absenden des Formulars und fügen Sie das Token direkt danach ein – so läuft es nicht ab, bevor Ihre Automatisierung es verwendet.

Beeinflusst der Headless-Modus die Erfolgsquote?

Nein. Setzen Sie headless=True in launch(). Manche Seiten erkennen den Headless-Modus zwar an der Auslieferung, aber CaptchaAI löst auf eigener Infrastruktur – ob Ihr Browser mit oder ohne Display läuft, ändert am Lösungsergebnis nichts. Testen Sie im Zweifel beide Varianten.


Fazit

Python Playwright und CaptchaAI ergeben einen modernen, asynchronen Stack für die CAPTCHA-Automatisierung. Die Klasse PlaywrightCaptchaSolver deckt den gesamten Ablauf ab – erkennen, lösen, einfügen, absenden – vollständig non-blocking und mit eingebautem Request-Interception. Beginnen Sie mit dem asynchronen Solver-Client und ergänzen Sie nach und nach die Typen, die Ihre Zielseiten tatsächlich einsetzen.

Verwandte Leitfäden

Kommentare sind für diesen Artikel deaktiviert.