The short answer: use fetch + Cheerio if the data is in the HTML source, Playwright if it isn't, and Crawlee once you need queues, retries and concurrency. Everything else in the Node scraping ecosystem is either a niche parser or, in several well-known cases, abandoned.
That last part is the reason to re-read this list. Several libraries that every "best of" article recommended in 2024 have not shipped a release in three years — including the stealth plugin most tutorials still tell you to install. Every version and date below was checked against the npm registry and GitHub on 2026-07-28.
Key Takeaways
puppeteer-extra-plugin-stealthis dead. Last npm release 2023-03-01, last repo commit 2024-07-18, 273 open issues. It is still the top recommendation in most 2024-era tutorials. Use Patchright or Camoufox instead.node-fetchhas not shipped a release since 2023-07-25. You don't need it — Node 18+ has a globalfetch()backed by undici.- Nightmare's last release was 2019-04-27. Seven years ago. Any 2026 article still listing it did not check.
- Cheerio only parses; it never fetches. It cannot execute JavaScript, so a React or Vue page returns an empty shell no matter what selector you write.
- Playwright ships roughly 10× more often than Puppeteer — 343 releases in 2026 vs 27 — which is why it tracks browser changes faster.
- Rule of thumb:
curlthe URL first. If your data is in the response, a headless browser costs you ~10× the CPU for nothing.
Which JavaScript web scraping library should I use?

Work down this list and stop at the first match:
- Data is in the HTML source →
fetch+ Cheerio. Fastest, ~50 MB of RAM, no browser. - Data comes from an XHR/JSON call → open DevTools, find the API endpoint,
fetchit directly. No parser needed at all. - Data is rendered client-side → Playwright. Puppeteer if you're Chrome-only and already invested.
- You need thousands of pages with retries, queues and proxy rotation → Crawlee, which wraps either of the above.
- You're getting blocked despite all of the above → a scraping API, so the anti-bot arms race isn't your problem.
The single biggest performance mistake in Node scraping is starting at step 3. A headless browser is a few hundred megabytes of Chrome per instance; an HTTP request is a socket.
The 2026 decision table
Versions and dates verified against the npm registry on 2026-07-28.
| Library | Version | What it's for | When it breaks | Maintained in 2026? |
fetch (built-in) | Node 18+ | HTTP requests, zero deps | No cookie jar, no retries, easy to fingerprint | ✅ Core Node |
| undici | 8.9.0 | The engine behind fetch; proxies, connection pools | Lower-level API than axios | ✅ 48 releases in 2026 |
| axios | 1.18.1 | HTTP with interceptors and timeouts | Won't run JavaScript | ✅ 2026-06-22 |
| got-scraping | 4.2.1 | HTTP with browser-like TLS/header fingerprints | Header spoofing alone rarely beats modern WAFs | ✅ 2026-02-24 |
| cheerio | 1.2.0 | jQuery-style HTML parsing | Static HTML only — no JS execution | ✅ Repo active; slow releases |
| jsdom | 30.0.0 | Full DOM + limited script execution | 10–50× slower than Cheerio; not a real browser | ✅ 2026-07-27 |
| htmlparser2 | 12.0.0 | Streaming parser, fastest option | Low-level; you handle the tree | ✅ 2026-03-20 |
| node-html-parser | 9.0.0 | Lightweight DOM-ish parsing | Smaller selector support than Cheerio | ✅ 2026-07-06 |
| linkedom | 0.18.13 | DOM built for server-side rendering | Not aimed at scraping specifically | ✅ 2026-07-07 |
| puppeteer | 25.4.0 | Chrome/Chromium automation | Chrome-family only; detectable by default | ✅ 27 releases in 2026 |
| playwright | 1.62.0 | Chromium, Firefox and WebKit automation | Heavy; ~500 MB of browsers on install | ✅ 343 releases in 2026 |
| selenium-webdriver | 4.46.0 | Cross-language, cross-browser via W3C protocol | Slowest setup; extra driver process | ✅ 2026-07-11 |
| crawlee | 3.17.0 | Full framework: queue, retries, proxies, storage | Overkill under ~100 pages | ✅ 240 releases in 2026 |
| patchright | 1.61.1 | Patched Playwright that removes automation leaks | Cat-and-mouse; no guarantees | ✅ 2026-07-16 |
| camoufox-js | 0.11.5 | Firefox build hardened against fingerprinting | Firefox only; larger download | ✅ 2026-07-27 |
Which JavaScript scraping libraries are dead in 2026?
This is where most "best JavaScript web scraping library" listicles go stale. These packages still appear in top-ranking guides and still install cleanly from npm — which is exactly why people don't notice.
| Library | Last npm release | Last repo commit | What to use instead |
| puppeteer-extra + plugin-stealth | 2023-03-01 | 2024-07-18 | Patchright, Camoufox, or a scraping API |
| playwright-extra | 2023-03-01 | 2024-07-18 | Patchright |
| node-fetch | 2023-07-25 | 2026-05-12 | Built-in fetch (Node 18+) |
| nightmare | 2019-04-27 | 2024-04-20 | Playwright |
| x-ray | 2019-07-15 | — | Crawlee |
| osmosis | 2019-03-01 | — | Crawlee |
| rebrowser-patches | 2025-05-08 | 2025-05-09 | Stalled ~14 months; prefer Patchright |
The stealth plugin deserves special attention. puppeteer-extra-plugin-stealth works by patching the specific detection signals that existed in 2022. Anti-bot vendors have shipped hundreds of releases since; the plugin has shipped none. Installing it in 2026 gives you a false sense of protection and a dependency that hasn't tracked three years of Chrome changes.
Also note jQuery, which several ranking articles still list. jQuery is a browser DOM library, not a scraping tool — on the server you want Cheerio, which is the jQuery API implemented over a parsed HTML string.
Cheerio vs Puppeteer: which one do you actually need?

They don't compete — they solve different halves of the problem. Cheerio parses HTML you already have. Puppeteer produces HTML by driving a browser.
Test which one you need in one command:
curl -s https://example.com/products | grep -c "product-item"
Non-zero means the data is server-rendered and Cheerio is enough. Zero means it's built client-side and you need a browser.
Cheerio with the built-in fetch — no axios, no node-fetch:
import * as cheerio from 'cheerio';
const res = await fetch('https://example.com/products', {
headers: { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ' +
'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36' },
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const $ = cheerio.load(await res.text());
const products = $('.product-item').map((_, el) => ({
name: $(el).find('.product-name').text().trim(),
price: parseFloat($(el).find('.price').text().replace(/[^0-9.]/g, '')),
url: new URL($(el).find('a').attr('href'), 'https://example.com').href,
})).get();
console.log(products);
Two details worth copying: res.ok is checked explicitly because fetch does not throw on a 404 or 403, and relative hrefs are resolved with new URL() rather than string concatenation. Rotating that User-Agent across requests matters more than most people expect — see user agent rotation for web scraping.
For selector syntax beyond CSS, the XPath cheat sheet covers what Cheerio's CSS selectors can't express.
Puppeteer vs Playwright in 2026

Both drive real browsers over the same underlying protocols, and the APIs are close enough that porting a script takes minutes. The differences that actually decide it:
| Puppeteer 25.4.0 | Playwright 1.62.0 | |
| Browsers | Chrome, Chromium, Firefox (partial) | Chromium, Firefox, WebKit |
| Release cadence 2026 | 27 releases | 343 releases |
| Auto-waiting | Manual waitForSelector | Built into every locator |
| Parallelism | One page at a time per browser | Browser contexts, isolated and cheap |
| Install size | Chrome only | ~500 MB for all three engines |
| Maintainer | Google (Chrome DevTools team) | Microsoft |
Choose Playwright for new projects. Auto-waiting locators alone remove the most common source of flaky scrapers — the waitForTimeout(2000) guess. Browser contexts let you run isolated sessions, each with its own cookies and proxy, inside one browser process.
Choose Puppeteer if you're Chrome-only, want a smaller install, or already have a working codebase. It is not abandoned — 27 releases in 2026 is healthy maintenance.
One migration gotcha: page.waitForTimeout() was removed from Puppeteer's Page API in v22 (2024-02-05). Scripts copied from older tutorials will throw page.waitForTimeout is not a function. Wait on a condition instead:
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.goto('https://example.com/products', { waitUntil: 'domcontentloaded' });
await page.waitForSelector('.product-item'); // not waitForTimeout
const products = await page.$$eval('.product-item', els => els.map(el => ({
name: el.querySelector('.product-name')?.textContent.trim(),
price: el.querySelector('.price')?.textContent.trim(),
})));
console.log(products);
} finally {
await browser.close(); // always, or you leak Chrome processes
}
The Playwright equivalent, with the wait implied by the locator:
import { chromium } from 'playwright';
const browser = await chromium.launch();
try {
const page = await browser.newPage();
await page.goto('https://example.com/products');
const products = await page.locator('.product-item').evaluateAll(els => els.map(el => ({
name: el.querySelector('.product-name')?.textContent.trim(),
price: el.querySelector('.price')?.textContent.trim(),
})));
console.log(products);
} finally {
await browser.close();
}
Full API coverage lives in the Puppeteer web scraping guide and the Playwright web scraping guide.
Playwright vs Selenium for scraping
Selenium is not obsolete — selenium-webdriver 4.46.0 shipped 2026-07-11 — but for Node scraping specifically, Playwright wins on nearly every axis.
| Playwright | Selenium | |
| Architecture | Direct protocol connection | W3C WebDriver via a separate driver process |
| Setup | npm i playwright downloads browsers | Driver binaries to install and version-match |
| Waiting | Auto-waiting locators | Explicit waits you write yourself |
| Language support | JS/TS, Python, Java, .NET | Nearly every language |
| Network interception | First-class | Limited (CDP only, Chrome) |
Pick Selenium when you need a language Playwright doesn't support, must test a real Safari or Edge grid, or already run Selenium Grid infrastructure. Pick Playwright for everything else. The extra driver process is Selenium's biggest practical tax: version drift between the browser and its driver is a recurring source of CI breakage that Playwright simply doesn't have.
If you're scraping from Python rather than Node, the Python Selenium guide and the Python web scraping libraries comparison cover that side.
When you need a framework: Crawlee
Crawlee (3.17.0, 240 releases in 2026) is the only mature all-in-one scraping framework in the Node ecosystem — the rough equivalent of Scrapy in Python. It gives you a persistent request queue, automatic retries with backoff, proxy rotation, concurrency limits, and a storage layer, and it runs on top of Cheerio, Puppeteer or Playwright with the same API.
import { PlaywrightCrawler } from 'crawlee';
const crawler = new PlaywrightCrawler({
maxRequestsPerCrawl: 500,
maxConcurrency: 10,
maxRequestRetries: 3,
async requestHandler({ page, request, enqueueLinks, pushData }) {
await pushData({
url: request.url,
title: await page.title(),
price: await page.locator('.price').first().textContent(),
});
await enqueueLinks({ selector: '.pagination a' });
},
failedRequestHandler({ request }) {
console.error(`Gave up on ${request.url}`);
},
});
await crawler.run(['https://example.com/products']);
Swap PlaywrightCrawler for CheerioCrawler and the same handler runs without a browser at roughly 10× the throughput. That swap is the main reason to adopt Crawlee: you can start browser-based and downgrade to HTTP once you learn the site doesn't need JavaScript.
Below ~100 pages, Crawlee is more machinery than the job needs. A for loop with a try/catch and a delay is fine.
Stealth in 2026: what still works

The honest state of things: the popular open-source stealth layer is unmaintained, and the alternatives are a moving target.
- Patchright (1.61.1, updated 2026-07-16) — a drop-in Playwright replacement that patches the automation signals Playwright leaks. Actively maintained, 0 open issues.
- Camoufox (
camoufox-js0.11.5, updated 2026-07-27) — a custom Firefox build with fingerprint spoofing at the C++ level rather than by patching JS properties. - got-scraping (4.2.1) — browser-like TLS and header ordering for HTTP-only scraping, from the Crawlee team.
None of these is a guarantee. Fingerprinting now combines TLS handshake order, HTTP/2 frame settings, canvas and WebGL rendering, and behavioural timing — a patched browser can still fail on the TLS layer before a single line of your JavaScript runs. If your scraper's uptime is a business requirement rather than a hobby, maintaining this layer yourself is a standing cost. See the headless browser guide for what actually leaks and why.
Skipping the library problem entirely
The libraries above solve parsing and automation. They don't solve proxies, browser fleets, or the fact that your stealth patches expire. WebScraping.AI handles the request side so your Node code only deals with data.
Fetch rendered HTML and parse it with the Cheerio you already know:
import * as cheerio from 'cheerio';
const params = new URLSearchParams({
api_key: process.env.WEBSCRAPING_AI_API_KEY,
url: 'https://example.com/products',
js: 'true',
wait_for: '.product-item', // wait for the selector, not a fixed timeout
proxy: 'residential',
country: 'us',
});
const res = await fetch(`https://api.webscraping.ai/html?${params}`);
const $ = cheerio.load(await res.text());
const rows = $('.product-item').map((_, el) => $(el).find('.product-name').text().trim()).get();
Or skip selectors altogether and describe the fields you want, which survives the markup changes that break CSS selectors:
const params = new URLSearchParams({
api_key: process.env.WEBSCRAPING_AI_API_KEY,
url: 'https://example.com/products/1',
'fields[name]': 'Product name',
'fields[price]': 'Numeric price, no currency symbol',
'fields[stock]': 'In stock, yes or no',
});
const data = await (await fetch(`https://api.webscraping.ai/ai/fields?${params}`)).json();
// { name: "...", price: "24.99", stock: "yes" }
Costs are a published multiplier, not a mystery: 1 credit for a datacenter request without JS, 5 with JS, 10/25 for residential, 50 for stealth, +5 for AI extraction. Failed requests are free, which matters when you're scraping sites that block a fraction of attempts. The free tier is 2,000 credits/month with no credit card; paid starts at $29/mo for 250k credits. Credits do not roll over between months.
There's a JavaScript SDK if you'd rather not build query strings, plus an MCP server and an n8n node for agent and no-code workflows.
Teams typically reach for this on price monitoring, job listing aggregation, and RAG knowledge base pipelines — jobs where a scraper breaking silently is worse than it being slow.
Frequently Asked Questions
What is the best JavaScript library for web scraping?
There isn't one, because the job splits in two. For fetching and parsing static HTML, Cheerio with the built-in fetch is the fastest and lightest choice. For JavaScript-rendered pages, Playwright. For crawling at scale with queues and retries, Crawlee. Start with Cheerio and only escalate when curl shows your data isn't in the HTML source.
Is Cheerio still maintained in 2026?
Yes. Cheerio 1.0.0 finally shipped on 2024-08-09 after years of release candidates, followed by 1.1.0 (2025-06-08) and 1.2.0 (2026-01-23). The release cadence is slow, but the repository sees regular commits and the library is stable and effectively feature-complete.
Should I still use puppeteer-extra-plugin-stealth?
No. Its last npm release was 2023-03-01 and its repository's last commit was 2024-07-18, with 273 issues open. It patches detection signals from 2022 while anti-bot vendors have shipped continuously since. Patchright and Camoufox are the maintained alternatives.
Do I need axios or node-fetch for scraping in Node?
Neither is required. Node 18 and later ship a global fetch() built on undici, which covers most scraping needs with zero dependencies. node-fetch hasn't published a release since 2023-07-25 and exists mainly for legacy code. Axios is still actively maintained and worth adding if you specifically want interceptors, automatic JSON handling, or per-request timeout config.
Is Puppeteer or Playwright faster for web scraping?
For a single page they're comparable — both drive the same browser engines. Playwright pulls ahead on real workloads because browser contexts let you run many isolated sessions in one browser process, where Puppeteer typically needs a full browser per isolated session. The larger factor is that neither is fast compared to plain HTTP: a Cheerio scrape of a static page is roughly an order of magnitude cheaper than either.
Is web scraping with JavaScript legal?
It depends on what you scrape, where you are, and what you do with the data — public data, personal data, and copyrighted content are treated very differently, and terms of service, the CFAA, and GDPR can all apply. It's not a question a library choice answers. See is web scraping legal for the detail.
Working through a first scraper rather than choosing between libraries? Start with web scraping with JavaScript for the end-to-end walkthrough, then come back here when you outgrow it. Or get 2,000 free credits and let the request layer be someone else's problem.