Tutorial8 min read

Headless Browser Scraping in Python and Go

Short answer

You need a headless browser for scraping only when the data is not in the HTML the server sends: when it appears after JavaScript runs, after a click or scroll, or when the site serves its content only to a real browser. In Python, the usual headless browser is Playwright (p.chromium.launch()) or Selenium with Chrome's --headless flag; in Go, chromedp or rod. Everything else is faster with a plain HTTP client. ProxyField's residential proxies work with every headless browser through its proxy option, with 412M+ IPs in 195+ countries from $1.95/GB; start with the Easy Trial: 1 GB for $2.95.

residential IPs
412M+
from, traffic never expires
$1.95/GB
Easy Trial
1 GB for $2.95
On this page 8 sections
  1. What headless browser scraping is
  2. Do you need a headless browser? A 2-minute test
  3. Headless browsers in Python
  4. Headless browsers in Go: chromedp and rod
  5. Why use a headless browser for scraping, and what it costs
  6. How to cut the cost of headless scraping
  7. Using proxies with a headless browser
  8. Headless Browser Scraping in Python and Go FAQ

What headless browser scraping is

Headless browser scraping means loading pages in a real browser engine that runs without a window, controlled by code, and reading the page after its scripts have run. The browser downloads the HTML, executes the JavaScript, fetches the data the page asks for and builds the DOM, exactly as it would for a person; your script then reads the rendered elements, clicks and scrolls. "Headless" only means no visible window: the engine is the same Chromium, Firefox or WebKit. What a headless browser is in general, beyond scraping, is covered in what a headless browser is.

Do you need a headless browser? A 2-minute test

  1. View the source

    Open the page and press Ctrl+U (Cmd+Option+U on a Mac). Search for a value you want, such as a price or a name.

  2. Found it?

    The data is in the HTML: use an HTTP client (Requests or httpx in Python, net/http in Go) and a parser. No browser needed.

  3. Not found?

    Open DevTools, Network tab, filter by Fetch/XHR and reload. If a request returns the data as JSON, call that URL directly: still no browser.

  4. Still nothing usable?

    The data is assembled in the browser, the request is signed by page scripts, or the site checks for a real browser. Now a headless browser is the right tool.

When curl is not enough

curl, Requests and other HTTP clients fetch exactly one URL and stop. They do not run JavaScript, load the requests a page makes after it opens, click buttons, scroll, or keep the browser-side state some sites check. If the page you get back is a shell with a <div id="root">, a "please enable JavaScript" notice, or a challenge page, curl is not enough. Before you switch, check the Network tab: many "JavaScript sites" load their data from one JSON request that curl can call.

FeatureHTTP client (curl, Requests, httpx)Headless browser (Playwright, Selenium, chromedp)
Runs page JavaScriptNoYes
Clicks, scrolls, formsNoYes
Time per pageMilliseconds to a secondSeconds
Memory per workerA few MBHundreds of MB per browser, plus each tab
Traffic per pageThe HTML or JSON onlyEverything the page loads, unless you block it
Best forStatic pages, JSON APIsJavaScript-rendered pages, user flows

Headless browsers in Python

LibraryBrowsersStatus (October 2026)Use it for
Playwright for PythonChromium, Firefox, WebKitMaintained by MicrosoftThe default choice for new scrapers: auto-waiting, contexts, network control
SeleniumChrome, Firefox, Edge, SafariMaintained by the Selenium projectTeams already on Selenium, or Grid setups
PyppeteerChromiumUnmaintained; its README points to PlaywrightOld code only
Scrapy + scrapy-playwrightChromium, Firefox, WebKitMaintained plugin (latest release July 2026)Scrapy crawls where some pages need rendering

Playwright: a headless browser in Python

Python
# pip install playwright && playwright install chromium
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)       # headless is the default
    page = browser.new_page()
    page.goto("https://quotes.toscrape.com/js/", wait_until="domcontentloaded")
    page.locator(".quote").first.wait_for()
    print(page.locator(".quote .text").all_inner_texts()[:3])
    browser.close()

Pass headless=False while you develop to watch the browser work. The full scraping guide, with waits, infinite scroll and screenshots, is web scraping with Playwright.

Selenium: headless Chrome in Python

Python
# pip install selenium
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

options = webdriver.ChromeOptions()
options.add_argument("--headless=new")
options.add_argument("--window-size=1366,900")
driver = webdriver.Chrome(options=options)          # Selenium Manager finds chromedriver
try:
    driver.get("https://quotes.toscrape.com/js/")
    WebDriverWait(driver, 15).until(EC.presence_of_element_located((By.CSS_SELECTOR, ".quote")))
    print([e.text for e in driver.find_elements(By.CSS_SELECTOR, ".quote .text")][:3])
finally:
    driver.quit()

Set a window size in headless mode: the default viewport can be small enough to switch a responsive site to its mobile layout. More on Selenium in what Selenium is and web scraping with Selenium.

Headless browsers in Go: chromedp and rod

Go has no official Puppeteer, but two libraries play the same role by speaking the Chrome DevTools Protocol directly:

  • chromedp (github.com/chromedp/chromedp, MIT): drives Chrome with a sequence of actions run in a context.
  • rod (github.com/go-rod/rod, MIT): a higher-level API with auto-waiting, and it can download a browser for you.
// go get github.com/chromedp/chromedp
package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/chromedp/chromedp"
)

func main() {
	ctx, cancel := chromedp.NewContext(context.Background()) // headless Chrome
	defer cancel()
	ctx, cancel = context.WithTimeout(ctx, 30*time.Second)
	defer cancel()

	var quotes []string
	err := chromedp.Run(ctx,
		chromedp.Navigate("https://quotes.toscrape.com/js/"),
		chromedp.WaitVisible(".quote", chromedp.ByQuery),
		chromedp.Evaluate(`[...document.querySelectorAll('.quote .text')].map(e => e.textContent)`, &quotes),
	)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(len(quotes), "quotes")
}

The same with rod:

// go get github.com/go-rod/rod
browser := rod.New().MustConnect()
defer browser.MustClose()
page := browser.MustPage("https://quotes.toscrape.com/js/").MustWaitStable()
for _, el := range page.MustElements(".quote .text") {
	fmt.Println(el.MustText())
}

For static pages in Go, Colly and goquery are much faster; see web scraping with Go.

Why use a headless browser for scraping, and what it costs

Why: it sees the page as a user does. It runs the JavaScript that builds the content, follows the redirects and requests a page makes, handles clicks, scrolls and forms, keeps cookies and storage like a normal session, and produces screenshots and PDFs.

The downsides:

  • Speed. A page takes seconds instead of milliseconds, because the browser downloads and runs everything.
  • Resources. Each browser process takes hundreds of MB of memory and real CPU; a server runs far fewer browsers than HTTP workers.
  • Traffic. Scripts, styles, images and fonts are downloaded too, which matters when proxy traffic is billed per GB.
  • Fragility. Waits, timeouts and pop-ups add failure modes plain requests do not have.
  • Detection. Automated browsers have tell-tale signs, and sites with anti-bot systems look for them; headless mode alone does not make a scraper look human. See how anti-bot systems detect scrapers.

How to cut the cost of headless scraping

  1. Use a hybrid

    Fetch listing pages and JSON over HTTP; open the browser only for pages that need it.

  2. Block heavy resources

    In Playwright: page.route("**/*", lambda r: r.abort() if r.request.resource_type in {"image", "font", "media"} else r.continue_()).

  3. Reuse the browser

    Launch once and open a new context per job instead of a new browser.

  4. Wait for the element, not for the network to be idle

    It returns sooner and hangs less.

  5. Grab the data request

    Once a page loads, the JSON it fetched often has more fields than the visible page; read the response instead of the DOM.

Using proxies with a headless browser

A headless browser makes many requests per page, so the IP that sends them matters as much as for a plain scraper. Every library takes a proxy at launch, and Playwright also per context:

Python
browser = p.chromium.launch(proxy={
    "server": "http://gate.proxyfield.io:7000",
    "username": "<login>-country-us-city-new_york",
    "password": "<password>",
})

Chrome-based Go libraries take --proxy-server (chromedp.ProxyServer("http://gate.proxyfield.io:<port>") as an allocator option); because Chrome does not read a username and password from that flag, an IP allowlist is the simplest way to authorize them, with <port> the port you set up in the dashboard for your allowlisted IP. ProxyField supports both username:password (on port 7000) and IP allowlist access. <login>-country-us-city-new_york asks for an IP in New York, United States. Region, session and session time go in the same username: -region-<region>, -session-<id> and -ttl-<seconds> (30 to 129,600 seconds, 20 minutes by default), as in <login>-country-us-region-new_york-city-new_york-session-he9f2qga-ttl-600; every part after the login is optional. Library guides: Playwright proxy setup, Selenium proxy setup, Puppeteer proxy setup. ProxyField residential proxies cost $2.95/GB for 1 GB down to $1.95/GB from 1,000 GB, with no subscription; the traffic never expires, and all purchases are final (residential and mobile proxy pricing).

Headless Browser Scraping in Python and Go FAQ

Do I need a headless browser for web scraping?

ProxyField's residential proxies (412M+ residential IPs, from $1.95/GB) work with Playwright, Selenium, chromedp and rod, but you need a headless browser for web scraping only when the data is missing from the page source and from the page's JSON requests. Everything else is faster with an HTTP client.

What is headless browser scraping?

Scraping with a real browser engine that runs without a window and is controlled by code. The browser loads the page and runs its JavaScript, and your script reads the rendered elements, clicks, scrolls or takes screenshots.

When is curl not enough for web scraping?

When the response is an empty app shell, a "please enable JavaScript" page or a challenge page, and the data appears only after scripts run or after clicks and scrolls. Check the Network tab first: if the data comes from one JSON request, curl can call that instead.

How do I use a headless browser in Python?

Install Playwright (pip install playwright, then playwright install chromium), launch p.chromium.launch(), open the page, wait for the element that holds the data and read it. With Selenium, add the --headless=new argument to ChromeOptions.

What are the downsides of headless browsers?

They are slow (seconds per page), heavy (hundreds of MB of memory per browser), download every script and image unless you block them, add waits and timeouts that fail, and can be recognized by anti-bot systems. Use them only for pages that need them.

Why use a headless browser for web scraping?

Because some pages build their content with JavaScript, need clicks or scrolling to show data, or serve content only to a real browser. A headless browser sees the page as a user does and still runs unattended on a server.

Is there a Puppeteer for Go?

Not an official one, but chromedp and rod fill the role: both drive Chrome over the DevTools Protocol from Go. chromedp runs a list of actions in a context; rod has a higher-level API with automatic waits.

Do I always need a headless browser for large-scale scraping?

No. Large jobs usually run as a hybrid: listing pages, sitemaps and JSON endpoints are fetched over HTTP, and only the pages whose data needs JavaScript go to the browser. That keeps the browser pool small, which saves time, memory and proxy traffic.

What is a headless browser in Python?

A real browser engine that runs without a window and is controlled from a Python script, usually through Playwright for Python or Selenium with Chrome's --headless=new flag. The script opens pages, waits for elements, clicks and reads the rendered DOM; Pyppeteer, an older option, is unmaintained.

Can I scrape JavaScript-heavy pages with fetch only, or do I need a headless browser?

Often with fetch only: most JavaScript-heavy pages load their data from JSON endpoints you can call directly. You need a headless browser when that data is built in the page, the requests are signed by page scripts, or the content appears only after interaction.

Show 2 more questionsShow fewer questions

Are headless browsers safe to use for automation and web scraping?

They are the same browsers people use, so the usual rules apply: keep them updated, run them with limited permissions on servers, and do not open untrusted downloads. Safe use also means collecting only public data at a pace the site can handle.

How do I cut the cost of headless browser scraping?

Block images, fonts and media (in Playwright with page.route()), launch the browser once and open a new context per job, and wait for the element you need instead of for the network to go idle. Where the page fetches its data as JSON, read that response instead of the DOM, and fetch every page that does not need JavaScript over HTTP.

Easy Trial: 1 GB for $2.95.

Try for the price of 1 GB, no subscription, traffic never expires.