Skip to content

Playwright vs Selenium for Web Scraping: The Same Scraper in Both (Python)

Playwright vs Selenium for web scraping: languages, browsers, waiting, network interception, parallel sessions and detection, from the official docs, plus the same Python scraper written in both and tested on 22 October 2026.

Published
Reading time
7 min read
Pixel-art robot with an orange >_ on its terminal head stands thinking between two matching arcade cabinets, one with the Playwright logo and one with the green Selenium logo, next to a crate bearing the Scrape.ly logo
In this article
  1. Playwright vs Selenium at a glance
  2. Languages and browsers
  3. The same scraper in both
  4. Waiting for content
  5. Network interception
  6. Running many sessions
  7. Detection and blocking
  8. Selenium vs Playwright: which should you pick?
  9. Puppeteer vs Selenium, and Playwright vs Puppeteer
  10. Skip the browser plumbing with Scrape.ly
  11. FAQ
  12. Is Playwright better than Selenium for web scraping?
  13. Is Playwright faster than Selenium?
  14. Can Playwright replace Selenium?
  15. Does Playwright work with Python?
  16. Which is harder for websites to detect, Playwright or Selenium?

Playwright vs Selenium for web scraping: Playwright is the better default for a new scraper, because it waits for elements on its own, intercepts network requests in one line and runs many isolated sessions inside one browser. Selenium remains a solid choice when you need a language Playwright doesn't offer, such as Ruby, or you already run a Selenium Grid.

Below is how they compare on what matters for scraping, using facts from playwright.dev and selenium.dev as of 22 October 2026, then the same scraper written in both and run on the same JavaScript-rendered page. At the end: how to skip the browser plumbing entirely.

Playwright vs Selenium at a glance

Playwright Selenium
Languages TypeScript/JavaScript, Python, .NET, Java Java, Python, C#, Ruby, JavaScript, Kotlin
Browsers Chromium, Firefox and WebKit builds it installs, plus installed Chrome and Edge Chrome, Edge, Firefox, Safari, Internet Explorer
Setup pip install playwright, then playwright install downloads matching browsers pip install selenium; Selenium Manager finds or fetches the driver
Waiting Auto-waits before every action (visible, stable, enabled, receives events) Implicit wait (default 0) or explicit waits you write
Network interception Built in: page.route() to block, mock or modify requests WebDriver BiDi network handlers, enabled in options, still being rolled out
Many sessions Browser contexts: isolated profiles inside one browser One driver per session; Selenium Grid spreads sessions across machines
Sync and async (Python) Both Synchronous
navigator.webdriver in headless mode true (our test) true (our test)

Languages and browsers

Playwright is "available for TypeScript, Python, .NET, and Java" and drives Chromium, Firefox and WebKit with one API (playwright.dev). Each Playwright release is tied to specific browser builds that playwright install downloads, and it can also drive the Chrome or Edge installed on your machine. It doesn't drive branded Firefox or Safari, because it relies on patched builds of those engines (browsers docs).

Selenium implements the W3C WebDriver standard, so each browser vendor's driver speaks the same protocol. Its docs show bindings for Java, Python, C#, Ruby, JavaScript and Kotlin, and it supports Chrome, Edge, Firefox, Safari and Internet Explorer (selenium.dev). Selenium Manager, a tool written in Rust, now finds or downloads the right driver automatically, which removed the old "download chromedriver by hand" step.

For scraping, the browser list rarely matters: almost every scraper runs Chromium. The language list matters more, for example if your team works in Ruby.

The same scraper in both

The task: scrape the first three pages of quotes.toscrape.com/js, a sandbox site whose quotes are written into the page by JavaScript, so a plain HTTP request sees no quotes at all. Install with pip install playwright selenium, then run playwright install chromium.

Playwright (sync API):

python
from urllib.parse import urljoin

from playwright.sync_api import sync_playwright

START_URL = "https://quotes.toscrape.com/js/"

def scrape(max_pages: int = 3) -> list[dict]:
    quotes = []
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=True)
        page = browser.new_page()
        # Skip images, fonts and stylesheets: the data is in the HTML.
        page.route(
            "**/*",
            lambda route: route.abort()
            if route.request.resource_type in {"image", "font", "stylesheet"}
            else route.continue_(),
        )
        url = START_URL
        for _ in range(max_pages):
            page.goto(url)
            cards = page.locator("div.quote")
            cards.first.wait_for()  # the quotes are written by JavaScript
            for card in cards.all():
                quotes.append({
                    "text": card.locator("span.text").inner_text(),
                    "author": card.locator("small.author").inner_text(),
                    "tags": card.locator("a.tag").all_inner_texts(),
                })
            next_link = page.locator("li.next > a")
            if next_link.count() == 0:
                break
            url = urljoin(page.url, next_link.get_attribute("href"))
        browser.close()
    return quotes

if __name__ == "__main__":
    rows = scrape()
    print(len(rows), rows[0])

Selenium:

python
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait

START_URL = "https://quotes.toscrape.com/js/"

def scrape(max_pages: int = 3) -> list[dict]:
    options = webdriver.ChromeOptions()
    options.add_argument("--headless=new")
    driver = webdriver.Chrome(options=options)  # Selenium Manager finds the driver
    wait = WebDriverWait(driver, timeout=10)
    quotes = []
    try:
        url = START_URL
        for _ in range(max_pages):
            driver.get(url)
            cards = wait.until(
                EC.presence_of_all_elements_located((By.CSS_SELECTOR, "div.quote"))
            )
            for card in cards:
                quotes.append({
                    "text": card.find_element(By.CSS_SELECTOR, "span.text").text,
                    "author": card.find_element(By.CSS_SELECTOR, "small.author").text,
                    "tags": [t.text for t in card.find_elements(By.CSS_SELECTOR, "a.tag")],
                })
            next_links = driver.find_elements(By.CSS_SELECTOR, "li.next > a")
            if not next_links:
                break
            url = next_links[0].get_attribute("href")  # Selenium returns the absolute URL
    finally:
        driver.quit()
    return quotes

if __name__ == "__main__":
    rows = scrape()
    print(len(rows), rows[0])

We ran both on 22 October 2026 on macOS with Python 3.14, Playwright 1.64.0 and Selenium 4.51.0 (driving the installed Google Chrome). Both printed the same thing:

Text
30 {'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', 'tags': ['change', 'deep-thoughts', 'thinking', 'world']}

The code is similar in length. The differences are in what you didn't have to write, and what each line protects you from.

Waiting for content

This is where most scrapers break. Selenium's own docs call race conditions between the browser and your script "one of the primary causes of flaky tests" (waits docs). Its implicit wait defaults to 0, so a lookup fails immediately if the element isn't there yet. You add explicit waits like the WebDriverWait above at every point where content loads, and the docs warn: "Do not mix implicit and explicit waits."

Playwright runs actionability checks before every action. A click waits until the element is visible, stable, enabled and not covered by another element, and fails with a TimeoutError only if that doesn't happen in time (auto-waiting docs). In a scraper you still wait once for the data to appear (cards.first.wait_for() above), but clicks, form fills and pagination stop being a source of random failures.

Network interception

Playwright can watch, block, mock or modify every request a page makes with page.route() (network docs). In the scraper above, one call stops images, fonts and stylesheets from loading, which saves bandwidth on every page, and that matters when you pay for proxy traffic. The same API lets you capture the JSON responses a page fetches, which is often cleaner than parsing the HTML.

Selenium is adding the same abilities through WebDriver BiDi: request and response handlers you enable in the browser options. Its docs note that the implementation is still being tracked (BiDi network docs). For scraping today, Playwright's version is the one you can rely on in every language.

Running many sessions

Scrapers need parallel, isolated sessions: separate cookies, separate logins, separate proxies. Playwright's browser contexts are exactly that, "equivalent to a brand new browser profile", created inside one running browser "with near-zero overhead" (playwright.dev), and each context can have its own proxy. Its async Python API lets one process drive many pages at once.

Selenium starts one browser per driver session. To run many, you start many browsers, or run Selenium Grid, which "is for you" if you want to run "in parallel across multiple machines" (Selenium docs). Grid is powerful, but it is infrastructure you operate.

Detection and blocking

Neither tool hides that it is automated, and neither project markets itself for that. Playwright "was created specifically to accommodate the needs of end-to-end testing" (intro). The W3C WebDriver standard defines a navigator.webdriver property that reports true when a browser is under automation (spec). In our test on the same machine, it was true in headless Playwright Chromium, headless Playwright Firefox and headless Selenium Chrome.

So on sites with serious bot protection, the choice between Playwright and Selenium doesn't decide whether you get through. Fingerprints, IP reputation and challenge pages do. That's a separate layer, and it's where most scraping projects spend their time.

Selenium vs Playwright: which should you pick?

  • Starting a new Python or JavaScript scraper: Playwright. Less waiting code, built-in request blocking, cheap isolated sessions.
  • Your team writes Ruby: Selenium, since Playwright has no official Ruby version.
  • You already run Selenium Grid for testing: reuse it for scraping, or move the scrapers to Playwright when the waits start costing you.
  • You need real Safari: Selenium. Playwright drives its own WebKit build instead.
  • The site blocks headless browsers: neither library alone. You need a browser with real fingerprints and good IPs, covered below.

Puppeteer vs Selenium, and Playwright vs Puppeteer

Puppeteer is a "JavaScript library which provides a high-level API to control Chrome or Firefox over the DevTools Protocol or WebDriver BiDi". Against Selenium it trades breadth for focus: one language and two browsers, versus Selenium's six languages and five browsers. Against Playwright, the main difference for scrapers is language: Puppeteer is JavaScript-only, while Playwright also has Python, .NET and Java. We cover Playwright vs Puppeteer in depth in a separate article.

Skip the browser plumbing with Scrape.ly

Picking the library is the easy part. Keeping a browser scraper running means proxies, fingerprints, retries, scheduling and storage. Scrape.ly gives you three ways to skip that work:

  • Start from a template. The Crawlee + Playwright (Python) template gives you a PlaywrightCrawler with a request queue, proxy configuration and an output schema: scrapely create my-actor --template crawlee-playwright-py, put your parsing in the router, and push it. It runs in the cloud with the same storage, scheduling and API as every Store Actor. More starters are on the templates page.
  • Run the browser on the Scrape.ly browser. Turn on the Scrape.ly browser for your Actor and rebuild: every Playwright or Puppeteer launch connects to a hosted Chromium with anti-bot fingerprints, Cloudflare Turnstile solving and its own residential proxy, with no code changes. You choose the exit country, state or city and the operating system. It costs $0.45 per browser hour at list price, billed in 30-second blocks plus the browser's proxy traffic, and it counts toward the run's maximum charge. See anti-blocking for how it fits with the managed proxy.
  • Use a ready Actor. For big sites, someone has already done it. The Scrape.ly Store has 568 Actors, and many don't need a browser at all: in our Google Maps tutorial, the Google Maps Scraper saved 10 places with 11 plain HTTP requests and under 1 MB of traffic.

If you'd rather have an AI agent drive the browser, see our guide to Playwright MCP.

FAQ

Is Playwright better than Selenium for web scraping?

For new scrapers, yes. Auto-waiting removes the most common source of flaky runs, page.route() lets you block heavy resources or capture JSON responses, and browser contexts give you cheap isolated sessions. Selenium is the better fit only if you need Ruby or real Safari, or already operate a Selenium Grid.

Is Playwright faster than Selenium?

We didn't benchmark them, and single runs on one machine wouldn't prove much. Architecturally, Playwright avoids starting a new browser for every session by using contexts, and lets you block images and stylesheets, which saves time and bandwidth on every page.

Can Playwright replace Selenium?

For scraping, almost always. Playwright covers the same tasks (navigation, clicks, forms, screenshots, JavaScript evaluation) and adds network interception and contexts. The exceptions are teams that need Selenium's language list, real Safari, or an existing Grid.

Does Playwright work with Python?

Yes. Playwright for Python has both a synchronous and an asynchronous API and needs Python 3.8 or newer. Install it with pip install playwright, then playwright install to download the browsers.

Which is harder for websites to detect, Playwright or Selenium?

Neither by default. Both report navigator.webdriver as true in headless mode in our test. Getting past bot protection depends on fingerprints, IPs and challenge solving, which is what the Scrape.ly browser handles.

  • playwright
  • selenium
  • python
  • headless browser
  • web scraping

About the author

The team behind Scrape.ly

Scrape.ly is the web data platform from TeraShift GmbH in Switzerland, powered by Evomi: ready-made scrapers in the Scrape.ly Store, a cloud runtime, an API with JavaScript and Python SDKs, proxies, a hosted anti-bot browser and an MCP server for AI agents.

//Keep reading\\

The internet is full of data.
Start owning yours.

Scrape.ly gives you the infrastructure, the tooling, and the team to extract, process, and operationalize web data at any scale. Setup takes minutes. The advantage lasts forever.

No credit card. No setup calls.