     [Blog](https://scrapfly.io/blog)   /  [cloud-browser](https://scrapfly.io/blog/tag/cloud-browser)   /  [7 Best Puppeteer Alternatives for Web Scraping in 2026](https://scrapfly.io/blog/posts/best-puppeteer-alternatives-for-web-scraping)   # 7 Best Puppeteer Alternatives for Web Scraping in 2026

 by [Mayada Shaaban](https://scrapfly.io/blog/author/mayada-shaaban-90143e67) Oct 14, 2026 23 min read [\#cloud-browser](https://scrapfly.io/blog/tag/cloud-browser) [\#headless-browser](https://scrapfly.io/blog/tag/headless-browser) [\#puppeteer](https://scrapfly.io/blog/tag/puppeteer) 

 [  ](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fscrapfly.io%2Fblog%2Fposts%2Fbest-puppeteer-alternatives-for-web-scraping "Share on LinkedIn") [  ](https://x.com/intent/tweet?url=https%3A%2F%2Fscrapfly.io%2Fblog%2Fposts%2Fbest-puppeteer-alternatives-for-web-scraping&text=7%20Best%20Puppeteer%20Alternatives%20for%20Web%20Scraping%20in%202026 "Share on X") [  ](https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fscrapfly.io%2Fblog%2Fposts%2Fbest-puppeteer-alternatives-for-web-scraping "Share on Facebook")    

 

 

Summarize this article with

 [  ](https://chat.openai.com/?q=Summarize%20this%20article%20and%20explain%20how%20Scrapfly%20helps%20me%20scrape%20any%20website%20at%20scale%20and%20bypass%20anti-bot%20systems%20for%20my%20use%20case%3A%20https%3A%2F%2Fscrapfly.io%2Fblog%2Fposts%2Fbest-puppeteer-alternatives-for-web-scraping) [  ](https://claude.ai/new?q=Summarize%20this%20article%20and%20explain%20how%20Scrapfly%20helps%20me%20scrape%20any%20website%20at%20scale%20and%20bypass%20anti-bot%20systems%20for%20my%20use%20case%3A%20https%3A%2F%2Fscrapfly.io%2Fblog%2Fposts%2Fbest-puppeteer-alternatives-for-web-scraping) [  ](https://x.com/i/grok?text=Summarize%20this%20article%20and%20explain%20how%20Scrapfly%20helps%20me%20scrape%20any%20website%20at%20scale%20and%20bypass%20anti-bot%20systems%20for%20my%20use%20case%3A%20https%3A%2F%2Fscrapfly.io%2Fblog%2Fposts%2Fbest-puppeteer-alternatives-for-web-scraping) [  ](https://www.perplexity.ai/search/new?q=Summarize%20this%20article%20and%20explain%20how%20Scrapfly%20helps%20me%20scrape%20any%20website%20at%20scale%20and%20bypass%20anti-bot%20systems%20for%20my%20use%20case%3A%20https%3A%2F%2Fscrapfly.io%2Fblog%2Fposts%2Fbest-puppeteer-alternatives-for-web-scraping) [  ](https://www.google.com/search?udm=50&aep=11&q=Summarize%20this%20article%20and%20explain%20how%20Scrapfly%20helps%20me%20scrape%20any%20website%20at%20scale%20and%20bypass%20anti-bot%20systems%20for%20my%20use%20case%3A%20https%3A%2F%2Fscrapfly.io%2Fblog%2Fposts%2Fbest-puppeteer-alternatives-for-web-scraping) 



         

  **Browser API**Automate browsers in the cloud with full JavaScript rendering support.

 

 [ Learn More  ](https://scrapfly.io/products/cloud-browser-api) [  Docs ](https://scrapfly.io/docs/scrape-api/javascript-rendering) 

 

 

One of the key challenges with Puppeteer scrapers is blocking, and switching to Playwright is often the first fix we'd try. Unfortunately, in our test both libraries failed the same two automation checks on the same Chrome build, as `navigator.webdriver` was `true` and the user agent had `HeadlessChrome` in it.

So which part of Puppeteer are we replacing? It can be the API we write scraper code against, the machine Chrome runs on or the browser itself.

Below, we'll take a look at seven Puppeteer alternatives - four open-source projects and three Scrapfly APIs - and which of these parts each one replaces.



## Which Puppeteer Alternative Is Best?

It depends on what's wrong with our Puppeteer scraper, so let's start with the packages we'll be testing. Here's the `package.json` we used:

json

```json
{
  "name": "puppeteer-alternatives-examples",
  "private": true,
  "type": "module",
  "dependencies": {
    "cheerio": "1.2.0",
    "crawlee": "3.18.2",
    "playwright": "1.63.0",
    "puppeteer": "25.12.0",
    "puppeteer-core": "25.12.0",
    "scrapfly-sdk": "0.12.0",
    "selenium-webdriver": "4.50.0"
  }
}
```



Most of these are the alternatives themselves. For example, `playwright` is there for the library swap, as it's the closest thing to Puppeteer we'll test. We'll use `crawlee` to crawl several pages and `selenium-webdriver` for the WebDriver example.

`cheerio` and `scrapfly-sdk` cover the pages where we don't run a browser ourselves, and `puppeteer-core` is what we'll connect to Cloud Browser with. That last one is where we'd start if our Puppeteer scraper keeps getting blocked!

Note that `"type": "module"` lets us use `import` and top-level `await` in all of our examples. Most of them scrape the same page, [web-scraping.dev/product/1](https://web-scraping.dev/product/1), where the reviews only show up after JavaScript runs.



## 1. Scrapfly Cloud Browser API: Best for Keeping Your Puppeteer Code on Protected Sites

Puppeteer works great for scraping javascript-heavy pages, though unfortunately when scraping at scale it's very likely we'll be blocked, as the headless Chrome it launches is easy to spot.

Scrapfly Cloud Browser gets around this by running our Puppeteer code on Scrapium, Scrapfly's own Chromium fork, with a proxy attached to every session. To use it, we only need to change one line in our scraper!

### Setup

Since the browser runs remotely, all we need is the `puppeteer-core` package without the bundled Chrome:

shell

```shell
npm install puppeteer-core
```



Rather than launching a browser, we'll be connecting to `wss://browser.scrapfly.io` and passing our session settings as URL parameters. For this example that's our `api_key`, the proxy pool and the `target_url`, which the [Puppeteer integration docs](https://scrapfly.io/docs/cloud-browser-api/puppeteer) recommend since routing is decided once the browser starts.

Other options like the proxy `country` or browser `os` work the same way.

### Connecting

Let's take a look at it in action by scraping reviews from our test product page:

javascript

```javascript
import puppeteer from 'puppeteer-core';

const params = new URLSearchParams({
  api_key: process.env.SCRAPFLY_KEY,
  proxy_pool: 'datacenter',
  target_url: 'https://web-scraping.dev/product/1',
});

// the only line that changes from a local script: connect() instead of launch()
const browser = await puppeteer.connect({ browserWSEndpoint: `wss://browser.scrapfly.io?${params}` });
try {
  const page = await browser.newPage();
  await page.goto('https://web-scraping.dev/product/1', { timeout: 60000 });
  await page.waitForSelector('#reviews .review');
  const reviews = await page.$$eval('#reviews .review', (els) => els.length);
  console.log('reviews:', reviews);
  console.log('navigator.webdriver:', await page.evaluate(() => navigator.webdriver));
  console.log('browser:', await browser.version());
} finally {
  await browser.close(); // closing the browser ends the billed session
}
```



Above, we're connecting to Scrapfly's browser rather than launching a local one, while the rest is the same Puppeteer code we'd use locally. We're also printing the `navigator.webdriver` variable, which is one of the variables websites check to detect automated browsers:

text

```text
reviews: 5
navigator.webdriver: false
browser: Scrapium/154.0.8037.92
```



All 5 reviews were scraped and `navigator.webdriver` is `false` - the automation flag is gone!

We'll cover the automation detection test in greater detail in the [blocking section](#will-switching-from-puppeteer-stop-your-scraper-getting-blocked) below, where this same script had no detected checks against 2 for local Puppeteer.

You might also notice that our code closes the browser in a `finally` block. [Cloud Browser](https://scrapfly.io/products/cloud-browser-api) is billed per minute of browser time plus bandwidth, so we always want to close our sessions once we're done (failed connections and crashed sessions are free).

### Sessions and Other Features

For scraping websites that require a login, we can reuse the same browser across multiple connections. For this, we pass a `session` ID together with `auto_close=false`, and the browser keeps its cookies and storage between connections (see [Session Resume](https://scrapfly.io/docs/cloud-browser-api/session-resume)).

There are a few other useful parameters too:

- `solve_captcha=true` - enables the built-in captcha solver, billed per solve (failed attempts are free)
- `block_images` and `block_fonts` - stub out heavy resources to save bandwidth

Finally, if we ever decide to switch to Playwright, the same browser can be used through `chromium.connectOverCDP()`.

[Web Scraping with Playwright and JavaScriptLearn about Playwright - a browser automation toolkit for server side Javascript like NodeJS, Deno or Bun.](https://scrapfly.io/blog/posts/web-scraping-with-playwright-and-javascript)

### Limitations

So what's the catch? The first one is that `connect()` doesn't take launch arguments, so Chrome flags like `--window-size` or `--no-sandbox` won't do anything here. The second is `page.on('console')`, which doesn't receive any output from page scripts because of the stealth patches.

Scrapium is Chromium only, so there's no Firefox here.

How long can a session last? 15 minutes by default, or up to 30 with the `timeout` parameter.



## 2. Playwright: Best for a Multi-Engine Rewrite Across Chromium, Firefox and WebKit

Playwright is a browser automation library from Microsoft, and it's the closest thing to a drop-in replacement for Puppeteer. That's no accident, as its team described itself as "the same team that originally built Puppeteer at Google" when Playwright came out in January 2020 ([InfoQ](https://www.infoq.com/news/2020/01/playwright-browser-automation/), January 30, 2020).

So what do we gain by switching? Mainly auto-waiting locators, which we'll see in a moment, and the WebKit engine, as Puppeteer already drives both Chrome and Firefox (through WebDriver BiDi).

Unlike Puppeteer, Playwright also has official versions for Python, Java and .NET.

Since the APIs are so similar, most Puppeteer calls have a direct counterpart in Playwright:

| Puppeteer | Playwright |
|---|---|
| `puppeteer.launch()` | `chromium.launch()` |
| `waitForSelector()` then `$$eval()` | `locator().first().waitFor()` then `allTextContents()` |
| `browser.createBrowserContext()` | `browser.newContext()` |
| `puppeteer.connect()` | `chromium.connectOverCDP()` |

To see the difference, let's port our review scraper:

javascript

```javascript
import { chromium } from 'playwright';

const browser = await chromium.launch();
try {
  const page = await browser.newPage();
  await page.goto('https://web-scraping.dev/product/1');
  // the locator re-queries the page and waits for a match on its own
  const reviews = page.locator('#reviews .review');
  await reviews.first().waitFor();
  console.log('reviews:', await reviews.count());
  console.log('first:', await reviews.first().locator('p').textContent());
} finally {
  await browser.close();
}
```



Above, we're using a locator rather than `waitForSelector()`. Locators re-query the page every time we use them, and methods like `textContent()` wait for a match by themselves. `count()` doesn't, which is why we wait on `first()` before counting:

text

```text
reviews: 5
first: Absolutely delicious! The orange flavor is my favorite.
```



All 5 reviews were found with Playwright 1.63.0 and its bundled Chromium, without a single `waitForSelector()` call!

So does Playwright help with blocking? Unfortunately not, as on the same Chrome build it tripped the same two automation checks as Puppeteer.

Playwright also uses its own browser builds, which we download with `npx playwright install`. Its Firefox [relies on patches](https://playwright.dev/docs/browsers), so it won't work with the regular Firefox we'd install ourselves.

For remote browsers, `connectOverCDP()` only works with Chromium, and the [BrowserType reference](https://playwright.dev/docs/api/class-browsertype) calls it "significantly lower fidelity" than Playwright's own protocol. Meaning, we can connect to a remote stealth Chromium like Cloud Browser, but not to a remote Firefox or WebKit.

For a feature-by-feature comparison, see [Puppeteer vs Playwright](https://scrapfly.io/blog/posts/puppeteer-vs-playwright), and for a full build, [Web Scraping with Playwright and JavaScript](https://scrapfly.io/blog/posts/web-scraping-with-playwright-and-javascript).



## 3. Scrapfly Web Scraping API: Best for Rendered Pages Without Running a Browser

Often our Puppeteer scrapers only use the browser to render the page - we go to a URL, wait for an element to load and read the HTML. In this case, we don't need to run the browser ourselves at all, as the [Scrapfly Web Scraping API](https://scrapfly.io/products/web-scraping-api) can render the page for us and return the final HTML (or even Markdown).

Using it, there's no browser to launch, patch or scale - we just send a request and get the page back!

Scrapfly has SDKs for Python, TypeScript, Go and Rust. For our Node.js example, let's install the [TypeScript SDK](https://scrapfly.io/docs/sdk/typescript):

shell

```shell
npm install scrapfly-sdk
```



Now, let's scrape our product page reviews again, this time without Puppeteer:

javascript

```javascript
import { ScrapflyClient, ScrapeConfig } from 'scrapfly-sdk';

const client = new ScrapflyClient({ key: process.env.SCRAPFLY_KEY });

const result = await client.scrape(
  new ScrapeConfig({
    url: 'https://web-scraping.dev/product/1',
    render_js: true, // render the page in a cloud browser
    wait_for_selector: '#reviews .review', // the same wait as page.waitForSelector()
    // unblocker: true, // anti-bot bypass for protected sites (asp is the older alias)
  }),
);

console.log('status:', result.result.status_code);
console.log('reviews:', result.selector('#reviews .review').length);
console.log('credits:', result.context.cost.total);
```



Here, we're creating a Scrapfly client with our API key and sending a scrape request for our product page.

Once the page is back, we can parse it right away with `result.selector()` using the same CSS selectors:

text

```text
status: 200
reviews: 5
credits: 6
```



We got all 5 reviews from a single API call!

So, how do we interact with the page, like clicking buttons or scrolling? For this, the API has [JavaScript scenarios](https://scrapfly.io/docs/scrape-api/javascript-scenario) (`js_scenario`), which let us define a list of browser actions like clicks, text input and scrolling that run before the page is returned.

There are a few other useful options too:

- `format` - return the page as `markdown`, `clean_html` or `text` (our page came back as 4,563 characters of Markdown)
- `unblocker` - Unblocker (previously ASP), Scrapfly's anti-bot bypass for protected websites
- `country` - the proxy country for geo-targeting

Failed scrapes aren't billed, so if the Unblocker can't get through or the target website errors out, we don't pay for it.

Unfortunately, a `js_scenario` only has 25 seconds to run, so for logins and longer flows we'd use Cloud Browser instead.

Rendering took 5 of our 6 credits too, so we only turn `render_js` on when a page needs it.



## 4. Crawlee: Best for Multi-Page Crawls Around Your Puppeteer Code

The product listing on web-scraping.dev is split into several pages, with links to the next ones on each page. To scrape all of them, our Puppeteer script would have to follow those links and keep track of the pages it has already seen, so it doesn't scrape the same one twice.

If a page fails we'd want to retry it, and with more pages we'd want to run a few at once and save the results somewhere. Crawlee does all of that for us, so our code only has to scrape each page with plain Puppeteer!

Crawlee isn't limited to Puppeteer either, as `PlaywrightCrawler` and `CheerioCrawler` work the same way as `PuppeteerCrawler`. Switching to `PlaywrightCrawler` keeps this handler as it is, but `CheerioCrawler` passes `$` in place of a browser `page`, so its handler needs rewriting.

To start, we'll need Crawlee itself and Puppeteer:

shell

```shell
npm install crawlee puppeteer
```



Now, let's write a crawler that scrapes the paginated product listing on web-scraping.dev:

javascript

```javascript
import { PuppeteerCrawler, Dataset } from 'crawlee';

const crawler = new PuppeteerCrawler({
  maxRequestsPerCrawl: 3,
  async requestHandler({ request, page, enqueueLinks }) {
    // the same page code you would write in a plain Puppeteer script
    const products = await page.$$eval('.products .product', (els) =>
      els.map((el) => ({
        title: el.querySelector('h3 a').textContent.trim(),
        price: el.querySelector('.price').textContent.trim(),
      })),
    );
    await Dataset.pushData({ url: request.url, products });
    // queue the pagination links; the queue skips URLs it has already seen
    await enqueueLinks({ selector: '.paging a' });
  },
});

await crawler.run(['https://web-scraping.dev/products?page=1']);
const { items } = await Dataset.getData();
for (const row of items) console.log(row.url, row.products.length, row.products[0].title);
```



Above, we defined our crawler which:

- opens each page in Puppeteer and passes it to our `requestHandler`
- scrapes product titles and prices with the usual `$$eval` call
- saves each page's products to Crawlee's `Dataset` storage
- finds pagination links with `enqueueLinks()` and adds them to the queue, skipping URLs it has already seen
- stops after 3 requests through `maxRequestsPerCrawl`

Running it, we get one row for each listing page:

text

```text
https://web-scraping.dev/products?page=1 5 Box of Chocolate Candy
https://web-scraping.dev/products?page=3 5 Classic Leather Sneakers
https://web-scraping.dev/products?page=2 5 Dragon Energy Potion
```



Notice that the pages finish out of order, as Crawlee scrapes them concurrently - and all 3 succeeded!

There's one thing to watch out for, though. If we start from `/products` and not `/products?page=1`, Crawlee sees them as separate URLs and the first page gets scraped twice. So, we should always start from the full first page URL, or normalize links before they're queued.

Crawlee is open source (Apache-2.0) and maintained by Apify, though it runs locally without an Apify account. It's also available for Python!

Unfortunately, Crawlee doesn't run the browsers for us. We still need to keep them updated and bring our own fingerprints and proxies for protected websites, and for a single page it's more setup than a plain Puppeteer script.

For more, see the [Crawlee quick start](https://crawlee.dev/js/docs/quick-start), which covers the other crawler classes.



## 5. Cheerio: Best When the Data Is Already in the HTML

Not every page that looks dynamic needs a browser. A lot of websites send their data together with the HTML and only use JavaScript to display it, which means we can skip Puppeteer and parse the page source directly with Cheerio.

Cheerio is an HTML parser for Node.js with jQuery-style selectors. It doesn't run JavaScript or render anything, so there's no browser to install at all!

So, how do we know if a page needs a browser? Before reaching for Puppeteer, we can check a few things:

- the page source (not the rendered page) - does it contain the text we need?
- hidden JSON - is the data stored in a `<script>` tag, waiting for JavaScript to display it?
- the Network tab - does the browser load the data from a JSON API we could call ourselves?

Our product page has its reviews embedded as JSON. We'll use Node's built-in `fetch` to download it, so Cheerio (1.2.0 in our run) is the only package we need:

shell

```shell
npm install cheerio
```



Now, let's take a look at what the page source holds:

javascript

```javascript
import * as cheerio from 'cheerio';

const response = await fetch('https://web-scraping.dev/product/1');
const $ = cheerio.load(await response.text());

// the review nodes don't exist yet: JavaScript renders them in the browser
console.log('rendered reviews:', $('#reviews .review').length);

// but the data they're rendered from is already in the page source
const reviews = JSON.parse($('#reviews-data').text());
console.log('embedded reviews:', reviews.length);
console.log(reviews[0]);
```



Above, we're downloading the page with `fetch` and loading the HTML into Cheerio. Then, we try the same `#reviews .review` selector Puppeteer waited for and read the `#reviews-data` script tag:

text

```text
rendered reviews: 0
embedded reviews: 5
{
  date: '2022-07-22',
  id: 'chocolate-candy-box-1',
  rating: 5,
  text: 'Absolutely delicious! The orange flavor is my favorite.'
}
```



Notice that the selector finds 0 reviews, as these nodes only appear once JavaScript runs in the browser. However, the script tag already carries all 5 of them, so `JSON.parse()` gets us the data without a browser!

Static pages are even easier - on the `/products` listing, Cheerio parsed all 5 products straight from the HTML.

Cheerio can't click anything though, so the "Load More" button on our product page is out of reach. It loads more reviews through a background request, which we'd have to find and call ourselves (see [how to capture background requests in Puppeteer](https://scrapfly.io/blog/answers/how-to-capture-xhr-requests-puppeteer)).

Unfortunately, plain `fetch` requests also don't look like a browser at the network level, so protected websites tend to block them. For these, the Web Scraping API above is a better fit.

For more, see our [How to Scrape Hidden Web Data](https://scrapfly.io/blog/posts/how-to-scrape-hidden-web-data) guide and our roundup of [JavaScript web scraping libraries](https://scrapfly.io/blog/posts/best-javascript-web-scraping-libraries).



## 6. Scrapfly Screenshot API: Best for Page Screenshots Without Running Chrome

Sometimes the only thing our Puppeteer scraper does is [take screenshots](https://scrapfly.io/blog/answers/how-to-take-screenshot-with-puppeteer), and the browser is only there so `page.screenshot()` has something to capture. For this, we don't need to keep Chrome around at all, as the [Scrapfly Screenshot API](https://scrapfly.io/products/screenshot-api) can take the screenshot for us and send back the image.

Let's take a full-page screenshot of our product page with the same TypeScript SDK we used earlier:

javascript

```javascript
import { ScrapflyClient, ScreenshotConfig } from 'scrapfly-sdk';
import { writeFileSync } from 'node:fs';

const client = new ScrapflyClient({ key: process.env.SCRAPFLY_KEY });

const shot = await client.screenshot(
  new ScreenshotConfig({
    url: 'https://web-scraping.dev/product/1',
    format: 'png',
    capture: 'fullpage',
    wait_for_selector: '#reviews .review',
  }),
);

writeFileSync('product-1.png', Buffer.from(shot.image));
```



Here, we're asking for a `png` image of the `fullpage` and waiting for the reviews to load with `wait_for_selector`, just like we would before `page.screenshot()` in Puppeteer. Then, we save the returned image to `product-1.png`.

This gave us a 1920 x 2343 pixel PNG with all five reviews visible!

We can change the screenshot with a few other options:

- `format` - `jpg`, `webp` or `gif` in place of `png`
- `capture` - a CSS selector to capture a single element
- `resolution` - the viewport size
- `rendering_wait` - a fixed wait time in place of `wait_for_selector`
- `auto_scroll` - scroll down first to load lazy-loaded content
- `js` - run a script before the screenshot
- `options` - extras like `block_banners` and `dark_mode`

Failed screenshots cost 0 credits, and the [Screenshot API docs](https://scrapfly.io/docs/screenshot-api/getting-started) cover the rest of the options.

What about PDFs? Unfortunately, these aren't supported, so we'd keep using `page.pdf()` in Puppeteer or Playwright for them.

For logins and other multi-step flows, Cloud Browser is the better fit.



Scrapfly

#### Need a cloud browser for scraping?

Run headless browsers at scale with Scrapfly Cloud Browser — no infrastructure to manage.

[Try Free →](https://scrapfly.io/register)## 7. Selenium WebDriver: Best for Teams Already on WebDriver or Selenium Grid

What if our team already tests with Selenium? Then there's a good reason to scrape with it too, as our scrapers can share the same Selenium Grid and the same Java, Python, C# or Ruby code.

Under the hood, Selenium uses WebDriver - the W3C standard that the `navigator.webdriver` flag comes from.

Getting started is easy too, as [Selenium Manager](https://www.selenium.dev/documentation/selenium_manager/) has downloaded the right driver for us since version 4.6 (and the browser too since 4.11), and newer releases also speak WebDriver BiDi.

Selenium has official bindings for Java, Python, C#, Ruby and JavaScript. For Node.js, we'll be using the `selenium-webdriver` package (4.50.0 in our run):

shell

```shell
npm install selenium-webdriver
```



To start, let's launch headless Chrome and scrape the reviews from our product page:

javascript

```javascript
import { Builder, By, until } from 'selenium-webdriver';
import chrome from 'selenium-webdriver/chrome.js';

const options = new chrome.Options().addArguments('--headless=new');
const driver = await new Builder().forBrowser('chrome').setChromeOptions(options).build();
try {
  await driver.get('https://web-scraping.dev/product/1');
  await driver.wait(until.elementLocated(By.css('#reviews .review')), 10000);
  const reviews = await driver.findElements(By.css('#reviews .review'));
  console.log('reviews:', reviews.length);
  console.log('navigator.webdriver:', await driver.executeScript('return navigator.webdriver'));
} finally {
  await driver.quit();
}
```



Here, we're using `driver.wait()`, which blocks our program until a condition is met. In this case, our condition is the first review appearing on the page, as Selenium doesn't wait for elements on its own like Playwright's locators. If we run this script, we should see:

text

```text
reviews: 5
navigator.webdriver: true
```



Selenium found the same 5 reviews, though `navigator.webdriver` is `true` - just like with local Puppeteer and Playwright.

What about running many browsers at once? This is what Selenium Grid is for, as it spreads browser sessions across multiple machines, so our scrapers can share the Grid our team already runs (see our [intro to web scraping with Selenium Grid](https://scrapfly.io/blog/posts/intro-to-web-scraping-using-selenium-grid)).

Unfortunately, Selenium can't connect to a remote CDP WebSocket on its own, so it can't drive Cloud Browser directly. For this, the [Cloud Browser Selenium integration](https://scrapfly.io/docs/cloud-browser-api/selenium) uses Playwright as the transport.



## How Do You Choose a Puppeteer Alternative for Your Scraper?

Most of the time, the choice comes down to one question - does our scraper need a browser at all? Let's check the raw HTML of our product page:

javascript

```javascript
const html = await (await fetch('https://web-scraping.dev/product/1')).text();

console.log('review text in HTML:', html.includes('Absolutely delicious'));
console.log('review elements in HTML:', html.includes('class="review"'));
```



Our script looks for the first review's text and for the `class="review"` elements Puppeteer waited for:

text

```text
review text in HTML: true
review elements in HTML: false
```



So the text is in the page, but the elements aren't, as JavaScript builds them from embedded data. Meaning, this page doesn't need Puppeteer, as Cheerio can pick the reviews up from the HTML!

Pages that only show their data after rendering can go to the Web Scraping API.

What about pages that need a browser and keep blocking us? For those, we'd move our Puppeteer code to Cloud Browser without changing it.

To scrape a whole website, the [Scrapfly Crawler API](https://scrapfly.io/products/crawler-api) can find and fetch every page for us.

And for PDFs, Chrome extensions or CDP tracing, we'd simply keep using Puppeteer.

If it's end-to-end tests we're running with Puppeteer, our roundup of [browser automation tools](https://scrapfly.io/blog/posts/best-browser-automation-tools) covers those.



## Will Switching From Puppeteer Stop Your Scraper Getting Blocked?

Unfortunately, not by itself. Let's launch the same Chrome with both Puppeteer and Playwright and see what a website can read from each:

javascript

```javascript
import puppeteer from 'puppeteer';
import { chromium } from 'playwright';

const chrome = await puppeteer.executablePath();
const browsers = {
  puppeteer: await puppeteer.launch(),
  playwright: await chromium.launch({ executablePath: chrome }),
};

for (const [name, browser] of Object.entries(browsers)) {
  const page = await browser.newPage();
  const flags = await page.evaluate(() => [navigator.webdriver, navigator.userAgent]);
  console.log(name, flags);
  await browser.close();
}
```



If we run it, we get the same result twice:

text

```text
puppeteer [
  true,
  'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/154.0.0.0 Safari/537.36'
]
playwright [
  true,
  'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/154.0.0.0 Safari/537.36'
]
```



In both cases `navigator.webdriver` is `true` and the user agent has `HeadlessChrome` in it.

So what does a detection page make of this? Here's what Scrapfly's [Automation Detection Tool](https://scrapfly.io/web-scraping-tools/automation-detector) reported when we tested all three setups on October 7, 2026:

| Setup | Detected | Suspicious | Safe |
|---|---|---|---|
| Puppeteer 25.12.0, headless Chrome 154 | 2 | 1 | 11 |
| Playwright 1.63.0, same Chrome | 2 | 1 | 11 |
| Same Puppeteer script on Cloud Browser | 0 | 1 | 13 |

Both local runs got caught on the same 2 checks, and only the Cloud Browser run hid them!

There's more to blocking than JavaScript flags though, which we cover in [how anti-bot detection works](https://scrapfly.io/blog/posts/how-anti-bot-detection-works).

To hide these flags ourselves, take a look at our [Puppeteer Stealth guide](https://scrapfly.io/blog/posts/puppeteer-stealth-complete-guide) or our roundup of [stealth browsers for web scraping](https://scrapfly.io/blog/posts/best-stealth-browsers).



## Is Puppeteer Still Relevant?

Yes, if the library itself isn't the problem. Let's check when the last few Puppeteer versions came out:

shell

```shell
npm view puppeteer time --json | grep '"25\.1[0-2]\.0"'
```



text

```text
  "25.10.0": "2026-09-03T13:16:42.050Z",
  "25.11.0": "2026-09-14T09:02:32.540Z",
  "25.12.0": "2026-09-23T12:57:22.906Z",
```



That's three releases in September 2026 alone!

The [Puppeteer docs](https://pptr.dev/guides/what-is-puppeteer) also say it controls Chrome or Firefox, and [Firefox launches over WebDriver BiDi by default](https://pptr.dev/webdriver-bidi).

That being said, Playwright is used a lot more. Let's check npm's download counts for the last week of September:

javascript

```javascript
const url = 'https://api.npmjs.org/downloads/point/2026-09-24:2026-09-30/playwright,puppeteer,puppeteer-core';
const data = await (await fetch(url)).json();

for (const [name, { downloads }] of Object.entries(data)) {
  console.log(name, downloads.toLocaleString('en-US'));
}
```



If we run it, we should see:

text

```text
playwright 123,721,643
puppeteer 13,670,209
puppeteer-core 26,539,074
```



That's roughly 3 times more `playwright` downloads than `puppeteer` and `puppeteer-core` combined! Downloads also count CI runs and other packages that depend on them, though.

We'd still pick Puppeteer for PDFs, Chrome extensions, direct CDP sessions and tracing.

To build one from scratch, see our [Puppeteer scraping tutorial](https://scrapfly.io/blog/posts/web-scraping-with-puppeteer-and-nodejs).



### Power your scraping with Scrapfly

Forget about getting blocked. Scrapfly handles anti-bot bypasses, browser rendering, and proxy rotation so you can focus on the data.



[Try for FREE!](https://scrapfly.io/register)



## FAQ

What is the best Puppeteer alternative for PDF generation?Playwright, as it has its own `page.pdf()` that works much like Puppeteer's. That being said, Puppeteer's `page.pdf()` still works fine, so there's no need to switch just for PDFs. Note that Scrapfly's Screenshot API only returns images (`jpg`, `png`, `webp` or `gif`), so it can't make PDFs.







Is there a Python alternative to Puppeteer?Yes, Playwright has a Python version with the same API and both sync and async clients. Selenium works from Python too, and so do Cloud Browser and Scrapfly's Web Scraping API. For nodriver, Camoufox and other Python stealth tools, see our [stealth browsers](https://scrapfly.io/blog/posts/best-stealth-browsers) roundup.







Can Puppeteer connect to a Chrome that is already running?Yes! Let's start Chrome with `--remote-debugging-port=9222` and its own `--user-data-dir`, and connect with `browserURL`:

javascript

```javascript
const browser = await puppeteer.connect({ browserURL: 'http://127.0.0.1:9222' });
```



This read the same 5 reviews from a running Chrome 154 in our test.

Why the separate `--user-data-dir`? Since Chrome 136, the debugging port doesn't work on the default profile (see [Chrome for Developers](https://developer.chrome.com/blog/remote-debugging-port)).









## Summary

So what did it take to stop our Puppeteer scraper from getting caught? Not a new library, as Playwright leaked the same `navigator.webdriver` flag and `HeadlessChrome` user agent.

In our case, this was the only line we had to change:

javascript

```javascript
// const browser = await puppeteer.launch();
const browser = await puppeteer.connect({ browserWSEndpoint: `wss://browser.scrapfly.io?${params}` });
```



For the other jobs, Playwright, Crawlee, Cheerio, Selenium and Scrapfly's Web Scraping and Screenshot APIs each replaced a different part of the setup. So, we'd start from whatever is going wrong with the scraper.

If your Puppeteer scraper works locally but gets blocked in production, give [Scrapfly Cloud Browser](https://scrapfly.io/products/cloud-browser-api) a try before rewriting anything!



## How Do Other Hosted Browser Services for Puppeteer Compare?

Side-by-side pages for other hosted browser services:

- [Browserless alternative](https://scrapfly.io/compare/browserless-alternative)
- [Browserbase alternative](https://scrapfly.io/compare/browserbase-alternative)
- [Lightpanda alternative](https://scrapfly.io/compare/lightpanda-alternative)
- [Hyperbrowser alternative](https://scrapfly.io/compare/hyperbrowser-alternative)
- [Steel.dev alternative](https://scrapfly.io/compare/steeldev-alternative)
- [Anchor Browser alternative](https://scrapfly.io/compare/anchorbrowser-alternative)
- [Bright Data alternative](https://scrapfly.io/compare/brightdata-alternative)



Legal Disclaimer and PrecautionsThis tutorial covers popular web scraping techniques for education. Interacting with public servers requires diligence and respect:

- Do not scrape at rates that could damage the website.
- Do not scrape data that's not available publicly.
- Do not store PII of EU citizens protected by GDPR.
- Do not repurpose *entire* public datasets which can be illegal in some countries.

Scrapfly does not offer legal advice but these are good general rules to follow. For more you should consult a lawyer.

 

   [  Add as a preferred source ](https://google.com/preferences/source?q=scrapfly.io) Table of Contents















 

  Table of Contents

- [Which Puppeteer Alternative Is Best?](#which-puppeteer-alternative-is-best)
- [1. Scrapfly Cloud Browser API: Best for Keeping Your Puppeteer Code on Protected Sites](#1-scrapfly-cloud-browser-api-best-for-keeping-your-puppeteer-code-on-protected-sites)
- [Setup](#setup)
- [Connecting](#connecting)
- [Sessions and Other Features](#sessions-and-other-features)
- [Limitations](#limitations)
- [2. Playwright: Best for a Multi-Engine Rewrite Across Chromium, Firefox and WebKit](#2-playwright-best-for-a-multi-engine-rewrite-across-chromium-firefox-and-webkit)
- [3. Scrapfly Web Scraping API: Best for Rendered Pages Without Running a Browser](#3-scrapfly-web-scraping-api-best-for-rendered-pages-without-running-a-browser)
- [4. Crawlee: Best for Multi-Page Crawls Around Your Puppeteer Code](#4-crawlee-best-for-multi-page-crawls-around-your-puppeteer-code)
- [5. Cheerio: Best When the Data Is Already in the HTML](#5-cheerio-best-when-the-data-is-already-in-the-html)
- [6. Scrapfly Screenshot API: Best for Page Screenshots Without Running Chrome](#6-scrapfly-screenshot-api-best-for-page-screenshots-without-running-chrome)
- [7. Selenium WebDriver: Best for Teams Already on WebDriver or Selenium Grid](#7-selenium-webdriver-best-for-teams-already-on-webdriver-or-selenium-grid)
- [How Do You Choose a Puppeteer Alternative for Your Scraper?](#how-do-you-choose-a-puppeteer-alternative-for-your-scraper)
- [Will Switching From Puppeteer Stop Your Scraper Getting Blocked?](#will-switching-from-puppeteer-stop-your-scraper-getting-blocked)
- [Is Puppeteer Still Relevant?](#is-puppeteer-still-relevant)
- [FAQ](#faq)
- [Summary](#summary)
- [How Do Other Hosted Browser Services for Puppeteer Compare?](#how-do-other-hosted-browser-services-for-puppeteer-compare)
 
    Join the Newsletter  Get monthly web scraping insights 

 

  



Scale Your Web Scraping

Anti-bot bypass, browser rendering, and rotating proxies, all in one API. Start with 1,000 free credits.

  No credit card required  1,000 free API credits  Anti-bot bypass included 

 [Start Free](https://scrapfly.io/register) [View Docs](https://scrapfly.io/docs/onboarding) 

 Not ready? Get our newsletter instead. 

 

 ## Related Articles

 [     

 python blocking 

### Best Stealth Browsers for Web Scraping in 2026

A neutral, benchmark-literate ranking of the best open-source stealth browsers for web scraping in 2026, with the succes...

 

 ](https://scrapfly.io/blog/posts/best-stealth-browsers) [     

 blocking nodejs 

### Puppeteer Stealth: Complete Guide to Avoiding Detection

Complete guide to puppeteer-extra-plugin-stealth for avoiding bot detection. Learn how detection works, configure stealt...

 

 ](https://scrapfly.io/blog/posts/puppeteer-stealth-complete-guide) [  

 python headless-browser 

### How to Scrape Dynamic Websites Using Headless Web Browsers

Introduction to using web automation tools such as Puppeteer, Playwright, Selenium and ScrapFly to render dynamic websit...

 

 ](https://scrapfly.io/blog/posts/scraping-using-browsers) 

  ## Related Questions

- [ Q Getting started with Puppeteer Stealth ](https://scrapfly.io/blog/answers/how-to-use-puppeteer-stealth-what-does-it-do)
 
  



   



 Run headless browsers at scale, **1,000 free credits** [Start Free](https://scrapfly.io/register)