Every scraping project reaches the same fork in the road, usually around week two. The plain HTTP script that harvested ten thousand product pages overnight starts returning 403s, or empty shells of HTML with a "checking your browser" interstitial where the data used to be. Someone on the team suggests switching to a headless browser. Someone else points out that browsers cost fifty times more to run. Both of them are right, which is exactly why the real browser scraping vs HTTP requests question deserves a proper answer instead of a reflex.
This guide is that answer. It covers what each approach actually does on the wire, how modern anti-bot systems tell them apart, what each one honestly costs at scale, and the hybrid architecture most serious operations eventually converge on. It also covers the part most tutorials skip entirely: running real browsers at scale is its own detection problem. Fifty identical Chromium instances hammering a site from one IP are just as dead as fifty Python scripts — they just die more expensively. Solving that is less about your scraping code and more about fingerprints, persistent profiles and proxy hygiene.
The short answer
If the data you want is present in the raw HTML, or exposed by a JSON endpoint you can call directly, and the site is not actively defending itself — use HTTP requests. Nothing beats them on cost, speed or simplicity, and reaching for a browser here is pure waste.
If the site renders its content with JavaScript, sits behind Cloudflare, Akamai, DataDome or PerimeterX, fingerprints its visitors, or keeps the valuable data behind a login — you need a real browser. And not just any browser: one whose fingerprint, IP address and behavior tell a single coherent story, because the anti-bot vendor is checking all three against each other.
Most mature scraping operations run both. A browser tier handles the hard 10% — solving challenges, minting session cookies, working inside logged-in areas — and a cheap HTTP tier does the easy 90% with the credentials the browser earned. The rest of this article explains how to figure out which bucket your target falls into, and how to run the browser tier without it becoming the thing that gets you blocked.
What an HTTP request actually is — and what it isn't
When your script calls requests.get() or fetch(), roughly this happens: a TCP connection opens, a TLS handshake negotiates encryption, your client sends a block of headers, and the server streams back bytes. That's the whole transaction. No JavaScript executes. No CSS is parsed. No images load. Nothing is rendered, clicked or scrolled.
This is worth stating plainly because it defines both the superpower and the ceiling of the approach. An HTTP client is not a degraded browser; it's a different animal that happens to speak the same protocol. It sees exactly what the server chooses to put in the response body — and if the server puts an empty <div id="root"></div> there and lets JavaScript fill it in later, your client sees an empty div, forever, no matter how long you wait.
Why HTTP requests are so hard to beat on cost
A single HTTP request to a typical page costs you a handful of kilobytes of bandwidth and a few milliseconds of CPU. You can hold thousands of concurrent connections open from one modest server. Python's asyncio with httpx, Go's stdlib, Node's undici — any of them will saturate a gigabit link with a few hundred megabytes of RAM.
A real browser rendering the same page pulls the HTML, then the CSS, then the JavaScript bundles, then the fonts, then the tracking pixels, then whatever XHR calls the app fires on mount. Two to five megabytes of transfer is normal for a modern e-commerce page. Then it parses, executes, lays out and paints — hundreds of milliseconds of CPU on a machine that's holding 300–600 MB of RAM per instance.
The multiplier is not subtle. On a per-page basis you're looking at something like 50–200× the bandwidth and 20–100× the memory. If you're pulling a million pages a month, that gap is the difference between a $20 VPS and a small cluster with a proxy bill attached.
The invisible tells in a plain HTTP client
Here's what surprises people who've only worked at the application layer: sites can identify your HTTP library before reading a single header, from the TLS handshake alone.
When a client opens a TLS connection it sends a ClientHello listing the cipher suites it supports, in a specific order, plus a set of extensions, plus supported elliptic curves. Chrome's list is distinctive. So is Firefox's. So is Python's requests (which uses OpenSSL through urllib3), and Go's crypto/tls, and curl. Hash that ClientHello and you get a JA3 fingerprint — a short string that identifies the TLS stack with uncomfortable precision. JA4, its successor, does the same job with better resistance to trivial evasion.
So when your script sets User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ... Chrome/131.0.0.0 Safari/537.36, an anti-bot service sees a client claiming to be Chrome 131 whose TLS handshake is unmistakably Python. That contradiction is not a soft signal. It's a hard block, and it's the single most common reason people's "but I set the User-Agent!" scripts fail.
HTTP/2 adds another layer. The protocol lets clients send SETTINGS frames, define header priority and order pseudo-headers — and every implementation does it slightly differently. Akamai publishes a well-known HTTP/2 fingerprinting technique built entirely on this. Real Chrome sends its headers in a stable, characteristic order; most HTTP libraries either sort them alphabetically or emit them in dictionary-insertion order. Getting the values right while getting the order wrong is a tell.
You can fight all of this. Libraries like curl-impersonate and tls-client replicate real browser TLS stacks. They work, and for a certain class of target they're an excellent middle path. But you're now maintaining a fingerprint-impersonation layer that goes stale every time Chrome ships a release, which is roughly every four weeks.
What a real browser gives you that a request cannot
A real browser is a full runtime. It has a JavaScript engine, a DOM, a layout engine, a compositor, a cookie jar, storage APIs, a network stack with correct TLS, and hundreds of Web APIs that pages can query. When you drive one for scraping, you inherit all of that for free.
JavaScript rendering, obviously
The headline benefit. Single-page apps built on React, Vue, Svelte or Angular ship a nearly empty HTML document and construct the page client-side. Infinite scroll, lazy-loaded images, data that arrives via GraphQL after mount, prices calculated in the browser from a pricing rules bundle — none of it exists in the initial response.
There's a caveat worth knowing: a lot of "JavaScript-rendered" content is actually just a JSON API call you could make yourself. Open DevTools, filter the Network tab to XHR/Fetch, reload, and look. Very often the page you thought needed a browser is fetching /api/v2/products?page=1 and rendering the result. If that endpoint accepts your requests, congratulations — you just saved yourself the browser tier entirely. This is the single highest-leverage twenty minutes of investigation in any scraping project, and it's the reason "does the site use JavaScript?" is the wrong question. The right one is "can I reach the data source the JavaScript is reaching?"
Challenge pages that resolve themselves
Cloudflare's managed challenge, and its equivalents from other vendors, is a proof-of-work-plus-fingerprint puzzle. The page loads a script, the script collects environmental signals and does some computation, it posts the result back, and if the server is satisfied it issues a clearance cookie. Then it redirects you to the content you asked for.
A real browser does this automatically because that's what browsers do — they run the script. An HTTP client sees the challenge HTML and stops there. You can theoretically reimplement the challenge logic, and people do, and it breaks within weeks because the challenge is deliberately a moving target. Letting a real browser solve it is the maintainable answer.
Sessions, logins, and everything behind them
Multistep authentication flows — a login form, a CSRF token, maybe an OTP, a redirect chain, three cookies set across two domains — are exhausting to replicate in raw HTTP and trivial in a browser. And once you're in, a browser with a persistent profile directory keeps you in. Cookies, localStorage, IndexedDB and service worker caches all survive between runs, so the session you established on Monday still works on Thursday instead of triggering a fresh login and a fresh risk assessment every time.
That persistence matters more than people expect. Sites weight account age and session continuity heavily. An account that logs in from a clean slate every single day looks less like a person than one that just... keeps being logged in, the way real people's browsers are.
Behavioral surface
Modern bot detection watches how you interact, not just what you request. Mouse movement paths and their acceleration curves, the microsecond gaps between keydown and keyup, scroll velocity and whether it decelerates naturally, whether focus and blur events fire in a plausible order, how long you dwell before clicking. A browser driving real input events produces plausible versions of all of this — especially if you drive it at the input layer (dispatching genuine mouse and keyboard events through the browser's own pipeline) rather than by calling element.click() from injected JavaScript, which produces an event with isTrusted: false and is a well-known giveaway.
Side by side
| Dimension | HTTP requests | Real browser |
|---|---|---|
| Bandwidth per page | 5–50 KB | 1–5 MB |
| Memory per worker | ~1–5 MB | 300–600 MB |
| Throughput, one mid-size server | Thousands/min | Tens/min |
| JavaScript-rendered content | No | Yes |
| TLS/JA3 fingerprint | Library-specific, a clear tell | Genuine browser |
| HTTP/2 frame fingerprint | Usually wrong | Correct by construction |
| Canvas / WebGL / audio APIs | Absent entirely | Present, and spoofable |
| Cloudflare-class challenges | Fails | Passes |
| Complex login flows | Painful, brittle | Natural |
| Behavioral signals | None to offer | Full range |
| Session persistence | Manual cookie jar | Real profile directory |
| Debuggability | Excellent — you see every byte | Good, with more moving parts |
| Proxy cost multiplier | 1× | 50–200× on metered residential |
| Maintenance burden | Breaks on layout/API change | Breaks on layout change |
Read the bottom rows carefully if you're using metered residential proxies. At $4–8 per gigabyte, a browser pulling 3 MB per page costs somewhere between one and two and a half cents in bandwidth alone, before compute. An HTTP request pulling 20 KB costs a fraction of a cent. Across a million pages that's a five-figure difference. This is the number that ends most "let's just use browsers for everything" conversations.
How to decide: a working checklist
Run this in order. Stop at the first stage that gets you the data.
Stage 1 — curl the URL. Literally: curl -s 'https://target/page' | grep 'the thing you want'. If the data is in the response, you're done. Build an HTTP scraper. Do not proceed.
Stage 2 — check for a JSON API. Load the page in a real browser with DevTools open, filter Network to Fetch/XHR, reload, and look through the responses. If your data arrives as JSON, try calling that endpoint directly with curl, copying the headers the browser sent. Roughly half the time it works with only a couple of headers. This tier is better than HTML scraping, not just cheaper — structured data, no parsing, no breakage when someone changes a CSS class.
Stage 3 — check for structured data in the HTML. Look for <script type="application/ld+json"> blocks, or Next.js's __NEXT_DATA__, or Nuxt's __NUXT__. Server-rendered frameworks routinely embed the entire page state as JSON in the markup. Pull that out and you have the API response without the API call.
Stage 4 — try a TLS-impersonating HTTP client. If plain requests get 403 but the content is server-rendered, the blocker is probably your fingerprint, not JavaScript. curl-impersonate or a tls-client binding may punch through at HTTP prices.
Stage 5 — reach for a real browser. JavaScript rendering is genuinely required, or there's an active challenge, or you need to be logged in. Now the question shifts from whether to use a browser to how to run many of them without looking like a farm — which is section six.
One more consideration that doesn't fit the ladder: change frequency. HTTP scrapers that parse HTML break when the markup changes, and marketing teams change markup constantly. JSON endpoints are far more stable — they're contracts other code depends on. If you're choosing between a fragile HTML scrape and a browser flow that reads rendered text, factor in who's going to fix it at 2 a.m.
Running real browsers at scale is a different problem
Here's where most guides stop and most projects fail. You decide you need browsers, you spin up fifty headless Chrome instances, you point them at your target, and within an hour every one of them is blocked. Not because browsers don't work — because fifty identical browsers don't work.
Headless is a signal, and it always was
Old headless Chrome announced itself in the User-Agent with the literal string HeadlessChrome, which was about as subtle as it sounds. That's been cleaned up, and the newer --headless=new mode is far closer to headful. But differences persist: navigator.plugins behaves differently, Notification.permission can return unusual values, some chrome.* objects that exist in a real browser are missing, WebGL renderer strings on a GPU-less server come back as SwiftShader or llvmpipe rather than a real GPU, and font enumeration on a bare Linux container looks nothing like a consumer desktop.
Open-source stealth patches exist for all of these, and detection vendors read the same open-source repositories you do. Every public patch is a known pattern. Some of them are worse than nothing — a page probing for a property that only exists because you patched it has caught you red-handed.
The automation flags
Standard automation frameworks launch Chromium with --enable-automation, which sets navigator.webdriver = true. That's a W3C WebDriver specification property whose entire job is to tell the page it's being automated. Some sites block on it directly. More sophisticated ones log it and use it to weight everything else you do.
Worse, in many setups just attaching a Chrome DevTools Protocol client changes observable browser state. A held CDP connection can be inferred. Certain CDP domains, once enabled — Runtime in particular — leave traces a page can find. Automation frameworks enable them by default because they need them for convenience features like page.evaluate().
The practical answer is to drive the browser through a narrow slice of CDP — DOM, Input, Page, Network, Target — and never enable Runtime on the driving path. You lose the ability to casually execute arbitrary JavaScript in the page, and you gain a browser that doesn't broadcast that it's being driven. This is the architecture Dual Login uses by default: the engine launches with no automation flags and no persistent CDP attachment, and automation drives it over that restricted protocol surface, so navigator.webdriver stays false and input events arrive as trusted.
Every instance needs its own identity
This is the part that separates working browser fleets from expensive failures.
A browser exposes an enormous amount of information about the machine it's running on. Screen dimensions and available screen dimensions. Device pixel ratio. Color depth. hardwareConcurrency and deviceMemory. Timezone and locale and the full Intl configuration. Installed fonts, enumerable by measuring text width. The WebGL vendor and renderer strings from WEBGL_debug_renderer_info. Canvas rendering, which varies by GPU, driver and font stack down to individual pixels. AudioContext output, which varies by audio hardware. Supported codecs. Battery status where available. The list runs to hundreds of attributes.
Collectively these form a fingerprint, and the EFF's Cover Your Tracks project demonstrated years ago how few bits it takes to make a browser globally unique. If you clone one Docker image fifty times, all fifty report the same screen size, the same zero-GPU WebGL renderer, the same font list, the same everything. From the site's perspective that's not fifty visitors — it's one machine with fifty tabs, and the correlation is trivial. Our beginner's guide to browser fingerprinting walks through the individual signals in more depth, and how to change your browser fingerprint covers what actually works to alter them versus what just adds a new tell.
What you need is not "a random fingerprint" but a coherent one. Randomizing attributes independently produces impossible machines: a macOS user agent reporting DirectX WebGL strings, an iPhone with a 2560×1440 viewport, a Windows font list on a Linux platform string. Detection vendors specifically test for those contradictions because they're cheap to check and only bots produce them. Every attribute has to be consistent with every other attribute, and the whole set has to be consistent with a device configuration that actually exists in the wild.
This is precisely the problem an antidetect browser solves. Dual Login generates internally consistent fingerprints — canvas, WebGL, audio, fonts, navigator, screen, user agent, timezone, geolocation and languages all derived together — and applies them natively in the engine rather than by injecting JavaScript into the page. That distinction matters more than it sounds. Injected spoofing means overridden functions whose toString() output betrays them, properties defined at the wrong point in the prototype chain, and Web Workers that see through the whole illusion because they run in a context your injection never reached. Native application has none of those seams.
Fingerprint and IP must agree
A browser reporting Europe/Berlin, German language preferences and a Berlin geolocation, connecting from a Vietnamese datacenter IP, is a contradiction. So is a residential IP in São Paulo paired with en-US and America/New_York. Anti-fraud systems check this alignment constantly because it's one of the highest-signal, lowest-cost tests available to them.
So whatever proxy you assign to a browser profile, the fingerprint has to follow it: timezone from the exit IP's location, language preferences that match the region, geolocation coordinates in the right city. WebRTC needs handling too — left alone, it will happily report your real local and public IP addresses through STUN, straight past your proxy, in a way that has burned an enormous number of otherwise careful setups. Our playbook on pairing antidetect browsers with residential proxies covers rotation strategy and the sticky-session question in detail.
One thing to be clear about: a VPN does not solve this. It changes your egress IP and nothing else — your fingerprint is identical across every profile, so all your sessions correlate perfectly regardless of what IP they came from. We wrote up the actual difference between an antidetect browser and a VPN because the confusion causes real losses.
Isolation between profiles
Even with distinct fingerprints and distinct IPs, browsers sharing a profile directory share cookies, localStorage, IndexedDB, cache entries and service workers. A tracking cookie set in one session shows up in the next. Cache timing attacks can reveal that two "different" browsers have visited the same resources. Sites absolutely link accounts this way — it's one of the most reliable correlation methods available, and we go through the full mechanism in how websites detect multiple accounts on the same device.
The fix is one data directory per profile, always, with nothing shared. Dual Login enforces this structurally — each profile is a separate OS process with its own --user-data-dir, so there is no shared state to leak. It also means one crashed profile doesn't take the fleet with it, which you will appreciate the first time it happens at 3 a.m.
The hybrid architecture that actually works
The operations that scale well don't choose. They build two tiers and route work between them.
Tier one: a small browser fleet for hard work
A modest number of real browser profiles — often fewer than twenty — with proper fingerprints, dedicated proxies and persistent storage. Their jobs:
- Solving challenge pages and minting clearance cookies
- Performing logins and establishing authenticated sessions
- Handling anything genuinely dependent on client-side rendering
- Periodically "warming" sessions with normal-looking browsing so they don't go stale
- Discovering and validating API endpoints when the site changes
They run slowly and deliberately. Human-plausible pacing, real navigation, occasional aimless clicking. They are not throughput machines; they're credential factories.
Tier two: a large HTTP fleet doing the volume
Hundreds or thousands of lightweight workers consuming what tier one produced: session cookies, bearer tokens, CSRF tokens, discovered endpoint URLs. They fetch fast and cheap, in parallel, with correct headers copied from what the browser tier actually sent.
The catch is that this only works if your HTTP clients don't blow the cover the browser established. If a session cookie minted by a real Chrome instance suddenly gets used by a client with a Python JA3 signature, from a different IP, with alphabetically sorted headers, some anti-bot systems will notice — and now you've burned the session and taught them something about your infrastructure. Use a TLS-impersonating client for tier two, keep the same proxy exit for a given session, and mirror the browser's header order.
Routing and feedback
Between the tiers sits the logic that makes it work: a queue where each URL is tagged with the tier that should handle it, plus a feedback path. When a tier-two request comes back with a challenge page or a 403, that URL gets re-queued to tier one, and the session it was using gets marked dead. When tier one refreshes a session, tier two picks up the new credentials.
Instrument the ratio. If tier two is succeeding on 95% of requests, your architecture is healthy. If it drops to 60%, the target has changed something and you'll know within minutes instead of discovering it in a report a week later.
Concurrency and pacing
The most common self-inflicted wound in scraping isn't fingerprinting at all — it's rate. A hundred requests per second from one /24 is a pattern no fingerprint work will hide.
Sensible defaults: cap per-IP request rate well below what feels productive, randomize inter-request delays rather than using a fixed sleep (a perfectly regular 2.0-second gap is itself a signature), respect off-peak hours in the target's timezone if you can, and pay attention to robots.txt and the site's terms. That last point isn't just ethics — sites that see well-behaved traffic patterns escalate their defenses far more slowly than ones under a hammering.
Choosing your browser tooling
A quick tour of the real options, with honest trade-offs.
Playwright and Puppeteer, unmodified
Excellent developer experience, great documentation, first-class debugging. Also detectable in their default configuration by essentially every commercial anti-bot product, for all the reasons in section six. Perfect for internal testing, staging environments, and scraping sites that aren't defending themselves. Not a stealth tool, and never claimed to be — Puppeteer's own documentation on the DevTools Protocol is upfront about what it's for.
Stealth plugins
puppeteer-extra-plugin-stealth and its descendants apply a battery of patches to the known tells. They genuinely help against basic detection. Against commercial products they're a known quantity — the plugin source is public, the patches are enumerable, and testing against them is part of any serious vendor's QA. Treat them as raising the floor, not as a solution.
Patched Chromium builds
Undetected-chromedriver and similar projects modify the browser itself rather than patching from JavaScript. Structurally the right idea: changes made in C++ before any page script runs leave no JavaScript-visible seam. The maintenance burden is the problem — Chromium ships roughly every four weeks and patches need rebasing constantly. Fine if you have the engineering capacity; a slow-motion crisis if you don't.
Antidetect browsers
Purpose-built products in this space — GoLogin, AdsPower, Multilogin, Dual Login — ship a modified Chromium with fingerprint control built into the engine, profile management, proxy assignment per profile, and an automation API. You're buying the maintenance burden as a subscription instead of a headcount.
The differences between them come down to whether spoofing is native or injected, whether the automation surface is genuinely stealthy or just wraps Puppeteer, how profiles are isolated, and what it costs to run fifty of them. We've compared GoLogin and AdsPower head to head, and looked at Multilogin alternatives that hold up under real use — worth reading if you're evaluating, because pricing models in this category vary wildly at scale.
Dual Login's specific angle for scraping work: the fingerprint is applied natively by the engine (read from a signed, encrypted config bound to the profile's data directory, not injected as JavaScript), the default launch path uses no automation flags and holds no CDP client, automation drives through a restricted protocol surface that never enables Runtime, and each profile is a genuinely separate process with its own data directory and optional proxy. Sessions persist across runs, so a login you establish today is still good next week.
Failure modes worth planning for
Some things that will happen, so you can decide now what to do about them.
Your target adds Cloudflare overnight. A pure HTTP pipeline goes to zero. If you have a browser tier, even a small one, you degrade to slower rather than stopping. Build the browser tier before you need it.
Your session cookies expire mid-run. Tier two starts failing en masse. Without feedback wiring you'll burn hours of proxy bandwidth on requests that were dead on arrival. Fail fast on the first challenge response and re-queue.
A proxy pool goes bad. Residential pools rotate; some exits land on blocklists. Track success rate per exit, not just in aggregate, or one bad subnet will drag your numbers down invisibly.
Fingerprints go stale. A profile reporting Chrome 118 in 2026 is suspicious for the same reason a 2019 phone browsing a banking site is: it's rare, and rare is memorable. Refresh the version-linked parts of your fingerprints as real Chrome moves.
The page structure changes. Inevitable. Write parsers that fail loudly with a diff of what they expected, not ones that silently return None. The worst scraping bug is the one that returns plausible empty data for three weeks.
Adjacent territory
The skills here overlap heavily with multi-account management, because both are ultimately about presenting distinct, coherent identities from one machine. If you're scraping marketplace data you're often also operating on those marketplaces, and the same infrastructure serves both — see our guides on avoiding account bans on Amazon Seller and managing multiple eBay accounts for the account-safety side of the same coin.
Small teams tend to hit this crossover fastest — one person doing research scraping, another running client accounts, both needing the same profile infrastructure. Our buyer's guide for small teams covers what to look for when the budget is real and the requirements are broad.
FAQ
Can I just add browser headers to my HTTP requests and be fine?
Sometimes, on undefended sites. But headers are the easiest thing in the world to check against your TLS handshake, and a mismatch between a Chrome user agent and a Python JA3 fingerprint is a hard signal, not a soft one. If plain headers aren't working, a TLS-impersonating client like curl-impersonate is the next step up — it fixes the handshake as well as the headers, and it's still enormously cheaper than a browser.
How much more expensive is browser scraping, really?
On bandwidth alone, typically 50–200× per page, because you're pulling the full asset set rather than one HTML document. On compute, roughly 20–100× in memory. With metered residential proxies at $4–8/GB, a browser page costs one to two and a half cents in bandwidth versus a small fraction of a cent for an HTTP request. Across a million pages that's the difference between a rounding error and a serious line item — which is exactly why the hybrid architecture exists.
Is headless detectable in 2026?
Chrome's newer headless mode is much closer to headful than the old one, but differences remain: WebGL renderer strings on GPU-less servers, font enumeration on bare containers, some missing chrome.* objects, plugin array behavior. Headful in a virtual display is safer where you can afford it. More importantly, headless-vs-headful is usually not what gets you caught — an incoherent fingerprint or a mismatched IP is, and those are problems in either mode.
Do I need an antidetect browser, or is a stealth plugin enough?
Depends entirely on the target. Sites with no active bot defense: a plugin is fine. Sites behind commercial anti-bot products, or anything where you're running more than a handful of parallel identities: you need genuine per-profile fingerprint isolation, and stealth plugins don't provide it — they make one browser less obviously automated, but every instance still looks like the same machine. That correlation is what kills fleets.
What's the single most common mistake in browser-based scraping?
Running many browsers with identical fingerprints. People invest heavily in making one instance undetectable, then clone it fifty times and can't understand why the whole pool dies together. Distinct, internally coherent fingerprints paired with distinct IPs and isolated storage matter far more than any individual stealth patch.
How do I tell whether a site needs a browser at all?
Five-minute test. Run curl against the URL and grep for your target data — if it's there, you're done. If not, open DevTools, filter Network to Fetch/XHR, reload, and look for a JSON endpoint carrying the data. Then check the HTML for <script type="application/ld+json"> or a __NEXT_DATA__ blob. Only if all three come up empty, or the site actively challenges you, do you need a browser. This check saves more money than any optimization you'll make later.
Wrapping up
The real browser scraping vs HTTP requests debate has a boring answer, which is usually a sign it's the right one: use the cheapest tool that reliably gets the data, and keep a more expensive tool ready for when the cheap one stops working. HTTP requests are staggeringly efficient and should handle the majority of your volume. Real browsers are the only thing that works against client-side rendering, active challenges and authenticated areas — and they're worth their cost precisely where they're the only option.
What determines whether a browser tier actually succeeds isn't the automation library you pick. It's whether each browser presents a coherent identity: a fingerprint that describes a device that could exist, an IP that agrees with it, storage that stays isolated from every other profile, and an automation surface that doesn't announce itself. Get those right and a small browser fleet will quietly outperform a large one that skipped them.
If you're building that tier, Dual Login handles the identity layer so you can spend your time on the scraping logic instead. Native engine-level fingerprinting rather than injected JavaScript, one isolated process and data directory per profile, per-profile proxy assignment with matched timezone and locale, persistent sessions that survive between runs, and a raw-CDP automation API that never enables Runtime and never sets the automation flags. Spin up a few profiles, point them at whatever's been blocking you, and see what happens.