Web Scraping with JavaScript and Node.js: A Tested Guide

Rewritten for Node.js 22: built-in fetch and ES modules, current Cheerio, jsdom, Axios, Playwright and Puppeteer, and a web scraping API example. The request and SuperAgent sections and an untested Selenium Google search example were removed. Every snippet was run on 2026-10-26 and its output is shown; the code is in the evidence packet.
Node.js is a good fit for web scraping: fetch is built in, the parsers are fast, and the two main headless browser libraries, Playwright and Puppeteer, are JavaScript-first. This guide goes from a one-line request to a page that only renders in a browser, then shows the same job through a web scraping API.
Setup
Use Node.js 22.13 or newer; the examples ran on 22.20. Node 18 added the built-in fetch, but the current Puppeteer and jsdom releases need Node 22.12 and 22.13 respectively. Create a project, switch it to ES modules so you can use import and top-level await, and install what you need:
npm init -y
npm pkg set type=module
npm install cheerio jsdom axios playwright puppeteer
npx playwright install chromium
Save each example as a .js file and run it with node file.js. The targets are public practice sites built for scraping: books.toscrape.com and quotes.toscrape.com.
The smallest scraper: fetch
fetch downloads the page; a regular expression is enough to pull one value out of it:
const response = await fetch('https://example.com/');
const html = await response.text();
const title = html.match(/<title>(.*?)<\/title>/)[1];
console.log(response.status, title);
200 Example Domain
Regular expressions break as soon as the markup changes, so for anything beyond a single value, use a parser.
Parse HTML with Cheerio
Cheerio parses HTML and gives you jQuery-style selectors. This collects every book on the first page with its price:
import * as cheerio from 'cheerio';
const response = await fetch('https://books.toscrape.com/');
const $ = cheerio.load(await response.text());
const books = $('article.product_pod').map((_, el) => ({
title: $(el).find('h3 a').attr('title'),
price: $(el).find('p.price_color').text(),
})).get();
console.log(books.length, 'books');
console.log(books.slice(0, 3));
20 books
[
{ title: 'A Light in the Attic', price: '£51.77' },
{ title: 'Tipping the Velvet', price: '£53.74' },
{ title: 'Soumission', price: '£50.10' }
]
Cheerio does not run JavaScript or build a browser DOM, which is why it is fast. For more parsers, see HTML parsing libraries for JavaScript.
jsdom: the browser DOM API
jsdom implements the DOM in Node.js, so you can use querySelectorAll and other browser APIs you already know:
import { JSDOM } from 'jsdom';
const response = await fetch('https://books.toscrape.com/');
const { document } = new JSDOM(await response.text()).window;
const prices = [...document.querySelectorAll('p.price_color')].map((p) => p.textContent);
console.log(prices.length, 'prices, first three:', prices.slice(0, 3).join(' '));
20 prices, first three: £51.77 £53.74 £50.10
jsdom is slower and heavier than Cheerio; pick it when you need the DOM API itself.
Axios instead of fetch
Axios is still the most common HTTP client in older codebases. It parses JSON for you, supports timeouts and interceptors, and works the same way with a parser:
import axios from 'axios';
import * as cheerio from 'cheerio';
const { status, data } = await axios.get('https://books.toscrape.com/catalogue/page-2.html', { timeout: 10000 });
const $ = cheerio.load(data);
console.log(status, $('article.product_pod').length, 'books on page 2,', $('li.current').text().trim());
200 20 books on page 2, Page 2 of 50
To send Axios through a proxy, see how to use proxies with Axios. For following "next" links across pages, see pagination techniques.
Pages that need JavaScript
Many sites send an almost empty HTML page and build the content in the browser. quotes.toscrape.com/js/ is one of them, and a plain request finds nothing to parse:
import * as cheerio from 'cheerio';
// quotes.toscrape.com/js/ builds its quotes with JavaScript after the page loads
const response = await fetch('https://quotes.toscrape.com/js/');
const $ = cheerio.load(await response.text());
console.log(response.status, $('div.quote').length, 'quotes in the raw HTML');
200 0 quotes in the raw HTML
For these pages you need a browser that runs the page's JavaScript: Playwright or Puppeteer, or an API that runs the browser for you.
Playwright
Playwright drives Chromium, Firefox and WebKit. Actions such as click() wait for their element automatically; reading a list of elements does not, so wait for the first one before you read them:
import { chromium } from 'playwright';
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://quotes.toscrape.com/js/');
await page.locator('div.quote').first().waitFor(); // reading many elements does not wait, so wait for the first one
const quotes = await page.locator('div.quote').evaluateAll((nodes) =>
nodes.map((q) => ({ text: q.querySelector('.text').textContent, author: q.querySelector('.author').textContent })),
);
console.log(quotes.length, 'quotes');
console.log(quotes[0]);
await browser.close();
10 quotes
{
text: '“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”',
author: 'Albert Einstein'
}
Puppeteer
Puppeteer is the Chrome team's library. It downloads its own Chrome on install, and you wait for elements explicitly:
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://quotes.toscrape.com/js/');
await page.waitForSelector('div.quote');
const authors = await page.$$eval('div.quote .author', (nodes) => nodes.map((n) => n.textContent));
console.log(authors.length, 'quotes, first authors:', authors.slice(0, 3).join(', '));
await browser.close();
10 quotes, first authors: Albert Einstein, J.K. Rowling, Albert Einstein
Both work. Playwright has the broader browser support and auto-waits on actions; Puppeteer is a little lighter if you only need Chrome. Our Playwright vs Puppeteer comparison goes into detail. Running many browsers at once is where the cost shows up, as scraping with Puppeteer Cluster describes.
ScrapingAnt API
Web scraping API from JavaScript
Skip the browser install: one fetch to the ScrapingAnt API returns the rendered page, loaded in headless Chrome behind rotating proxies, and you parse it with Cheerio exactly as above. Pick any proxy_country from the documented list at no extra charge; requests the API answers with a non-200 status are not charged, and there is a free plan to start.
import * as cheerio from 'cheerio';
const params = new URLSearchParams({
url: 'https://quotes.toscrape.com/js/',
'x-api-key': process.env.SCRAPINGANT_API_KEY,
}); // browser=true is the default: the page renders in headless Chrome
const response = await fetch(`https://api.scrapingant.com/v2/general?${params}`);
const $ = cheerio.load(await response.text());
console.log(response.status, response.headers.get('ant-page-status-code'), $('div.quote').length, 'quotes');
console.log($('div.quote .author').first().text(), '|', $('div.quote .text').first().text().slice(0, 50));
200 200 10 quotes
Albert Einstein | “The world as we have created it is a process of o
The second number is Ant-Page-Status-Code, the target's own status. Run on 2026-10-26 with Node.js 22.20 and cheerio 1.2.0. Set SCRAPINGANT_API_KEY to the key from your dashboard before running it. There is also an npm client if you prefer one.
Which tool to use
| The page | Use |
|---|---|
| Returns the data in its HTML | fetch (or Axios) and Cheerio |
| Needs browser DOM APIs in Node | jsdom |
| Builds content with JavaScript, a few pages | Playwright or Puppeteer |
| Builds content with JavaScript at volume, or blocks your IP | A web scraping API, or a browser plus rotating proxies |
When a site starts refusing requests, check how to scrape without getting blocked and, for headless browsers, detection avoidance libraries for JavaScript. If you are still choosing a language, see JavaScript vs Python for web scraping.
Examples tested on 2026-10-26 with Node.js 22.20.0, cheerio 1.2.0, jsdom 29.1.1, axios 1.20.0, playwright 1.64.1 and puppeteer 25.16.0. Code: scrapingant-examples/examples/web-scraping-javascript.
This article was drafted with AI assistance from a tested evidence packet and reviewed by the named author, who is responsible for the code, measurements and corrections.