Taking apart the networking layers behind Scrapling: the JA3 and JA4 fingerprint mechanisms in the TLS handshake, browser profile impersonation via curl_cffi, HTTP/2 settings fingerprints, connection and cookie reuse in sessions, and how to verify that your request fingerprint truly resembles a real browser.

In episode 12 you wrestled with proxy rotation and block handling: choosing proxy types, rotating them with ProxyRotator, and using impersonate plus stealthy_headers to disguise yourself. Now we go one layer deeper: networking and TLS impersonation.
This episode dissects what actually happens when Fetcher.get sends a request — how servers distinguish your request from a real browser's, what JA3 and JA4 fingerprints are, why HTTP/2 settings also leak identity, and how curl_cffi behind Scrapling disguises all of it. You'll also learn to verify for yourself that your requests really resemble a browser.
User-Agent is no longer the only deciding factor. Modern servers do passive fingerprinting: without any JavaScript at all, they analyze the first packet the client sends when communicating. This pattern is shaped by the configuration of the HTTP library that starts the connection — and every library has a distinctive signature.
Plain Python with requests, for example, produces a handshake pattern that a real browser almost never has: a different cipher suite order, fewer TLS extensions, and odd HTTP/2 settings. Once this pattern matches an HTTP library's database, the request is flagged as a bot without even waiting for JavaScript.
There are two levels of disguise. The naive one: mimicking the User-Agent. The serious one: mimicking the entire fingerprint from the TLS level up to the HTTP/2 level — exactly what impersonate does in Scrapling.
When an HTTPS connection opens, the client sends a ClientHello — a packet declaring the TLS version, cipher suites, and supported extensions. The combination of these values is hashed into a fingerprint.
JA3 is the older method that computes a hash from the TLS version, cipher suites, and extensions in the ClientHello. For years, JA3 was the de facto standard for identifying clients. Its weakness: many clients use the same settings, making it hard to distinguish two browsers, and JA3 is easy to misuse for misidentification.
JA4 is a sharper successor. It splits the analysis into several categories — TLS, HTTP/2, and TCP — so the result is more specific. Because it separates the fingerprint per protocol, JA4 can distinguish one Chrome version from another, even catching small changes in handshake configuration.
Scrapling doesn't compute these hashes manually — it uses curl_cffi, the Python binding of curl-impersonate. That library stores complete presets for specific browsers: cipher suite order, extension choices, elliptic curves, even HTTP/2 settings, all taken from the real handshake of that browser. When you write impersonate="chrome", curl_cffi imitates that entire preset, so your request's JA4 is identical to the latest Chrome.
The impersonate value can be a browser family name or a specific version. A name without a version automatically uses the latest version available in curl_cffi:
from scrapling import Fetcher
page = Fetcher.get("https://example.com", impersonate="chrome")Commonly available names: chrome, chrome_android, edge, firefox, safari, safari_ios, even tor. For specific versions, use markers like chrome110. A list value makes Scrapling pick a profile randomly on each request — useful for spreading patterns across the same pool:
from scrapling import Fetcher
page = Fetcher.get(
"https://example.com",
impersonate=["chrome", "firefox", "edge"],
)The choice isn't about "the best one", but consistency: pick the browser that makes the most sense for your target. Targeting Indonesian users? Chrome Android and Safari iOS are reasonable choices.
A perfect TLS fingerprint is wasted if the User-Agent says the latest Chrome while the header order mentions another library. That's why stealthy_headers=True matters: Scrapling regenerates the entire request header set — including its order — to match the browser version being impersonated.
from scrapling import Fetcher
page = Fetcher.get(
"https://example.com",
impersonate="chrome",
stealthy_headers=True,
)Header order sounds trivial, but it's one of the signals modern anti-bot systems check. Browsers send headers in a certain order; requests sends them in another. Consistency between the TLS fingerprint and the headers is the hallmark of a convincing request.
After the TLS handshake completes, the game continues over HTTP/2. Browsers send a SETTINGS frame with certain parameters — header table size, max concurrent streams, and others. The combination of these values produces an HTTP/2 fingerprint that is also distinctive per client. curl_cffi manages all of this automatically, so you don't need to touch it manually.
For HTTP/3, Scrapling has the http3=True parameter using QUIC. An important note: HTTP/3 can be problematic when combined with impersonate because some browser profiles don't yet have complete QUIC presets — test it on your target.
What's more commonly used in production is connection reuse via sessions. FetcherSession maintains a single connection pool and shares cookies across all requests — exactly the behavior of one browser session:
from scrapling import FetcherSession
with FetcherSession(
impersonate="chrome",
stealthy_headers=True,
retries=3,
retry_delay=1,
) as session:
login = session.post(
"https://example.com/login",
data={"username": "user", "password": "pass"},
)
dashboard = session.get("https://example.com/dashboard")Note: behind the scenes, Fetcher also uses a temporary FetcherSession for each request. By using a session explicitly, the TCP connection can be reused for subsequent requests — reducing the cost of a new handshake every time, and this persistent live connection makes your behavior even more like a real user.
Don't take things at face value. Sites like tls.browserleaks.com/json show the fingerprint the server sees — compare it with a Scrapling request:
from scrapling import Fetcher
page = Fetcher.get("https://tls.browserleaks.com/json", impersonate="chrome")
print(page.text)The JSON output contains your request's ja3n_hash and ja4. Do a comparison experiment:
python check-fingerprint.pyTry running it with impersonate="chrome" then with impersonate="firefox". Notice how the JA4 value changes. Also compare it with a request using plain curl — the difference will be obvious, and that's why Scrapling has used impersonation since episode 3.
Info
A fingerprint is only one side. Some advanced anti-bot systems add JavaScript challenges or check full browser behavior — that's where StealthyFetcher comes in, which you'll dissect in episode 15.
You now understand the networking layers that make Scrapling requests hard to distinguish from a browser: the TLS ClientHello that produces JA3 and JA4, browser profile presets in curl_cffi, HTTP/2 settings that also leak identity, and sessions that maintain a connection pool and cookies. Most importantly, you know how to verify for yourself via browserleaks that your fingerprint truly resembles the browser you chose.
The key takeaways:
User-Agent.ClientHello; JA4 splits the analysis per protocol and is far more specific.impersonate in Scrapling imitates the entire curl_cffi preset — cipher suites, extensions, even HTTP/2 settings.stealthy_headers=True keeps headers and their order consistent with the chosen profile.FetcherSession enables connection reuse and cookie sharing, mimicking a real browser session's behavior.In episode 14 we pause from the technical side and discuss something often forgotten: web scraping security and ethics. You'll learn to read robots.txt, understand Terms of Service limits, set up polite rate limiting, protect extracted data, and secure your scraper's credentials.