---
title: "JavaScript Web Scraping Libraries: What to Use in 2026"
description: "Every JavaScript web scraping library compared: Cheerio, Puppeteer, Playwright, Crawlee. Verified 2026 versions, what each breaks on, and which are now dead."
url: https://webscraping.ai/blog/javascript-web-scraping-libraries
markdown_index: https://webscraping.ai/llms.txt
---
# JavaScript Web Scraping Libraries: What to Use in 2026

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-stealth` is 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](https://www.npmjs.com/package/patchright) or Camoufox instead.
- **`node-fetch` has not shipped a release since 2023-07-25.** You don't need it — Node 18+ has a global `fetch()` 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:** `curl` the 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:

1. **Data is in the HTML source** → `fetch` + Cheerio. Fastest, ~50 MB of RAM, no browser.
2. **Data comes from an XHR/JSON call** → open DevTools, find the API endpoint, `fetch` it directly. No parser needed at all.
3. **Data is rendered client-side** → Playwright. Puppeteer if you're Chrome-only and already invested.
4. **You need thousands of pages with retries, queues and proxy rotation** → Crawlee, which wraps either of the above.
5. **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:

```bash
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`:

```javascript
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](https://webscraping.ai/blog/user-agent-rotation-for-web-scraping).

For selector syntax beyond CSS, the [XPath cheat sheet](https://webscraping.ai/blog/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:

```javascript
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:

```javascript
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](https://webscraping.ai/blog/puppeteer-web-scraping) and the [Playwright web scraping guide](https://webscraping.ai/blog/playwright-web-scraping).

## 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](https://webscraping.ai/blog/python-selenium) and the [Python web scraping libraries](https://webscraping.ai/blog/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.

```javascript
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-js` 0.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](https://webscraping.ai/blog/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](https://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:

```javascript
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:

```javascript
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](https://webscraping.ai/ai-web-scraping). **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](https://webscraping.ai/docs) if you'd rather not build query strings, plus an [MCP server](https://webscraping.ai/integrations/mcp-server) and an [n8n node](https://webscraping.ai/integrations/n8n) for agent and no-code workflows.

Teams typically reach for this on [price monitoring](https://webscraping.ai/use-cases/price-monitoring), [job listing aggregation](https://webscraping.ai/use-cases/job-listing-aggregation), and [RAG knowledge base](https://webscraping.ai/use-cases/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](https://webscraping.ai/blog/is-web-scraping-legal) for the detail.

* * *

Working through a first scraper rather than choosing between libraries? Start with [web scraping with JavaScript](https://webscraping.ai/blog/web-scraping-with-javascript) for the end-to-end walkthrough, then come back here when you outgrow it. Or [get 2,000 free credits](https://webscraping.ai/) and let the request layer be someone else's problem.
