TLS-Fingerprinting: Warum dein Python-Scraper blockiert wird
Du kopierst die Header direkt aus dem Netzwerk-Tab deines Browsers. User-Agent, Accept, Accept-Language, alles. Du fugst sie in ein Python-Skript ein, sendest die Anfrage und bekommst trotzdem einen 403. Offne dieselbe URL in einem echten Browser, und sie ladt sofort.
Wenn dir das passiert ist, hat die Blockade fast sicher nichts mit deinen Headern zu tun. Sie passiert, bevor ein einziger Header gelesen wird. Die Website liest deinen TLS-Handshake, und dein HTTP-Client sieht auf dieser Ebene uberhaupt nicht wie ein Browser aus.
Dieser Beitrag erklart, was ein TLS-Fingerprint ist, warum Standard-HTTP-Clients auffallen, wie du deinen eigenen prufst und wie du dich mit Open-Source-Werkzeugen tarnst.
Was ein TLS-Fingerprint Ist
Jede HTTPS-Verbindung beginnt mit einem TLS-Handshake. Das Allererste, was dein Client sendet, ist eine ClientHello-Nachricht. Lange bevor irgendeine URL, ein Cookie oder ein Header ausgetauscht wird, verrat diese Nachricht schon eine Menge:
- Die TLS-Version, die du unterstutzt
- Die Liste der Cipher Suites, die du anbietest, in einer bestimmten Reihenfolge
- Die Erweiterungen, die du ankundigst (SNI, ALPN, unterstutzte Gruppen, Signaturalgorithmen und mehr)
- Die elliptischen Kurven und Punktformate, die du akzeptierst
Nichts davon ist geheim, und alles ist pro Client deterministisch. Eine TLS-Bibliothek baut das ClientHello jedes Mal auf dieselbe Weise zusammen. Man kann diese Felder also hashen und bekommt eine stabile, kurze Zeichenkette dafur, “welche Art von Client das ist”. Dieser Bezeichner ist ein JA3-Fingerprint (und sein neuerer, robusterer Nachfolger, JA4).
Der wichtige Punkt: Der Fingerprint ist eine Eigenschaft der TLS-Bibliothek, nicht deines Codes. Pythons requests setzt auf urllib3 auf, das das System-OpenSSL nutzt. Chrome nutzt BoringSSL. Firefox nutzt NSS. Jedes baut das ClientHello anders auf: andere Cipher-Reihenfolge, anderer Satz an Erweiterungen, anderes Padding. Ein Server, der JA3-Fingerprints protokolliert, kann also “das ist OpenSSL-basiertes Tooling” von “das ist Chrome” unterscheiden, ohne irgendetwas zu entschlusseln.
Warum requests Auffallt
Hier das Problem in einem Satz: Echte Browser machen die uberwaltigende Mehrheit der TLS-Handshakes im offentlichen Web aus, und Pythons Standard-Stack erzeugt einen Handshake, den kein Browser erzeugt.
Ein paar konkrete Grunde, warum dein Client ungewohnlich aussieht:
- Reihenfolge der Cipher Suites. Die Standard-Praferenzliste von OpenSSL ist nicht die, die Chrome ausliefert, und die Reihenfolge ist Teil des Fingerprints.
- Erweiterungen. Browser senden Erweiterungen wie
application_settings, GREASE-Werte und spezifischesupported_versions, die ein blanker OpenSSL-Client nicht sendet. - GREASE. Chrome schleust absichtlich zufallige “GREASE”-Werte in seine Cipher- und Erweiterungslisten ein, damit Server unbekannte Werte tolerieren. Ihre Anwesenheit (und das Muster) ist selbst ein Signal.
In Summe passt der JA3 einer Standard-requests-Installation zu einem kleinen, wohlbekannten Satz von Werten, den sich Scraping-Skripte und Bots teilen. Anti-Bot-Anbieter pflegen Listen dieser Werte. Wenn dein Handshake zu einem passt, kannst du blockiert, gedrosselt oder mit einer gefalschten Seite abgespeist werden - alles, bevor deine sorgfaltig kopierten Header je geparst werden.
Deshalb bringt es auch nichts, einfach einen Browser-User-Agent einzusetzen. Du hast einen Header geandert. Der Fingerprint, der dich verraten hat, liegt eine Ebene tiefer.
Sieh dir Deinen Eigenen Fingerprint An
Du kannst genau prufen, was dein Client sendet. Mehrere offentliche Dienste geben dir deine TLS-Details zuruck. Vergleiche einen einfachen Client mit einem Browser:
import requests
r = requests.get("https://tls.peet.ws/api/all")
data = r.json()
print(data["tls"]["ja3"])
print(data["tls"]["ja3_hash"])
Fuhre das aus, offne dann https://tls.peet.ws/api/all in Chrome und sieh dir das Feld ja3_hash an. Sie werden nicht ubereinstimmen. Der Hash des Browsers gehort zu einem Wert, den Millionen echter Nutzer teilen; der requests-Hash gehort zu einem Wert, den vor allem andere Skripte teilen.
Dieser eine Vergleich ist meist der Aha-Moment. Deine Header waren die ganze Zeit in Ordnung.
Wie du dich Tarnst: curl_cffi
Die sauberste Losung in Python ist curl_cffi, ein Binding an curl-impersonate. Es liefert TLS-Profile, die das ClientHello echter Browser Byte fur Byte reproduzieren, inklusive Cipher-Reihenfolge, Erweiterungen und GREASE.
from curl_cffi import requests
# Imitiert ein aktuelles Chrome - TLS- und HTTP/2-Fingerprint inklusive
r = requests.get(
"https://example.com/api/products",
impersonate="chrome",
timeout=30,
)
print(r.status_code)
print(r.json())
Die API ist bewusst nah an requests, sodass die Migration bestehenden Codes vor allem eine Frage des Imports und des impersonate-Arguments ist. Es kummert sich auch um HTTP/2, was wichtig ist (dazu unten mehr). Du kannst ein bestimmtes Ziel wie impersonate="chrome124" oder impersonate="firefox" festlegen, wenn eine Website bei einer bestimmten Version wahlerisch ist.
Prufe den Erfolg, indem du den Echo-Dienst erneut uber curl_cffi aufrufst - der JA3-Hash sollte jetzt zu einem echten Browser passen.
Weitere Optionen
curl_cffi ist nicht der einzige Weg. Wahle nach deinem Stack und danach, wie streng das Ziel ist.
tls-client- ein Python-Wrapper um eine TLS-Implementierung in Go mit einer grossen Bibliothek an Browser-Profilen. Eine gute Alternative, wenn eincurl_cffi-Profil zu einer bestimmten Website nicht passt.- Ein echter Browser - Playwright oder Selenium steuern eine echte Browser-Engine, sodass der TLS-Fingerprint per Definition echt ist. Der Preis sind Geschwindigkeit und Speicher: Ein Browser ist weit schwerer als ein HTTP-Client, also reserviere ihn fur Seiten, die wirklich JavaScript-Rendering brauchen.
- Node.js - das Okosystem ist dunner, aber es gibt Wrapper um
curl-impersonate. Undici erlaubt das Feintuning einiger TLS-Optionen, doch einen Browser exakt zu treffen ist schwerer als in Python.
Ein praktisches Muster: Warme eine Session einmal in einem echten Browser auf, sichere die Cookies und wiederhole dann billige Anfragen mit einem HTTP-Client, der den Fingerprint trifft. So bekommst du Browser-Vertrauen, ohne bei jedem Aufruf die Browser-Kosten zu zahlen.
Der Fingerprint Ist Nur die Halbe Miete
Deinen JA3 zu treffen bringt dich durch die TLS-Ebene, aber moderne Anti-Bot-Systeme prufen mehr als eine Sache. Wenn du den Handshake reparierst und trotzdem blockiert wirst, sieh dir Folgendes an:
- Reihenfolge und Gross-/Kleinschreibung der Header. HTTP/2 sendet Header in einer bestimmten Reihenfolge und klein geschrieben. Wenn dein Client Browser-Header in der falschen Reihenfolge sendet, ist das ein eigener Fingerprint (manchmal als “HTTP/2-Fingerprint” oder Akamai-Fingerprint gehasht). Das ist ein grosser Grund, warum HTTP/2-Unterstutzung wichtig ist:
curl_cffikann es, blankesrequestsspricht gar kein HTTP/2. - TCP/IP-Signale. Manche Systeme erkennen das Betriebssystem an TCP-Fenstergrossen und -Optionen. Das lasst sich aus dem User-Space kaum falschen, und genau hier kommen Proxies mit sauberen, residentiellen IPs ins Spiel.
- Verhalten. Das Timing der Anfragen, Navigationsmuster und ob du die Sub-Ressourcen der Seite ladst, fliessen alle in ein Reputations-Scoring ein.
Der Punkt ist, in Ebenen zu denken. Eine blockierte Anfrage ist selten ein einzelnes Problem; sie ist die Summe jeder Ebene, auf der sich dein Client vom Browser eines Menschen unterscheidet.
Wann du Aufhoren Solltest zu Feilen
TLS- und HTTP/2-Fingerprints selbst zu treffen ist fur eine Handvoll Ziele gut machbar. Im grossen Massstab wird es muhsam, weil Profile driften: Browser aktualisieren sich, Anti-Bot-Anbieter passen an, und ein Profil, das letzten Monat funktionierte, fangt an zu scheitern. Eine Flotte aus Fingerprints, Proxies und Retry-Logik zu pflegen wird zu einem eigenen Projekt.
Wenn das Wettrusten beim Fingerprinting nicht das ist, was du verwalten willst, ubernimmt eine Scraping-API den gesamten Stack fur dich: einen browsergenauen TLS- und HTTP/2-Fingerprint, IP-Rotation und Retries, hinter einem einzigen HTTP-Aufruf. ScrapeUnblocker macht genau das: Du sendest eine URL, es gibt die echte Seite zuruck, und der Handshake sieht aus wie der eines echten Browsers, weil die Anfrage von echter Browser-Infrastruktur gestellt wird. Es ist ein sauberer Ruckfall fur die Seiten, bei denen sich handgetunte Fingerprints nicht mehr lohnen. Du kannst die API-Dokumentation lesen, um zu sehen, wie es in einen bestehenden Scraper passt.
Haufige Fragen
Kann ich meinen TLS-Fingerprint andern, indem ich einen Browser-User-Agent setze?
Nein. Der User-Agent ist ein HTTP-Header, der nach dem Handshake gesendet wird. Dein JA3 wird von der TLS-Bibliothek entschieden, die das ClientHello baut. Den Header zu andern beruhrt den Fingerprint nicht, der dich verraten hat.
Ist JA3 dasselbe wie JA4?
Sie dienen demselben Zweck - einen Client an seinem ClientHello zu erkennen - aber JA4 ist neuer und widerstandsfahiger gegen triviale Umgehung. Viele Systeme protokollieren jetzt beide. Werkzeuge wie curl_cffi imitieren echte Browser gut genug, um beide zufriedenzustellen.
Behebt ein Proxy das TLS-Fingerprinting? Nein. Ein Proxy andert deine IP-Adresse, nicht deinen Handshake. Du kannst eine perfekte residentielle IP haben und trotzdem blockiert werden, wenn dein JA3 “Python-Skript” schreit. Meist brauchst du beides: eine saubere IP und einen browserartigen Fingerprint.
Macht mich ein browsergenauer Fingerprint unentdeckbar? Keine einzelne Massnahme tut das. Sie entfernt ein starkes Signal. Header-Reihenfolge, HTTP/2-Frames, IP-Reputation und Verhalten zahlen weiterhin. Behandle den Fingerprint als notwendig, nicht als hinreichend.
Zum Abschluss
Wenn eine Anfrage in deinem Browser funktioniert, aber nicht in deinem Skript, widerstehe dem Drang, weiter an Headern zu drehen. Prufe die Ebene darunter. Vergleiche deinen JA3 mit einem echten Browser uber einen offentlichen Echo-Dienst, und wenn sie sich unterscheiden - das werden sie - greife zu curl_cffi oder einer echten Browser-Engine, um die Lucke zu schliessen.
Repariere zuerst den Handshake. Dann kummere dich um Header, HTTP/2, IPs und Verhalten, in dieser Reihenfolge. Und wenn die Pflege all dessen keine gute Nutzung deiner Zeit mehr ist, gibt es eine Scraping-API wie ScrapeUnblocker, die das ganze Problem abfangt, damit du wieder die Daten nutzen kannst.
ScrapeUnblocker kostenlos testen
Über 95 % Erfolgsquote · ab 0,55 € pro 1.000 Aufrufe · 500 kostenlose Anfragen bei der Registrierung.