Web Scraping in general9 min read  ·  Published: 05/06/2026

Web scraping with JavaScript is one of the most popular approaches among developers — but choosing between a headless browser like Puppeteer and a managed scraping API like ScrapingBot can make or break your project. In this guide, you will see both approaches side by side, with real Node.js code, so you can pick the right tool for your use case.

1. Why use JavaScript for web scraping?

JavaScript is a natural fit for web scraping, for several reasons. First, most modern websites are built with JavaScript frameworks — React, Vue, Angular — which means the content you want is often generated client-side, long after the initial HTML is served. As a result, a plain HTTP request that only fetches raw HTML will return an empty shell instead of the data you need.

Node.js brings two key advantages to scraping workflows. On one hand, its asynchronous model makes it efficient for firing many concurrent requests. On the other hand, the ecosystem around headless browsers (Puppeteer, Playwright) is mature and well-documented. However, running a browser process comes with real infrastructure costs — and that trade-off is exactly what this guide explores.

2. Approach 1 — Puppeteer and headless browsers

What is Puppeteer?

Puppeteer is a Node.js library that controls a headless Chromium browser programmatically. It can click buttons, fill forms, scroll pages, wait for dynamic content, and extract the fully rendered DOM — making it one of the most powerful JavaScript scraping tools available.

Installation

npm init -y
npm install puppeteer

Scraping a dynamic product page with Puppeteer

The example below navigates to a product page, waits for a CSS selector to appear, then extracts structured data from the live DOM. Note how waitForSelector ensures the JavaScript-rendered content is present before extraction.

const puppeteer = require('puppeteer');

async function scrapeWithPuppeteer(url) {
  const browser = await puppeteer.launch({ headless: 'new' });
  const page = await browser.newPage();

  // Rotate user agent to reduce detection risk
  await page.setUserAgent(
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
  );

  await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });

  // Wait for target element
  await page.waitForSelector('.product-title', { timeout: 5000 });

  const data = await page.evaluate(() => ({
    title:  document.querySelector('.product-title')?.innerText,
    price:  document.querySelector('.price')?.innerText,
    rating: document.querySelector('.rating')?.innerText,
  }));

  await browser.close();
  return data;
}

scrapeWithPuppeteer('https://example-shop.com/product/42')
.then(console.log)
.catch(console.error);

Puppeteer limitations to keep in mind

  • Resource-heavy: every scrape spins up a full Chromium instance (~150 MB RAM).
  • IP bans: scraping at scale from a single IP triggers rate limits and CAPTCHAs quickly.
  • Maintenance overhead: you must handle retries, proxy rotation, and stealth plugins yourself.
  • Slow startup: browser launch adds 1–3 seconds per scrape, which compounds at scale.

3. Approach 2 — ScrapingBot API with Node.js

How does a scraping API work?

Instead of running a browser on your own machine, a scraping API like ScrapingBot handles all the browser infrastructure, proxy rotation, and anti-bot mitigation on its end. Your Node.js code simply sends an HTTP request with a target URL and receives back the fully rendered HTML — in one API call.

Installation — just axios (or fetch)

npm install axios

Fetching rendered HTML via ScrapingBot

const axios = require('axios');

const API_KEY  = 'YOUR_SCRAPINGBOT_API_KEY';
const BASE_URL = 'https://api.scraping-bot.io/scrape/raw-html';

async function scrapeWithScrapingBot(url) {
  const response = await axios.get(BASE_URL, {
    params: { url },
    auth: { username: API_KEY, password: '' },
  });

  // response.data contains the fully-rendered HTML
  return response.data;
}

scrapeWithScrapingBot('https://example-shop.com/product/42')
.then(html => console.log(html.slice(0, 500)))
.catch(console.error);

Parsing the response with Cheerio

ScrapingBot returns rendered HTML, so you can parse it with Cheerio — a lightweight jQuery-like library for Node.js. Therefore, combining ScrapingBot + Cheerio gives you the full power of a headless browser, without managing one.

const axios    = require('axios');
const cheerio  = require('cheerio');

const API_KEY  = 'YOUR_SCRAPINGBOT_API_KEY';
const BASE_URL = 'https://api.scraping-bot.io/scrape/raw-html';

async function scrapeProduct(url) {
  // 1. Fetch rendered HTML via ScrapingBot
  const { data: html } = await axios.get(BASE_URL, {
    params: { url },
    auth: { username: API_KEY, password: '' },
  });

  // 2. Parse with Cheerio — same jQuery-like syntax
  const $ = cheerio.load(html);

  return {
    title:  $('.product-title').first().text().trim(),
    price:  $('.price').first().text().trim(),
    rating: $('.rating').first().text().trim(),
  };
}

scrapeProduct('https://example-shop.com/product/42')
.then(console.log)
.catch(console.error);

Running parallel requests

Because ScrapingBot manages concurrency on its infrastructure, you can fire parallel requests from Node.js without worrying about overloading a local browser pool.

// Parallel requests — ScrapingBot handles concurrency for you
const urls = [
  'https://example-shop.com/product/1',
  'https://example-shop.com/product/2',
  'https://example-shop.com/product/3',
];

const results = await Promise.all(
  urls.map(url => scrapeProduct(url))
  );
console.log(results);

4. Side-by-side comparison

CriterionPuppeteer (local)ScrapingBot API
Handles JavaScript rendering✓ Yes✓ Yes
Setup complexityMedium (browser + deps)Low (HTTP only)
RAM per request~150 MBNear zero (your side)
Proxy rotationManual (extra lib)Automatic
CAPTCHA handlingManual (stealth + solver)Automatic
Scaling to 1,000+ URLsComplex, costlySimple (Promise.all)
Code to maintainHighLow
Cost modelInfrastructure costPay-per-request
Best forInteractive flows, formsData extraction at scale

5. How to choose: a practical decision guide

The right tool depends on what you actually need to do. In practice, most scraping projects fall into one of the following categories:

  • Use Puppeteer if you need to simulate user interactions — logging in, clicking through a multi-step form, solving interactive flows, or taking screenshots. These are tasks where controlling a real browser is genuinely necessary.
  • Use ScrapingBot API if your goal is data extraction — collecting product listings, prices, job postings, real estate data, or any structured content from a page. The API handles rendering, rotation and blocking for you, so you can focus entirely on parsing the response.
  • Combine both when you need to automate a login flow with Puppeteer first, then hand off the session cookies to ScrapingBot for large-scale extraction. This hybrid approach is common in production pipelines.

Moreover, if your project involves scraping more than a few dozen pages per day, the operational cost of self-managed Puppeteer (proxies, retry logic, stealth plugins, server uptime) typically exceeds the cost of a managed API.

6. Common errors and how to fix them

Error: Target page crashed (Puppeteer)

This usually means the Chromium process ran out of memory. As a fix, reduce concurrency, add --no-sandbox to launch args on Linux, or switch to a lightweight alternative like Playwright with Firefox.

Error: 403 Forbidden or empty response

The target site is blocking your IP or detecting the headless browser fingerprint. With Puppeteer, add puppeteer-extra-plugin-stealth. With ScrapingBot, this is handled automatically — the API rotates residential proxies and mimics real browser headers on every request.

Error: Selector not found / empty data

The page content loaded after your extraction ran. In Puppeteer, increase waitForSelector timeout or use waitUntil: 'networkidle0'. With ScrapingBot, the HTML returned is already fully rendered, so however the selector is absent, the element simply does not exist on that page.

Error: Rate limit hit (429)

You are sending too many requests too fast. With Puppeteer, add a delay between requests and use a proxy pool. With ScrapingBot, rate limiting is managed at the API level — you can however throttle your Promise.all batches with a concurrency limiter like p-limit.

7. Going further

Web scraping with JavaScript offers tremendous flexibility, but choosing the right tool from the start saves weeks of debugging. For simple interactive flows, Puppeteer remains unmatched. For everything involving data extraction at scale, a scraping API eliminates the infrastructure overhead entirely and lets you ship faster.

If you are building a price monitoring tool or a real estate data pipeline, you may also find these articles useful: How to Build a Price Monitoring Tool with Python and a Scraping API and Web Scraping vs Crawling: Python & JavaScript Guide. Additionally, if you want to automate full workflows, check out How to Automate Web Scraping with n8n and ScrapingBot API.

Ready to scrape JavaScript-heavy sites without the headache? ScrapingBot handles rendering, proxies, and anti-bot measures — so your Node.js code stays clean and simple.

Try ScrapingBot for free →