The reflex, when a scraping job starts, is to reach for a headless browser. Playwright, Puppeteer, or Selenium can load anything a real browser can, so it feels like the safe default. But a headless browser is the most expensive way there is to fetch a page. It burns CPU and memory, it pulls down the entire page and every asset attached to it, and it is slow, which caps how many pages you can collect per worker. A lot of the time you are paying all of that to extract a few fields that were sitting in plain text the whole time. The real question is never “browser or not” as a habit, it is what the specific page actually requires. Here is how to decide.
What a headless browser actually buys you
A headless browser is a real browser engine with no visible window. It runs the page’s JavaScript, builds the DOM, executes the background fetch and XHR calls that modern pages make, renders client-side content, and lets you interact with the result by clicking, scrolling, and typing. It also presents a full, realistic browser fingerprint and TLS handshake because it genuinely is a browser. That is a lot of capability. The catch is that every bit of it costs something, and most pages do not need most of it.
When plain HTTP is enough, which is more often than you think
Before automating a browser, look at what the page is really made of. In a large share of cases a plain HTTP client like Python’s requests or httpx is all you need.
The first case is server-rendered HTML. If the data you want is present in the initial HTML response, view the page source or curl the URL and you will see it sitting right there, then a browser adds nothing but overhead. The second, and the one people miss most, is an underlying JSON API. Modern pages very often render from a backend endpoint the page itself calls, and if you open the network panel and watch the XHR requests, you will frequently find a clean JSON response with exactly the data you want, no HTML parsing required. Calling that endpoint directly with HTTP is faster, more stable, and easier to parse than rendering the page that consumes it. The third case is static or lightly dynamic pages where nothing important depends on client-side scripting.
This path is not just simpler, it is dramatically cheaper. One HTTP request pulls the bytes you asked for and nothing else, so it is fast, it parallelizes well, and it moves a fraction of the data through your proxies. That last point matters directly to cost: the general client patterns are in the guide to using residential proxies with Python, and staying on the HTTP path is one of the biggest levers for cutting proxy bandwidth and keeping latency down.
When you genuinely need a browser
Some pages really do require one, and forcing HTTP against them is its own waste of time. Reach for a headless browser when the data only exists after JavaScript runs, a single-page app that renders its content client-side with no accessible API behind it. Reach for one when the data is assembled through interaction, the infinite scroll and lazy-loaded pagination that only fetch more as you scroll or click. Reach for one when a login or multi-step flow depends on client-side scripting to establish a usable session. And reach for one when a site actively checks for a real browser context, executing JavaScript challenges or inspecting browser-only signals that a bare HTTP client cannot satisfy. In these cases the browser is not overhead, it is the only tool that gets the data.
The cost of the browser path, stated plainly
When you do use a browser, know what you are spending. The largest hidden cost is bandwidth. A browser loads the whole page the way a person’s would, the HTML plus every image, stylesheet, font, tracker, and third-party script, which can be many times the size of the one HTML document or JSON blob you actually wanted. Every one of those bytes travels through your proxy. The fix is to block the resource types you do not need, images, media, fonts, and analytics, so the browser renders enough to produce your data without downloading the entire page, which can cut proxy bandwidth on the browser path substantially. The second cost is speed: rendering is slow, so a browser worker collects far fewer pages per minute than an HTTP worker, and browsers are memory-hungry, so scaling a fleet of them is heavier infrastructure than firing HTTP requests. The third cost is a surprise to many teams, which is that a headless browser is not automatically stealthier. Out of the box it carries its own detectable tells, so a naive headless setup can be easier to flag than a well-formed HTTP request, not harder.
The middle path most people skip
A great many “I need a browser” situations are really “I need a request that looks real.” Before escalating to full rendering, try plain HTTP with a correct, consistent fingerprint: complete and coherent headers, proper cookie handling, and a matching TLS and HTTP/2 fingerprint, because a request that carries a browser-shaped set of fingerprints often gets through where a bare client is blocked. Pair that with the general discipline of avoiding blocks, and a lot of pages that seemed to demand a browser turn out to yield to a well-formed HTTP request at a fraction of the cost. Escalate to a browser only when the data genuinely will not come any other way.
Proxies are needed either way
Choosing HTTP over a browser does not change your need for a clean network identity, both paths run through residential IPs to look like ordinary visitors. What changes is how much you pay to do it. A browser pushes many times more data through the proxy per page, so on metered residential bandwidth the tool you pick has a direct effect on cost. The residential proxies and their per-GB pricing are the same underneath either approach, which is exactly why reaching for the lighter tool when it works is worth the discipline.
A short decision flow
Run each target through the same quick check before writing a line of automation.
- Is the data in the initial HTML? Use HTTP.
- Does the page call a JSON or XHR endpoint that returns the data? Call that endpoint with HTTP.
- Does the content only appear after JavaScript runs, with no accessible API? Use a headless browser.
- Does the data load only through scrolling or clicking? Use a headless browser.
- Are you blocked despite having the right data path? Fix the fingerprint and IP first, then consider a browser as a last resort.
In practice a quick look at view-source and the network panel answers the first two before you commit to anything, and the first two cover more sites than most people expect. When you must render, keep it lean:
# HTTP first: hit the underlying JSON endpoint the page already callsimport requests
PROXY = "http://customer-USERNAME-country-us:PASSWORD@p.shifter.io:443"proxies = {"http": PROXY, "https": PROXY}
r = requests.get("https://shop.example.com/api/products?page=1", proxies=proxies, timeout=15)r.raise_for_status()data = r.json() # structured data, no rendering, minimal bytes
# Only if rendering is truly required, run a browser through the same# proxy and block heavy resource types so it does not pull the whole page:# route.abort() on image / media / font / stylesheet requests# before reading the rendered content.The bottom line
A headless browser is the right tool for client-side rendering and genuine interaction, and the wrong tool for everything else, which is most of the web. Check whether the data is in the HTML or behind a JSON endpoint before you automate anything, prefer a well-formed HTTP request with a real fingerprint over spinning up a browser, and when you do render, block what you do not need so you are not paying to download an entire page for a handful of fields. Both paths need residential IPs, but only one of them sends a whole page through the proxy for every record. Reach for the browser last, not first, and the cheapest tool that reliably gets the data is the one to use.
That network layer is what residential proxies provide, a large pool of real, home-grade IPs that works the same whether you fetch with plain HTTP or drive a full browser. The per-GB pricing is why the lighter path pays off: you move only the data you actually need, across as many targets as the job requires.