Quick verdict: A headless browser is a real web browser (Chrome, Firefox, Safari) running without a graphical window. Same JavaScript engine, same DOM, same network stack — you just cannot see the page. Used for testing (run E2E tests in CI), scraping (handle JavaScript-rendered sites), automation (fill forms, take screenshots, generate PDFs), and security research. Main tools in 2026: Playwright, Puppeteer, Selenium. Most scrapers should default to HTTP clients (requests, httpx) and only reach for headless when JavaScript rendering is required — headless browsers use 100-200x the memory of plain HTTP.
A normal browser launches a GUI window, renders pages, and accepts user input. A headless browser does everything except the window:
From the destination server's perspective, a request from a headless Chrome looks identical to one from regular Chrome — same User-Agent, same TLS fingerprint, same HTTP/2 settings. Modern detection systems try to distinguish them via JavaScript checks (presence of WebDriver flag, navigator properties), but the network layer is identical.
Three driving use cases:
Many modern sites are Single Page Applications — the initial HTML is mostly empty, content gets injected by JavaScript after page load. curl https://example.com returns the empty shell; a headless browser runs the JS and exposes the populated DOM.
Examples: React/Vue admin dashboards, Twitter feeds, Reddit listings, modern e-commerce sites.
End-to-end tests need to simulate real user interactions in a real browser. Running browsers headlessly lets CI servers run tests without a display:
# GitHub Actions example
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npx playwright test # runs headless by default in CIAnything that needs a real browser but is not a user-facing flow:
| Tool | Maker | Browsers | Language | Best for |
|---|---|---|---|---|
| Playwright | Microsoft | Chromium, Firefox, WebKit | Python, Node, Java, .NET | Modern default; cross-browser |
| Puppeteer | Chromium (some Firefox) | Node (community Python: pyppeteer) | Chrome-specific work | |
| Selenium | Open source | All major | Python, Java, Ruby, C#, JS | Legacy compatibility, broad browser support |
Playwright is the modern default — cleaner API, faster, more reliable cross-browser support. Puppeteer is fine if you only need Chromium. Selenium is the legacy choice with the largest ecosystem but slower and clunkier than the alternatives.
pip install playwright
python -m playwright install chromiumfrom playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.goto("https://example.com/spa-page")
page.wait_for_selector("div.content-loaded")
title = page.title()
html = page.content()
browser.close()
print(title)That launches headless Chromium, navigates to a SPA, waits for client-side content to render, then captures the title and HTML. Same code works headed by passing headless=False — useful for debugging.
Headless browsers are 100-200x heavier than HTTP clients:
| Approach | Memory per request | Pages/second per worker |
|---|---|---|
| Plain Python requests | ~2 MB | 100+ (network-bound) |
| aiohttp / httpx async | ~2 MB | 500+ (concurrent) |
| curl_cffi (TLS-impersonated requests) | ~3 MB | 100+ |
| Headless Chrome via Playwright | ~250 MB | 1-2 (CPU-bound) |
| Headless Firefox via Playwright | ~300 MB | 1-2 |
The takeaway: only use a headless browser when JavaScript rendering is REQUIRED. For static HTML, plain HTTP wins by orders of magnitude.
curlm.target.comMany scrapers reach for Playwright by default. Often the right answer is "spend 30 min in DevTools finding the real endpoint and use plain HTTP" — faster, lighter, and more resilient.
Most scraping with headless browsers needs proxies:
browser = p.chromium.launch(
headless=True,
proxy={
"server": "http://gw.spyderproxy.com:8000",
"username": "USER",
"password": "PASS",
},
)For sites with strong anti-bot, Premium Residential ($2.75/GB) is the default. For account-based workflows, LTE Mobile ($2/IP) has the lowest detection rate. Plain datacenter IPs are blocked instantly by most sites that justify a headless browser in the first place.
Modern anti-bot systems try to distinguish headless from regular browsers:
navigator.webdriver is true — biggest tell. Override with stealth plugins.For tough targets, use playwright-stealth, undetected-chromedriver (Selenium fork), or pair with an antidetect browser for maximum stealth.
Related: Headless browser in Python, Cheerio vs Puppeteer, Puppeteer vs Playwright vs Selenium.