In my experience building web data pipelines, the moment you hit a modern page full of empty <div> tags, your plain HTTP requests start coming back with nothing useful. The content is there in the browser but not in the response, because the page executes client-side scripts before rendering anything. That forces a choice: reach for browser automation, or hand the problem to a scraping API. If you go the automation route, the decision usually comes down to Selenium vs Puppeteer scraping.
The best approach depends on scale. Always try to intercept direct data endpoints first. A DIY script makes sense for a handful of domains. A managed API wins when the infrastructure overhead starts to outweigh the extraction logic itself.
What is the best way to scrape JavaScript-heavy websites?
Intercept the background JSON endpoints (XHR/GraphQL) directly. If those are protected, use browser automation for low-volume jobs or a managed scraping API at scale.
Why Scraping JavaScript-Heavy Sites Breaks Standard Parsers
94% of modern websites rely on client-side rendering, which often means a static fetch returns an empty container instead of content. Static sites still work fine with a simple request plus a parser. Single-page applications do not.
An SPA loads an empty container first, then fills it after JavaScript executes and fetches data from background APIs. The DOM keeps updating through hydration and lazy loading. To capture that content, you need JavaScript-enabled crawling rather than a static fetch.
Start with Option Zero: Can You Avoid the Browser?
Before you launch a headless browser, open DevTools. Intercepting the background JSON directly is 10x to 100x faster and avoids all the RAM and CPU overhead of a real browser.
Here is the default workflow:
- Open Chrome DevTools (F12) and go to the Network tab.
- Filter to Fetch/XHR.
- Reload the page and trigger the UI action that loads data.
- Look for a JSON or GraphQL response.
- Right-click the request and Copy as cURL.
If you find a clean JSON endpoint without strict session tokens, skip the browser entirely.
Selenium vs Puppeteer Scraping: Choosing Your Headless Engine
Use Selenium when you need multiple languages (Python, Java, C#) or you already have an enterprise QA stack. Use Puppeteer when you want native Node.js and fast, Chrome-centric prototypes.
Selenium is a cross-browser automation framework and the default standard for browser control. Selenium Manager now handles driver setup for you. Puppeteer is a Node.js library that controls Chromium through the Chrome DevTools Protocol. It is lightweight and event-driven.
Selenium vs Puppeteer at a Glance
| Attribute | Selenium | Puppeteer |
|---|---|---|
| Developed by | Selenium Project (originally ThoughtWorks) | |
| Released | 2004 | 2017 |
| Language support | Python, Java, JavaScript, C#, Ruby | Node.js / JavaScript only |
| Browser support | Chrome, Firefox, Safari, Edge | Chromium / Chrome only |
| Protocol | W3C WebDriver | Chrome DevTools Protocol |
| Execution speed | Slower per command | Faster on Chromium |
| Best use case | Multi-language and enterprise QA | Node.js, Chrome-centric prototypes |
Minimal Selenium Snippet (Python)
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
options = Options()
options.add_argument("--headless=new")
driver = webdriver.Chrome(options=options)
driver.get("https://example.com")
element = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, "h1"))
)
print(element.text)
driver.quit()Minimal Puppeteer Snippet (Node.js)
const puppeteer = require("puppeteer");
(async () => {
const browser = await puppeteer.launch({ headless: "new" });
const page = await browser.newPage();
await page.goto("https://example.com");
await page.waitForSelector("h1");
const text = await page.$eval("h1", (el) => el.textContent);
console.log(text);
await browser.close();
})();The 2026 Third Option: Playwright
For greenfield builds in 2026, evaluate Playwright as a third option. It automates Chromium, Firefox, and WebKit from one API, ships with native auto-wait so you write fewer explicit waits, runs tests fast in parallel, and includes built-in network interception. It is worth a look when you are starting fresh and want multi-browser coverage without stitching together separate tools.
When a Scraping API Beats Browser Automation
A scraping API wins when the infrastructure, memory, and proxy work costs more of your time than the extraction logic does.
The Real Cost of DIY Browser Scraping
Each headless Chrome instance eats roughly 200MB to 500MB of RAM. Run high concurrency and you hit memory thrashing fast. You start adding Docker flags like --disable-dev-shm-usage, standing up premium proxy pools, and fixing CSS selectors every time a target site ships a redesign. Maintenance becomes a full-time burden.
The Scraping API Advantage
A scraping API removes the browser orchestration and abstracts away proxy rotation, session handling, and retries. You send a URL and get back extracted data. Clean, structured JSON is far better suited to AI agents and a SaaS database than raw HTML.
This is exactly where Olostep fits. Olostep is a Web Data API with two endpoints built for this workload: /scrapes turns any single URL into clean data, and /batches handles high-volume jobs. Full JavaScript rendering is the norm on every request, so you capture JS-rendered content by default. Requests run through premium residential IPs with proxy rotation to improve reliability, and you get output as clean Markdown or structured JSON.
Here is a minimal /scrapes request specifying Markdown and JSON output:
{
"url": "https://example.com",
"formats": ["markdown", "json"]
}And an illustrative clean response:
{
"markdown": "# Example Domain\n\nThis domain is for use in illustrative examples.",
"json": {
"title": "Example Domain",
"heading": "Example Domain"
}
}At scale, the throughput is the differentiator. Batch Executions take 100 to 100k URLs and return content in 5 to 7 minutes, and the system scales to around 1 million requests in roughly 15 minutes with multiple threads. Supported output formats are Markdown, HTML, JSON, and PDF.
Beating Anti-Bot Systems: The Hidden Tax of Scraping
Default headless browsers leak automation signals, and stealth plugins are not a durable fix.
Anti-bot systems like Cloudflare, DataDome, and PerimeterX inspect several signals:
- Hardware concurrency and WebGL fingerprints
- TLS (JA3/JA4) fingerprint mismatches
- Missing plugins and the absence of realistic mouse movement
The old advice to install puppeteer-extra-plugin-stealth is outdated. Bypassing Cloudflare is a daily arms race, which is another reason managed infrastructure earns its keep.
Decision Framework: The Best Way to Scrape JavaScript Sites
Start with the cheapest tool that works, then move up the ladder only when it stops paying off.
| Approach | Best Fit | Weakness | Infra Cost | Output Type |
|---|---|---|---|---|
| Direct Endpoint / XHR | Clean, unprotected JSON APIs | Breaks when endpoints are locked down | None | Structured JSON |
| Selenium | Multi-language, enterprise QA | Slower per command | High | Rendered DOM |
| Puppeteer | Node.js Chrome-centric jobs | Chromium-only | High | Rendered DOM |
| Scraping API | Scale and low maintenance | External dependency | Low | Clean Markdown / JSON |
Rule of thumb: start with an HTTP request, then a quick script, then buy infrastructure when maintenance becomes the job.
FAQ
How do you scrape JavaScript-heavy websites?
First try to intercept the background JSON endpoints (XHR/GraphQL) the page already calls. If those are protected, use a headless browser for low-volume jobs or a managed scraping API when you need scale without maintaining browser infrastructure.
Is Selenium or Puppeteer better for scraping?
Neither is universally better in the Selenium vs Puppeteer scraping debate. Choose Selenium for multi-language support (Python, Java, C#) or an existing enterprise QA stack. Choose Puppeteer for native Node.js and fast, Chrome-centric prototypes.
When should I use a scraping API instead of writing my own scraper?
Use a scraping API when the cost of running browsers, proxies, and retries outweighs the extraction logic. It abstracts proxy rotation, session handling, and JS rendering so you send a URL and get clean structured data back.
Can I scrape a JavaScript-heavy site without a headless browser?
Often, yes. If the page loads data from a clean JSON or GraphQL endpoint without strict session tokens, you can call that endpoint directly and skip the browser, which is 10x to 100x faster.
Do anti-bot systems block default headless browsers?
Yes. Default headless browsers leak automation signals through fingerprints, TLS mismatches, and missing plugins. Stealth plugins are not a durable fix against systems like Cloudflare or DataDome.
Should AI agents consume raw HTML or structured JSON?
Structured JSON. It is cleaner to store, cheaper to parse, and far easier for an AI agent or database to consume than raw HTML. Tools like Olostep parsers return structured JSON directly.
