So lösen Sie DataDome CAPTCHA in C# mit HttpClient

datadome captcha in c# - How to Solve DataDome CAPTCHA in C# With HttpClient

Um ein DataDome CAPTCHA in C# zu lösen, lesen Sie die 403-Sperrseite, bauen daraus die Challenge-Adresse und senden diese Adresse an den CapSkip-Solver für DataDome mit DataDomeAsync und wiederholen die Anfrage über HttpClient mit dem zurückgegebenen Cookie. Die Antwort ist ein Cookie und kein Token für ein Formular, und laut der CapSkip-Dokumentation funktioniert es nur mit dem User-Agent und der IP-Adresse, mit denen es ausgestellt wurde. Dieser Leitfaden führt die ganze Schleife in einem Konsolenprojekt mit dem NuGet-Paket CapSkip und sonst nichts aus. CapSkip läuft auf Ihrem eigenen Windows-PC ohne Gebühr pro Lösung, daher kostet eine Wiederholung nichts.

Was Sie brauchen

  • CapSkip 3.2.0 oder neuer, das auf einem Windows-PC läuft. Mit dieser Version kam das Lösen von DataDome für den Slider und die Geräteprüfung dazu. Die Einrichtungsanleitung führt durch den ersten Start.
  • Das .NET 8 SDK oder neuer und das NuGet-Paket CapSkip ab Version 1.5.0. Frühere Versionen haben keine Methode DataDomeAsync. Der Code unten lief auf .NET SDK 10.0.100, auf net10.0 und net8.0 mit CapSkip 1.5.0.
  • Eine PowerShell-Eingabeaufforderung. Die Befehle verwenden PowerShell-Syntax.
  • Eine Seite, die Ihnen gehört oder die Sie automatisieren dürfen, hinter DataDome.
  • Eine Adresse für den Solver. Im Local-Modus antwortet CapSkip auf 127.0.0.1 nur für dieses Gerät. Im Server-Modus lauscht es auf Ihrer Netzwerkadresse oder öffentlichen IP, damit ein Programm auf einem anderen PC, einem VPS oder einer gehosteten Plattform es über die API aufrufen kann. Eingestellt werden beide Modi in den Verbindungseinstellungen.
  • Ein Sticky-Proxy (ein Proxy, der dieselbe IP-Adresse behält) pro Aufgabe, sobald Sie über das Testen hinausgehen. Das Cookie ist an die IP-Adresse gebunden, mit der es ausgestellt wurde, was Schritt 3 erklärt.

So wurde getestet: Der Code lief gegen drei Mock-Server, die ich geschrieben habe, nicht gegen eine echte DataDome-Website. Einer bildet die Prüfungen nach, die im DataDome-Abschnitt der CapSkip API-Dokumentation, ein zweiter spielt eine geschützte Website, die eine 403-Sperrseite ausliefert und nur ein Cookie akzeptiert, das CapSkip ausgestellt hat, mit dem User-Agent, für den es ausgestellt wurde, und ein dritter spielt einen Forward-Proxy. Das SDK war das echte Paket CapSkip 1.5.0 von NuGet. Die CapSkip-App lief nicht auf meinem PC, daher zeigt nichts hier, wie lange eine echte Lösung dauert oder was eine echte DataDome-Website akzeptiert. Die Regeln zum Cookie stammen aus der CapSkip-Dokumentation. Die eigene Cookie-Dokumentation von DataDome sagt, dass das Cookie datadome ein Jahr gültig ist, und warnt davor, seine Domain oder seinen Path zu ändern. Woran das Cookie gebunden ist, sagt sie nicht.

Schritt 1: Das Projekt anlegen und die Sperrseite lesen

Legen Sie ein Konsolenprojekt an und fügen Sie das Paket hinzu. Die Standardvorlage schaltet implizite usings und Nullable-Typen ein, und der Code verlässt sich auf beides.

dotnet new console -o datadome-demo
cd datadome-demo
dotnet add package CapSkip

Wenn DataDome an einer Anfrage zweifelt, antwortet die Website statt mit dem Inhalt mit dem Status 403, manchmal 401, und einer kleinen HTML-Seite. Diese Seite enthält ein Skript-Objekt namens dd und lädt die Challenge in einem iframe von geo.captcha-delivery.com. Die Adresse des iframe braucht CapSkip, und die Dokumentation nennt sie captchaUrl. Enthält die Seite den iframe bereits, können Sie dessen src unverändert übernehmen. Die meisten Sperrseiten bauen den iframe stattdessen per Skript, daher setzt der erste Block dieselbe Adresse aus dem Objekt dd zusammen. Die erste Anfrage geht mit einem einfachen Chrome-User-Agent hinaus. Diese Zeichenfolge wird nie an CapSkip gesendet, daher muss ihre Chrome-Version zu nichts passen, und ich habe nicht getestet, ob eine echte DataDome-Website einem anderen User-Agent dieselbe Challenge ausliefert. Ersetzen Sie den Inhalt von Program.cs durch die vier Blöcke dieses Leitfadens, einen nach dem anderen, beginnend mit diesem.

Der Wert von rt bestimmt den Pfad. Ein c steht für den Slider unter /captcha/, ein i für die Geräteprüfung unter /interstitial/. Auch der Wert t zählt: fe ist eine Challenge, und bv bedeutet, dass DataDome die IP-Adresse gesperrt hat, die die Seite abgerufen hat.

using System.Net;
using System.Text.Json;
using System.Text.RegularExpressions;
using CapSkip;

static class BlockPage
{
    // Build the challenge address from a DataDome block page, or return null.
    public static string? CaptchaUrl(HttpStatusCode status, string html, string pageUrl)
    {
        if (status != HttpStatusCode.Forbidden && status != HttpStatusCode.Unauthorized) return null;
        var found = Regex.Match(html, @"var\s+dd\s*=\s*(\{[^}]*\})");
        if (!found.Success) return null;

        using var doc = JsonDocument.Parse(found.Groups[1].Value.Replace('\'', '"'));
        var dd = doc.RootElement;
        string? Get(string name) => dd.TryGetProperty(name, out var v)
            ? (v.ValueKind == JsonValueKind.String ? v.GetString() : v.GetRawText())
            : null;

        var query = new List<(string Key, string? Value)>
        {
            ("initialCid", Get("cid")), ("hash", Get("hsh")), ("cid", Get("cookie")),
        };
        if (!string.IsNullOrEmpty(Get("t"))) query.Add(("t", Get("t")));  // fe challenge, bv banned
        query.Add(("referer", pageUrl));
        query.Add(("s", Get("s")));
        foreach (var key in new[] { "e", "b" })
            if (!string.IsNullOrEmpty(Get(key))) query.Add((key, Get(key)));
        query.Add(("dm", "cd"));

        var path = Get("rt") == "i" ? "interstitial" : "captcha";
        var qs = string.Join("&", query.Select(p => p.Key + "=" + Uri.EscapeDataString(p.Value ?? "")));
        return $"https://{Get("host") ?? "geo.captcha-delivery.com"}/{path}/?{qs}";
    }
}

Die Methode gibt null zurück, wenn die Antwort keine Sperrseite ist, sodass der Aufrufer “nichts zu lösen” von “gelöst” unterscheiden kann. Jede Sperrseite ist eine neue Challenge. Verwenden Sie deren Adresse bald, und rufen Sie die Seite für die nächste Aufgabe erneut ab, statt eine alte Adresse wiederzuverwenden.

Schritt 2: Mit DataDomeAsync lösen

Die SDK-Methode lautet DataDomeAsync(captchaUrl, url, options) und folgt der Argumentreihenfolge von 2Captcha. Die beiden Adressen sind Positionsargumente, userAgent und proxy gehen in das Dictionary options. Beide sind in CapSkip optional. Senden Sie keinen User-Agent, verwendet CapSkip den aus seinem eigenen Browser und gibt ihn im Ergebnis als UserAgent zurück. Senden Sie doch einen, muss es Chrome für Windows mit derselben Hauptversion wie im Browser von CapSkip sein, daher ist das Weglassen die einfache Wahl. Die Methode wartet mit dem längeren Timeout von 300 Sekunden, den auch reCAPTCHA verwendet, weil CapSkip die Challenge in einem echten Browser besteht. Laut der API-Dokumentation dauert eine Lösung meist 3 bis 20 Sekunden, und ich habe keine gemessen.

Der Proxy ist derjenige, den Ihre späteren Anfragen verwenden. Übergeben Sie ihn, wenn Ihr Programm über einen Proxy nach außen geht, und übergeben Sie nichts, wenn die Anfragen von dem Rechner ausgehen, auf dem CapSkip läuft. Bei einem CapSkip auf einem Server ist dieser Rechner der Server. Die Klasse Proxy nimmt den Typ und die Adresse entgegen, etwa HTTP und user:[email protected]:8080.

static class Solve
{
    // Local mode. In Server mode, use your server's address instead.
    static readonly CapSkipClient Solver = new(host: "127.0.0.1", port: 8080);

    public static Task<SolveResult> Run(string captchaUrl, string pageUrl, string? proxyUrl)
    {
        var options = new Dictionary<string, object?>();
        if (proxyUrl != null)
        {
            var i = proxyUrl.IndexOf("://", StringComparison.Ordinal);
            options["proxy"] = new Proxy(proxyUrl[..i].ToUpperInvariant(), proxyUrl[(i + 3)..]);
        }
        // No userAgent option: CapSkip uses its own browser's and reports it.
        return Solver.DataDomeAsync(captchaUrl, pageUrl, options);
    }
}

Lässt sich die Geräteprüfung nicht erfüllen, wechselt DataDome zu einem Slider. Laut der CapSkip-Dokumentation folgt der eine Aufruf dorthin, Sie brauchen also keinen getrennten Code für die beiden Challenges.

Schritt 3: Das Cookie mit seinem User-Agent und seiner IP-Adresse senden

Das Ergebnis ist ein SolveResult mit vier wichtigen Eigenschaften. Die Eigenschaft Code des Ergebnisses ist das Cookie genau so, wie DataDome es gesetzt hat, mit Max-Age, Domain und den anderen Attributen, und genau das will ein Cookie-Jar. Seine Eigenschaft Cookie ist nur das Paar aus Name datadome und Wert, und genau das will ein Cookie-Header. UserAgent ist der User-Agent, mit dem es ausgestellt wurde. Die Eigenschaft Proxy ist nur gesetzt, wenn Sie keinen Proxy gesendet haben und CapSkip über die eigene Proxy-Liste von CapSkip für DataDome gelöst hat. Laut der CapSkip-Dokumentation lehnt DataDome das Cookie von einer anderen IP-Adresse oder mit einem anderen User-Agent ab. Senden Sie also jedes Mal denselben User-Agent, und wenn das Ergebnis einen Proxy nennt, senden Sie die Anfrage darüber. Diese Eigenschaft Proxy besteht nur aus Host und Port, ohne Schema und ohne Login. Das Programm setzt http:// davor und geht daher von einem Listen-Proxy ohne Login aus. Hat Ihrer einen, ergänzen Sie ihn.

Der Helfer unten setzt den Header Cookie von Hand und schaltet UseCookies aus, damit HttpClient ihn so sendet, wie er geschrieben ist. Er liest außerdem ein Ersatz-Cookie aus der Antwort, weil DataDome bei einer späteren Antwort eines senden kann und ein Browser es behalten würde. Er erzeugt pro Aufruf einen HttpClient, um kurz zu bleiben. Bei vielen Anfragen behalten Sie stattdessen einen Client pro Proxy.

static class Web
{
    static HttpClient Client(string? proxyUrl)
    {
        // UseCookies is off, so the Cookie header set by hand goes out as it is.
        var handler = new HttpClientHandler { UseCookies = false };
        if (proxyUrl != null)
        {
            var uri = new Uri(proxyUrl);
            var proxy = new WebProxy($"{uri.Scheme}://{uri.Host}:{uri.Port}");
            if (uri.UserInfo.Length > 0)
            {
                var login = uri.UserInfo.Split(':', 2);
                proxy.Credentials = new NetworkCredential(
                    Uri.UnescapeDataString(login[0]),
                    login.Length > 1 ? Uri.UnescapeDataString(login[1]) : "");
            }
            handler.Proxy = proxy;
        }
        return new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(30) };
    }

    // Returns the status, the body and a replacement datadome cookie if the site sent one.
    public static async Task<(HttpStatusCode Status, string Body, string? Fresh)> Get(
        string url, string userAgent, string? cookie, string? proxyUrl)
    {
        using var http = Client(proxyUrl);
        using var request = new HttpRequestMessage(HttpMethod.Get, url);
        request.Headers.TryAddWithoutValidation("User-Agent", userAgent);
        if (cookie != null) request.Headers.TryAddWithoutValidation("Cookie", cookie);
        using var response = await http.SendAsync(request);

        string? fresh = null;
        if (response.Headers.TryGetValues("Set-Cookie", out var lines))
            fresh = lines.Select(l => Regex.Match(l, "^datadome=[^;]+"))
                         .FirstOrDefault(m => m.Success)?.Value;
        return (response.StatusCode, await response.Content.ReadAsStringAsync(), fresh);
    }
}

Schritt 4: Das vollständige funktionierende Beispiel

Der letzte Block fügt den Ablauf für das DataDome CAPTCHA in C# zusammen und ergänzt die Wiederholung. Führen Sie ihn mit dotnet run aus. Jeder Versuch ruft die Seite erneut ab, daher trägt jeder Versuch eine frische Challenge. Eine fehlgeschlagene Lösung wirft ApiException, und die Schleife versucht es erneut. Auch ein Cookie, das trotzdem eine 403 bekommt, schickt die Schleife mit der neuen Sperrseite noch einmal los.

static class Program
{
    const string PageUrl = "https://www.example.com/";  // a page behind DataDome
    const string Browser = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " +
                           "(KHTML, like Gecko) Chrome/156.0.0.0 Safari/537.36";
    // The proxy your requests use, e.g. "http://user:[email protected]:8080". Null means
    // they leave from the PC that runs CapSkip.
    static readonly string? ProxyUrl = null;

    static async Task<int> Main()
    {
        for (var attempt = 1; attempt <= 3; attempt++)
        {
            var (status, body, _) = await Web.Get(PageUrl, Browser, null, ProxyUrl);  // fresh block page
            var captchaUrl = BlockPage.CaptchaUrl(status, body, PageUrl);
            if (captchaUrl is null)
            {
                Console.WriteLine($"{(int)status}: no DataDome block page on this response");
                return 0;
            }

            SolveResult result;
            try { result = await Solve.Run(captchaUrl, PageUrl, ProxyUrl); }
            catch (ApiException ex)
            {
                Console.WriteLine($"attempt {attempt}: {ex.Message}");  // the next try gets a new page
                continue;
            }

            // The cookie works only from the address and User-Agent that earned it.
            var via = ProxyUrl ?? (result.Proxy is null ? null : "http://" + result.Proxy);
            var cookie = result.Cookie;
            var (code, page, fresh) = await Web.Get(PageUrl, result.UserAgent!, cookie, via);
            if (code != HttpStatusCode.OK)
            {
                Console.WriteLine($"attempt {attempt}: HTTP {(int)code} with the cookie");
                continue;
            }
            cookie = fresh ?? cookie;  // DataDome may rotate the cookie; keep the newest
            Console.WriteLine($"{(int)code} {page[..Math.Min(40, page.Length)]}");
            var (again, _, _) = await Web.Get(PageUrl, result.UserAgent!, cookie, via);  // reuse it
            Console.WriteLine((int)again);
            return 0;
        }
        Console.WriteLine("DataDome not passed in 3 attempts");
        return 1;
    }
}

Das SDK weist eine captchaUrl mit t=bv ab, bevor etwas gesendet wird, weil keine Lösung für diese Challenge-Adresse bestehen kann. Diese ValidationException ist keine ApiException, daher fängt die Schleife sie nicht ab. Lassen Sie sie durchlaufen: Sie ist das Signal, die IP-Adresse zu wechseln und von vorn zu beginnen. Eine Sperre, die CapSkip stattdessen als ERROR_PROXY_BANNED meldet, kommt als ApiException an, und die Schleife wiederholt sie mit derselben IP-Adresse. Sehen Sie diesen Fehler in der Ausgabe, wechseln Sie den Proxy, bevor Sie erneut starten. Der Leitfaden zur Proxy-Rotation behandelt rotierende Proxys, und der C#-Captcha-Detektor zeigt, wie Sie herausfinden, welches Captcha eine Seite verwendet, bevor Sie eine Methode wählen.

Im Mock-Lauf verhielt sich das Programm so:

Mock-EinstellungWas das Programm tat
Sperrseite mit Slider, jede Lösung beantwortetEine Aufgabe, dann Status 200 mit dem Cookie und bei Wiederverwendung wieder 200
Sperrseite mit GeräteprüfungDerselbe Code lief: Die captchaUrl zeigte auf /interstitial/, und das Ergebnis war dasselbe
Die ersten zwei Lösungen abgelehntZwei Versuchszeilen, dann Status 200 bei der dritten Aufgabe
Die ersten drei Lösungen abgelehntDrei Versuchszeilen, dann die Meldung, dass DataDome nicht bestanden wurde, und Exit-Code 1
Das Ergebnis nennt einen Proxy, so wie es die eigene Liste von CapSkip tutDie Anfrage mit dem Cookie ging über diesen Proxy und bekam 200
Ihr eigener Proxy, gesetzt in ProxyUrlin.php erhielt den Proxy und seinen Typ, und die Anfragen nutzten ihn
Sperrseite mit t=bvUnbehandelte ValidationException, und das Mock-CapSkip sah gar keine Anfrage

Eines hat der Proxy-Lauf gezeigt: Der Text nach dem Schema geht so an CapSkip, wie Sie ihn geschrieben haben. Ein Login, der in ProxyUrl prozentkodiert geschrieben war, etwa pa%24s für ein Passwort mit einem Dollarzeichen, kam bei in.php weiterhin kodiert an. Halten Sie den Login frei von Zeichen, die eine Kodierung brauchen, oder bauen Sie das Argument Proxy selbst aus den dekodierten Werten.

Häufige Fehler

FehlerUrsacheBeheben
ValidationExceptionEine captchaUrl, die keine DataDome-Challenge-Adresse ist, eine Sperrseite mit t=bv, eine url, die nicht http oder https ist, oder ein userAgent, der nicht Chrome für Windows ist. Die Meldung sagt, welcher dieser Fälle es istBauen Sie die Adresse aus einer frischen Sperrseite neu, wechseln Sie die IP-Adresse, falls sie gesperrt ist, und lassen Sie die Option userAgent weg
ApiException mit ERROR_WRONG_USERAGENTDer User-Agent ist Chrome für Windows, aber seine Hauptversion weicht von der des CapSkip-Browsers abSenden Sie keinen userAgent und verwenden Sie den, den das Ergebnis meldet
ApiException mit ERROR_CAPTCHA_UNSOLVABLEDie Challenge wurde nicht bestandenRufen Sie die Seite erneut ab und senden Sie ihre neue captchaUrl
ApiException mit ERROR_PROXY_BANNEDCapSkip lehnt eine Aufgabe ab, deren Sperrseite t=bv enthält, laut der API-DokumentationWechseln Sie den Proxy und rufen Sie die Seite erneut ab
ApiException mit ERROR_PROXY_CONNECT_REFUSEDCapSkip meldet diesen Fehler, wenn es sich nicht über den Proxy verbinden kann. Ich habe ihn nicht reproduziertPrüfen Sie Adresse, Login und Typ des Proxys
CapSkip.TimeoutExceptionCapSkip hat innerhalb der Wartezeit von 300 Sekunden nicht geantwortet. Das ist keine ApiException, daher fängt die Schleife sie nicht ab. Schreiben Sie den vollständigen Namen, denn System hat ebenfalls eine TimeoutExceptionPrüfen Sie, ob CapSkip läuft und löst, und führen Sie das Programm dann erneut aus
NetworkExceptionCapSkip läuft nicht, oder Host oder Port sind falschStarten Sie CapSkip und prüfen Sie die Verbindungseinstellungen
Weiterhin eine 403 mit dem CookieDie Anfrage ging von einer anderen IP-Adresse aus, nutzte einen anderen User-Agent, oder das Cookie ist abgelaufenSenden Sie jedes Mal den UserAgent und den Proxy des Ergebnisses, oder führen Sie die Schleife mit der neuen Sperrseite erneut aus

Den Löser auf einem Server betreiben

Das Programm braucht von CapSkip zwei Dinge: einen Host und einen Port. Im Local-Modus sind das 127.0.0.1 und Ihr eigener PC. Im Server-Modus lauscht CapSkip auf Ihrer Netzwerkadresse oder einer öffentlichen IP, sodass ein Programm auf einem anderen PC, einem VPS oder einer gehosteten Plattform es über dieselbe API aufrufen kann. Ändern Sie Host und Port im Konstruktor CapSkipClient, und nichts anderes ändert sich. Eine statische öffentliche IP wird für stabilen Fernzugriff empfohlen. Der Server-Modus bleibt Ihre eigene Hardware, und es fallen weiterhin keine Gebühren pro Lösung an. Er öffnet den Port allerdings für jede Adresse, fügen Sie daher eine Blockierregel der Windows Firewall für die Adressen hinzu, die ihn nicht erreichen sollen. Denken Sie daran, dass die Lösung auf diesem Rechner läuft, das Cookie also von der IP-Adresse des Servers ausgestellt wird. Wenn Sie keinen Proxy übergeben, muss auch die Anfrage, die das Cookie trägt, von diesem Server ausgehen. Mit einem Proxy übergeben Sie denselben an die Lösung und an Ihre Anfragen. Die Verbindungseinstellungen zeigen, wie Sie den Modus wechseln.

FAQ

Gibt DataDomeAsync einen Token zurück?

Nein. Er gibt das Cookie datadome zurück. Dieses Cookie senden Sie im Cookie-Header der Anfrage, die blockiert wurde, zusammen mit demselben User-Agent. Es gibt kein Formularfeld, das auszufüllen wäre.

Brauche ich einen Proxy für DataDome?

Zum Testen nicht. Eine IP-Adresse reicht für einen ersten Lauf. Laut der CapSkip-Dokumentation funktioniert das Cookie nur von der IP-Adresse, mit der es ausgestellt wurde. Sobald Sie viele Anfragen senden, planen Sie daher einen Sticky-Proxy pro Aufgabe ein und übergeben Sie denselben Proxy an die Lösung und an Ihre Anfragen.

Kann eine gehostete Plattform das gegen mein CapSkip ausführen?

Ja, über den Server-Modus. Stellen Sie CapSkip so ein, dass es auf Ihrer Netzwerkadresse oder öffentlichen IP lauscht, richten Sie den CapSkipClient-Konstruktor auf diesen Host und Port, und die Schleife funktioniert genauso, mit einer Änderung: Die Lösung läuft dann auf dem Server, geben Sie also der Lösung und Ihren Anfragen denselben Proxy. Der Solver bleibt auf Ihrer Hardware.

Die Kurzfassung

Ein DataDome CAPTCHA in C# besteht aus vier Schritten: die Sperrseite lesen, die captchaUrl bauen, DataDomeAsync aufrufen und die Anfrage mit dem Cookie sowie dem User-Agent und der IP-Adresse wiederholen, zu denen es gehört. Eine Wiederholung ist auf Ihrem eigenen Rechner kostenlos, und genau dafür ist ein Captcha-Löser da, wenn eine geschützte Website Ihnen immer wieder neue Challenges liefert. Für den Rest des SDK siehe die C#-Captcha-Solver-Seite.