So beheben Sie TLS-Fingerprinting-Blockaden in Python Requests

Wenn Ihre Header perfekt sind und die Website Sie trotzdem blockiert, geschah die Blockade, bevor Ihr erster Header überhaupt ankam. TLS-Fingerprinting identifiziert Ihren HTTP-Client anhand der Form seines TLS-Handshakes, und die Python-requests-Bibliothek erzeugt einen Handshake, den kein Browser der Welt produziert. Das lässt sich nicht beheben, indem Sie einen User-Agent-String kopieren. Sie beheben es, indem Sie den Handshake selbst wie einen Browser aussehen lassen. Dieser Beitrag zeigt Ihnen, wie Sie Ihren eigenen Fingerprint auslesen, wie Sie ihn ändern, und wie Sie dieses Problem von den beiden Problemen unterscheiden, mit denen es häufig verwechselt wird.
Was Sie brauchen
- Python 3.10 oder neuer. Die Beispiele verwenden zunächst die requests-Bibliothek, um das Problem zu zeigen, dann curl_cffi, um es zu beheben.
- Ein Terminal und etwa zehn Minuten. Der Diagnoseschritt benötigt zwei Anfragen und keinerlei Codeänderungen.
- CapSkip muss nur für den letzten Abschnitt laufen, entweder im lokalen Modus auf der Loopback-Adresse oder im Servermodus auf einer Maschine, die Ihre Worker erreichen können. Beide Modi werden unter den Verbindungseinstellungenbeschrieben, wählen Sie also einen aus, bevor Sie beginnen.
Was TLS-Fingerprinting tatsächlich ausliest
Jede TLS-Verbindung beginnt mit einer ClientHello-Nachricht. Diese Nachricht listet die von Ihnen unterstützten TLS-Versionen, die angebotenen Cipher-Suites, die gesendeten Erweiterungen, die akzeptierten elliptischen Kurven und die Reihenfolge auf, in der Sie das alles anordnen. Nichts davon ist geheim, und nichts davon lässt sich pro Anfrage konfigurieren. Es wird von der jeweiligen TLS-Bibliothek bestimmt, gegen die Ihr HTTP-Client gebaut wurde.
Hashen Sie diese Liste, und Sie erhalten einen stabilen Identifikator für die Client-Software. JA3 war die erste weit verbreitete Version dieses Hashes. JA4 ist die aktuelle, und sie sortiert die ClientHello-Erweiterungen vor dem Hashing, sodass derselbe Browser nicht bei jeder Verbindung einen anderen Fingerprint erzeugt. Cloudflare beschreibt beide als eine Möglichkeit, TLS-Clients anhand der Art, wie sie Verbindungen aufbauen, zu identifizieren, und macht den Wert für Firewall-Regeln, für Analysen und für Workers zugänglich.
Deshalb wiegt das schwerer als jeder von Ihnen gesetzte Header. OpenSSL, wie Python es ausliefert, BoringSSL, wie Chrome es ausliefert, und NSS, wie Firefox es ausliefert, senden alle unterschiedliche ClientHello-Nachrichten. Eine Anfrage, die vorgibt, Chrome zu sein, aber wie Python den Handshake durchführt, ist deshalb kein subtiles Indiz, das raffinierte Erkennung braucht. Es sind zwei Felder, die nicht übereinstimmen, und eine Bot-Regel kann sie in einer einzigen Zeile vergleichen.
Schritt 1: Lesen Sie Ihren eigenen Fingerprint aus, bevor Sie irgendetwas ändern
Raten Sie nicht, ob TLS-Fingerprinting Ihr Problem ist, denn Sie können es messen. Es gibt einen öffentlichen Endpoint, der den bei Ihrer Verbindung gesehenen JA3-Hash zurückspiegelt, und zwei Anfragen dagegen zu vergleichen dauert unter einer Minute.
# pip install requests
import requests
# The endpoint echoes back the handshake it received from you.
r = requests.get("https://tls.browserleaks.com/json", timeout=30)
seen = r.json()
print(seen["ja3_hash"]) # stable per TLS library, not per user agent
print(seen["ja3_text"]) # the raw cipher and extension listFühren Sie denselben Aufruf jetzt noch einmal aus, diesmal mit einem Browser-User-Agent in den Headern, und lesen Sie den Hash ein zweites Mal aus. Er wird sich nicht verändert haben. Das ist die ganze Lektion in einem Experiment: Der User-Agent steckt in einem Header, der Fingerprint steckt im Handshake, und das Ändern des einen bewirkt beim anderen nichts. Wenn Sie User-Agents rotiert haben, um an einer Blockade vorbeizukommen, ist genau das der Grund, warum es nicht funktioniert hat.
Schritt 2: Den Handshake eines echten Browsers mit curl_cffi nachbilden
Sie können den Standard-TLS-Stack von Python nicht dazu bringen, ein Chrome-ClientHello zu erzeugen, denn die Menge der Erweiterungen und ihre Reihenfolge sind fest in den Bibliotheks-Build eingebacken, statt als Einstellungen zugänglich zu sein. Was Sie tun können, ist eine andere Bibliothek zu verwenden. Das curl_cffi-Paket bindet an einen curl-Build, der bestimmte Browser-Handshakes nachbildet, und stellt eine requests-ähnliche API bereit, sodass sich der Rest Ihres Codes kaum ändert.
# One dependency, no browser and no driver involved. pip install curl_cffi --upgrade
Das Argument, das die eigentliche Arbeit erledigt, ist impersonate. Es legt fest, welchen Browser-Build der Handshake nachahmen soll, und Sie können eine bestimmte Version festlegen, wenn eine Website bei der aktuellen wählerisch geworden ist.
# pip install curl_cffi
import curl_cffi
# Same call as before, but the handshake now matches Chrome.
r = curl_cffi.get("https://tls.browserleaks.com/json", impersonate="chrome")
print(r.json()["ja3_hash"]) # a different hash from the requests run above
# Pin a version when a site rejects the current default.
r = curl_cffi.get("https://example.com/", impersonate="chrome124")
print(r.status_code)Lesen Sie die beiden Hashes nebeneinander. Wenn sie sich unterscheiden, ist die Impersonation aktiv, und das ist die einzige Bestätigung, die zählt. Das Projekt dokumentiert die unterstützten Ziele, zu denen neben Chrome auch Safari und die iOS-Safari-Version gehören. Handgeschriebene JA3-Strings werden ebenfalls akzeptiert, auch wenn ein gepflegtes Preset in fast jedem Fall besser abschneidet, weil Presets aktualisiert werden, wenn Browser sich ändern.
Wechseln Sie zu einer Session, sobald es funktioniert. Sie wollen Verbindungswiederverwendung und ein Cookie-Jar, aus denselben Gründen, aus denen Sie sie schon bei requests wollten.
# pip install curl_cffi
import curl_cffi
# One session, one handshake profile, cookies carried across calls.
s = curl_cffi.Session(impersonate="chrome")
s.get("https://example.com/login")
r = s.get("https://example.com/dashboard")
print(r.status_code, len(s.cookies))Schritt 3: Den Rest des Clients konsistent halten
Ein Browser-Handshake an einer offensichtlich skriptgesteuerten Anfrage ist selbst eine Art Widerspruch, und ein Signal zu reparieren, während die anderen laut bleiben, ist der häufigste Weg, wie das schiefgeht. Drei Dinge müssen zu dem gewählten Profil passen.
Ihr User-Agent muss dieselbe Browserfamilie und ungefähr dieselbe Version nennen wie das Impersonationsziel. Firefox vorzugeben, während der Handshake wie Chrome aussieht, ist schlimmer, als gar nichts vorzugeben, weil es aus einem schwachen Signal ein eindeutiges macht.
Ihre Header-Reihenfolge spielt eine Rolle, und ebenso Ihr HTTP/2-Verhalten. Browser senden eine stabile Header-Reihenfolge, einen stabilen Satz an HTTP/2-Settings-Frames und eine stabile Pseudo-Header-Reihenfolge, und all das lässt sich genauso sauber fingerprinten wie die TLS-Schicht. Header in der Reihenfolge zu senden, in der Ihr Dictionary sie zufällig durchläuft, macht die gerade geleistete Arbeit wieder zunichte. Das ist ein weiteres Argument für ein gepflegtes Impersonation-Preset, das die HTTP/2-Schicht für Sie übernimmt, statt sie den Standardeinstellungen Ihres Clients zu überlassen.
Ihre IP muss zum Verkehr passen, den Sie senden. Eine Rechenzentrumsadresse, die browserähnliche Anfragen mit Maschinengeschwindigkeit sendet, ist ein eigenes Signal, das kein Handshake repariert. Eine ausführliche Übersicht der Optionen finden Sie im Beitrag zu CAPTCHA-Proxy-Rotation, der beschreibt, wo Rotation in einem Worker-Pool ihren Platz hat.
Die Signale nach Priorität ordnen, damit Sie sie in der richtigen Reihenfolge beheben
| Signal | Wo es ausgelesen wird | Können Sie es ändern |
|---|---|---|
| TLS-ClientHello, gehasht als JA3 oder JA4 | Bevor überhaupt HTTP-Daten gesendet werden | Ja, durch den Austausch der TLS-Bibliothek |
| HTTP/2-Settings und Pseudo-Header-Reihenfolge | Erste Frames der Verbindung | Ja, und ein gutes Preset erledigt das für Sie |
| Header-Namen, -Werte und -Reihenfolge | Die Anfrage selbst | Ja, und dabei passieren am leichtesten Fehler |
| IP-Reputation und Adresstyp | Die Verbindungsquelle | Nur, indem Sie ändern, von wo aus Sie sich verbinden |
| Browser-Laufzeitsignale wie Canvas und WebGL | Innerhalb einer echten Seite, nachdem JavaScript ausgeführt wurde | Nicht zutreffend, wenn Sie keinen Browser einsetzen |
| Anfragerate und Navigationsmuster | Über viele Anfragen hinweg | Ja, indem Sie langsamer werden und die aufgerufenen Pfade variieren |
Arbeiten Sie diese Tabelle von oben nach unten ab, statt quer durch sie zu springen. TLS-Fingerprinting geschieht am frühesten in der Verbindung, deshalb ist es für einen Verteidiger die günstigste Stelle für eine Entscheidung, und es ist die erste Stelle, an der Ihre Lösung ansetzen muss.
Was all das nicht behebt
Ein korrekter Handshake ist keine Eintrittskarte. Er nimmt nur einen Grund weg, Ihnen zu misstrauen, was bedeutet, dass die Website nun den Rest Ihres Traffics bewertet, statt Sie schon an der Tür abzuweisen. Viele Websites zeigen Ihnen nach der Korrektur des Fingerprints trotzdem noch eine Challenge, und das ist das zu erwartende Ergebnis, kein Zeichen dafür, dass die Arbeit gescheitert ist.
Das ist die ehrliche Grenze, und man sollte sie klar benennen. CapSkip ändert Ihren TLS-Fingerprint nicht und ist keine Tarnschicht. Es löst das Captcha, das Ihnen vorgesetzt wurde, also den Teil dieses Problems, an dessen Ende ein Token steht. Steht ein Turnstile-Widget oder eine Challenge-Seite zwischen Ihnen und dem Antwortkörper, erzeugt der Solver das Token, und Ihr vorhandener Client sendet es ab.
# pip install capskip
from capskip import CapSkip
solver = CapSkip(host="127.0.0.1", port=8080)
# The challenge you were served, solved on your own machine.
result = solver.turnstile(
sitekey="YOUR_SITEKEY",
url="https://example.com/protected",
)
token = result["code"]
agent = result["userAgent"] # send this exact user agent with the tokenDer zurückgegebene User-Agent ist keine Dekoration. Turnstile bindet das Token an die Browser-Identität, die es erzeugt hat, sodass das Einreichen eines völlig gültigen Tokens unter einem anderen User-Agent eine zuverlässige Methode ist, es abgelehnt zu bekommen. Senden Sie beide zusammen, und die Übermittlung hält stand. Die Seite zum Cloudflare-Turnstile-Löser behandelt den Fall des Widgets und den Fall der Challenge-Seite getrennt, weil sie unterschiedliche Eingaben benötigen.
Den Löser stattdessen auf einem Server betreiben
Fingerprint-Arbeit taucht meist in einem Worker-Pool auf statt in einem einzelnen Skript, und ein Pool teilt sich keine Loopback-Adresse. Die Verbindungseinstellungen decken beide Fälle ab:
| Modus | Lauscht auf | Sinnvoll, wenn |
|---|---|---|
| Lokal | 127.0.0.1, nur dieses Gerät | Ihr Scraper und der Löser laufen auf einer Maschine |
| Server | Ihre Netzwerkadresse oder öffentliche IP | Worker, ein VPS oder eine gehostete Plattform rufen über die API an |
Richten Sie den SDK-Host auf die Solver-Maschine aus, und sonst ändert sich nichts an Ihrem Code, sodass eine ganze Flotte sich eine einzige Instanz teilen kann. Eine statische öffentliche IP wird empfohlen, wenn die Aufrufer außerhalb Ihres eigenen Netzwerks sitzen. Die Details finden Sie unter den Verbindungseinstellungen, und der Servermodus bleibt weiterhin Ihre eigene Hardware und bleibt ohne Nutzungsgebühren: Er verändert nur, wo der Solver läuft, niemals, wem er gehört.
FAQ
Kann ich den TLS-Fingerprint der requests-Bibliothek selbst ändern?
Nicht sinnvoll. Sie können die Cipher-Suites über den zugrunde liegenden SSL-Kontext neu anordnen und damit den Hash verändern, aber Sie können auf diesem Weg kein Browser-ClientHello nachbilden, weil die Erweiterungsmenge und ihre Reihenfolge aus dem Bibliotheks-Build stammen und nicht aus der Konfiguration. Am Ende erhalten Sie einen Fingerprint, der zu gar nichts passt, was eine schlechtere Position ist, als zu Python zu passen. Tauschen Sie stattdessen die Bibliothek aus.
Verschwindet das Problem, wenn ich einen echten Browser steuere?
Für die TLS-Schicht ja, denn ein echtes Chrome sendet einen echten Chrome-Handshake. Es kostet Sie aber auch Hunderte Megabyte pro Worker und eine deutlich langsamere Anfrage, ist also eine schwergewichtige Antwort auf ein eng begrenztes Problem. Greifen Sie zu einem Browser, wenn Sie die Seite wirklich JavaScript ausführen lassen müssen, und zu einem impersonierenden HTTP-Client, wenn Sie nur den Antwortkörper brauchen.
Woran erkenne ich, dass die Blockade am Fingerprinting lag und nicht an einem Rate-Limit?
Verlangsamen Sie deutlich und versuchen Sie es erneut vom selben Client aus. Ein Rate-Limit entspannt sich, wenn Sie warten, sagt Ihnen meist, wie lange Sie warten müssen, und funktioniert danach wieder sauber. Eine TLS-Fingerprinting-Blockade kümmert sich überhaupt nicht um Timing, sodass die erste Anfrage des Tages genauso scheitert wie die hundertste. Scheitert ein kalter Client schon bei seiner allerersten Anfrage, ist Warten nicht Ihre Lösung.
Ändert CapSkip meinen JA3- oder JA4-Fingerprint?
Nein, und das sollte auch so bleiben. CapSkip ist ein Solver: Sie übergeben ihm eine Challenge, und er gibt Ihnen ein Token zurück, auf Hardware, die Ihnen gehört, und ohne Gebühr pro Lösung. Ihr Fingerprint gehört zu dem Client, der die Anfrage stellt, dieser Teil bleibt also Ihre Aufgabe. Die beiden Teile passen in der Praxis gut zusammen, denn ein browserförmiger Client bekommt eine lösbare Challenge vorgesetzt statt einer glatten Ablehnung.
Die kürzeste Fassung
Lesen Sie zuerst Ihren Fingerprint aus, denn das kostet nur eine Anfrage und klärt die Frage. Bewegt sich der Hash nicht, während Sie User-Agents rotieren, ist der Handshake das Problem, und kein Header wird das beheben. Wechseln Sie zu einem impersonierenden Client, halten Sie User-Agent, Header-Reihenfolge und HTTP/2-Profil konsistent mit dem, was Sie imitieren, und prüfen Sie dann Ihre IP. Erscheint anschließend eine Challenge, ist das Fortschritt statt Fehlschlag, und ein lokaler Captcha-Löser ist das Teil, das daraus ein Token macht. Hinweise, wie Sie einen in einen Worker-Pool einbinden, finden Sie unter dem Captcha-Löser für Web Scraping. Sobald es an den Code selbst geht: die Python-Captcha-Löser-Seite trägt die SDK-Details, die Sie als Nächstes brauchen werden.
