Web Scraping
Arslan
ArslanMar 15, 2026

Learn Selenium web scraping in Python with this step-by-step tutorial. Render JavaScript, handle logins and infinite scroll, intercept JSON APIs, and scale reliably.

Selenium Web Scraping in Python: Step-by-Step Guide (2026)

You need to scrape a website, but the data is locked behind JavaScript, user logins, or infinite scrolling. Standard HTTP libraries fail here because they only download static code. You need a real browser.

Selenium web scraping in Python solves this by automating a live browser session to render JavaScript, click buttons, and extract hidden data. This step-by-step tutorial walks you from setup to a working scraper. While Selenium excels at complex frontend interactions, running a full browser carries heavy compute overhead.

What is Selenium web scraping?

Selenium web scraping uses the Selenium WebDriver and Python to automate a headless browser, load JavaScript-heavy websites, and extract the fully rendered HTML. It forces the browser to run the site's frontend logic before pulling data, which makes it ideal for dynamic content, logins, and DOM interaction.

When to use Selenium for web scraping (and when not to)

Stop treating browser automation as the default extraction method. Running headless browsers requires RAM, CPU, and constant maintenance. Use Selenium only when the page actively resists standard HTTP requests by requiring JavaScript rendering, stateful interactions, login flows, or pagination clicks.

Do not use Selenium for static HTML, clean REST APIs, or bulk high-throughput extraction pipelines. Direct HTTP extraction is significantly cheaper and faster.

The Extraction Decision Tree

Your extraction layer depends entirely on the target site's architecture:

  • Static page: Use Requests + Beautiful Soup.
  • Same data available via XHR/Fetch: Make a direct HTTP request.
  • JS-rendered page requiring user interaction: Use Selenium or Playwright.
  • High-volume production pipeline: Use a managed browser or scraping API.

Selenium is a good fit when you need specific browser behavior, not when you only need data. Use it for interaction, not just extraction.

Is Selenium outdated in 2026?

No. Selenium is not outdated for browser interaction, but it is being displaced for pure data extraction. Its core strength remains driving a real browser through logins, clicks, and multi-step flows.

For simple extraction, teams increasingly reach for direct HTTP requests, Playwright, or a managed scraping API. Search interest reflects this shift. So the rule from the decision tree holds: use Selenium for interaction, and use a lighter layer when you only need the data.

Modern Selenium Python setup (2026-correct)

Most online tutorials teach obsolete setup routines. You no longer need to manually download chromedriver.exe or match it to your browser version.

1. Install Selenium

Install the library using pip:

code
pip install selenium

Modern versions of Selenium include Selenium Manager. When you call webdriver.Chrome(), Selenium Manager automatically detects your installed browser, fetches the correct driver binary, and resolves the path.

2. Configure Chrome for headless scraping

Run headed mode (with the UI visible) while writing and debugging your script. Switch to headless mode (no UI) for automated scraping.

Google unified Chrome's headless architecture, so headless now runs the same browser as headed mode. Passing --headless=new selects this modern unified mode rather than the legacy path (--headless=old, which runs the separate chrome-headless-shell).

code
from selenium import webdriver

options = webdriver.ChromeOptions()
options.add_argument("--headless=new") # Modern headless architecture
options.add_argument("--disable-gpu") # Optional stability for Linux servers
options.add_argument("--window-size=1920,1080") # Prevents responsive mobile layouts

3. Smoke-test the environment

Run this minimal check to confirm Selenium Manager and Chrome communicate successfully.

code
driver = webdriver.Chrome(options=options)
driver.get("https://example.com")

print(f"Success! Page title is: {driver.title}")
driver.quit()

Rely on Selenium Manager to handle driver binaries automatically. Keep your Chrome arguments minimal to avoid creating unique, highly detectable browser fingerprints.

Selenium Python web scraping tutorial: Build your first scraper

We will build a scraper against a dynamic e-commerce template. The target URL below is a placeholder pattern, not a live site. Point it at a real page you have permission to scrape, or practice against a public sandbox built for this purpose such as the Sauce Labs demo store or Scrape This Site. Swap in the site's actual selectors before running.

Map the target elements

Before writing code, inspect the target elements in your browser's DevTools. We want to extract product cards injected via JavaScript.

  • Title: h2.product-title (Text)
  • Price: span.price-tag (Text)
  • Link: a.product-link (href attribute)

Build the minimal scraper step-by-step

Launch the driver, load the page, and wait for JavaScript to inject the product cards into the Document Object Model (DOM).

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

# 1. Launch browser
options = webdriver.ChromeOptions()
options.add_argument("--headless=new")
driver = webdriver.Chrome(options=options)

driver.get("https://demo-ecom.com/js-products")

# 2. Wait for dynamic content to render
wait = WebDriverWait(driver, 10)
cards = wait.until(EC.presence_of_all_elements_located((By.CSS_SELECTOR, ".product-card")))

extracted_data = []

# 3. Extract fields from each card
for card in cards:
    title = card.find_element(By.CSS_SELECTOR, "h2.product-title").text
    price = card.find_element(By.CSS_SELECTOR, "span.price-tag").text
    link = card.find_element(By.CSS_SELECTOR, "a.product-link").get_attribute("href")

    extracted_data.append({
        "title": title,
        "price": price,
        "link": link
    })

driver.quit()

# 4. Export to JSON
with open("products.json", "w", encoding="utf-8") as f:
    json.dump(extracted_data, f, indent=2, ensure_ascii=False)

Why explicit waits beat fixed sleeps

Never use time.sleep(5). It forces your script to wait exactly 5 seconds even if the page loads in 500 milliseconds, destroying throughput. Explicit waits (WebDriverWait) pause execution only until a specific condition is met, then instantly proceed.

If you can load a page, explicitly wait for a state change, and extract one element reliably, you have mastered the core Selenium scraping loop.

How to scrape dynamic websites with Selenium

Dynamic websites require interaction to reveal full datasets. Each interaction introduces a potential race condition between your script and the browser's rendering engine.

Click-driven content and modal gates

To scrape hidden tabs or trigger a "Load More" button, locate the element safely, click it, and wait for the DOM to update.

code
load_more_btn = wait.until(EC.element_to_be_clickable((By.ID, "load-more")))
load_more_btn.click()

# Wait for the item count to increase
wait.until(lambda d: len(d.find_elements(By.CSS_SELECTOR, ".product-card")) > len(cards))

Always wait for state changes, not time. Use content count, text visibility, or attribute mutations as your trigger condition.

Infinite scroll without brittle loops

Scrolling down a page triggers new XHR network requests. Scroll incrementally and stop when the document height plateaus.

code
import time

last_height = driver.execute_script("return document.body.scrollHeight")

while True:
    driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
    time.sleep(1.5) # Short pause to allow network request firing

    new_height = driver.execute_script("return document.body.scrollHeight")
    if new_height == last_height:
        break # Reached the end of the page
    last_height = new_height

Handling stale element references

A StaleElementReferenceException occurs when a script tries to interact with an element that a JavaScript framework (like React or Vue) has rebuilt in the Virtual DOM. Catch the error, re-query the element using your selector, and retry the interaction.

Dynamic pages fail less when your wait conditions track actual page state changes rather than arbitrary countdown timers.

The Hybrid Pattern: Selenium plus Beautiful Soup

Live DOM extraction using Selenium locators is slow. Every .find_element() call communicates back and forth with the browser via the WebDriver protocol.

If you only need Selenium to bypass a JavaScript loading screen, stop using it for parsing. Let the browser render the page, then pass the static HTML to Beautiful Soup.

code
from bs4 import BeautifulSoup

# 1. Render with Selenium
driver.get("https://demo-ecom.com/js-products")
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, ".product-card")))

# 2. Extract raw HTML and close the browser immediately
html = driver.page_source
driver.quit()

# 3. Parse locally with Beautiful Soup
soup = BeautifulSoup(html, "html.parser")
for card in soup.select(".product-card"):
    print(card.select_one("h2.product-title").text)

This pattern eliminates stale element exceptions and speeds up parsing. It carries the same RAM footprint, though, because you still launch headless Chrome to acquire the source code.

Bypassing the DOM: Intercept the real data source

This is the most important concept in modern web scraping. Browsers render UI for humans. Scraping the DOM forces your machine to download images, execute CSS, and render layouts just to pull text from an <h2> tag.

Almost all dynamic content is populated by a backend JSON API. Find it before you write a single locator:

  1. Open Chrome DevTools.
  2. Navigate to the Network panel.
  3. Filter by Fetch/XHR and refresh the page.
  4. Look for endpoints returning raw JSON that contains the data you need.

Once you spot the endpoint, you often skip the browser entirely and request the JSON directly.

code
import requests

# The endpoint you found in the Network panel (Fetch/XHR)
api_url = "https://demo-ecom.com/api/products?page=1"
resp = requests.get(api_url, headers={"Accept": "application/json"})

for product in resp.json()["items"]:
    print(product["title"], product["price"])

Modern Selenium environments using WebDriver BiDi (Bidirectional) can natively intercept these underlying network payloads without parsing the DOM, which is useful when the endpoint requires an authenticated browser session to reach.

The best Selenium scraper uses Selenium only where the browser is strictly required. Always check the Network tab first.

Production hardening for Selenium WebDriver scraping

Tutorial scripts break the moment they hit the real world. Real-world scrapers demand aggressive fault tolerance.

1. Build resilient selectors

Never rely on autogenerated class names (e.g., class="css-1xhj18k"). Scope selectors to stable parent containers. Follow this fallback hierarchy:

  1. Data attributes ([data-test="price"])
  2. Semantic attributes (name="description")
  3. Stable CSS classes (.product-card)
  4. XPath (use strictly as a last resort for complex DOM traversal)

2. Add retry logic

Network requests drop. Proxies time out. Wrap interactions in deliberate retry loops. Catch TimeoutException for slow loads and NoSuchElementException for missing data.

3. Manage browser resources

Headless browsers leak memory over time. Never run a single browser instance for 10,000 URLs. Implement browser recycling: kill the driver and launch a fresh session every 100 pages to clear the cache and free RAM.

4. Implement observability

When a headless script fails on a remote server, you need visual proof. Capture screenshots (driver.save_screenshot("error.png")) or save the HTML dump in exception blocks to debug failures after the fact.

The anti-bot reality check

No amount of code optimization matters if the target site blocks your IP.

Selenium handles basic dynamic sites and internal portals seamlessly. Advanced bot protection systems (like Cloudflare or DataDome) look directly at the browser execution environment. The default Selenium WebDriver leaks a webdriver: true variable in the browser's JavaScript environment.

Open-source stealth patches try to hide these fingerprints, but it is a permanent arms race. Stealth tools degrade over time.

Do not try to build CAPTCHA-solving OCR scripts. If a site throws a CAPTCHA, your IP or browser fingerprint is already burned. Route traffic through residential proxy rotation and respect rate limits aggressively.

The real cost of Selenium at scale

The library is open-source. Running it is not.

A single headless Chrome instance consumes significant RAM, plus heavy CPU cycles during JavaScript execution. Local parallelism hits a severe hardware ceiling quickly.

Scraping economics also rarely account for engineer salaries. Maintenance is dominated by selector churn, forced browser binary updates, proxy rotation management, and debugging.

  • One-off script: Local Selenium is perfect.
  • Small recurring workflow: Selenium on a cheap VPS works fine.
  • Production data pipeline: Managed infrastructure removes the DevOps burden of running browsers, proxies, and retries yourself.

When to transition to managed infrastructure (Olostep)

If your goal is reliable, structured web data at scale, not babysitting driver updates and proxy bans, evaluate an infrastructure platform like Olostep.

Olostep replaces the resource-heavy orchestration script with a single API call. The /scrapes endpoint renders JavaScript and routes through premium residential proxies to return clean Markdown, HTML, or schema-defined JSON, so it covers the same JS-rendering job as your Selenium script without the local browser. When you outgrow one page at a time, /batches submits 100 to 100,000 URLs and returns content in 5 to 7 minutes, and scales toward 1 million requests in around 15 minutes with multiple threads.

That is where the cost math turns. Instead of buying RAM, CPU, and engineer hours to run headless Chrome, you pay per request on a model that starts free and scales with paid plans. When local Selenium prototypes hit their operational limits, this is the logical next step for production batch jobs.

Start Building →

Selenium is free to install, but expensive to scale. Choose the tool for the extraction layer you actually need, not the tool you learned first.

FAQ

What does Selenium do in scraping?

In scraping, Selenium drives a real headless browser to render JavaScript, then interacts with the page (clicking buttons, filling forms, scrolling) before extracting the fully loaded HTML. This lets you pull data that only appears after the site's frontend code runs.

What is Selenium in web scraping?

Selenium is an open-source browser automation framework. In web scraping, it controls headless browsers, renders JavaScript-heavy dynamic pages, and interacts with elements like buttons and forms before extracting the underlying data.

What are the 4 types of Selenium?

Selenium has four components:

  • Selenium WebDriver: Programmatically drives a real browser; this is the tool used for scraping.
  • Selenium IDE: A record-and-playback browser extension for building tests without code.
  • Selenium Grid: Distributes tests and sessions across multiple machines and browsers in parallel.
  • Selenium Remote Control (RC): The legacy server component, now deprecated and replaced by WebDriver.

Is Selenium outdated now?

No, but its role has narrowed. Selenium remains a strong choice for browser interaction such as logins, clicks, and multi-step flows. For pure data extraction it is increasingly displaced by direct HTTP requests, Playwright, or a managed scraping API, which are lighter and faster.

Can Selenium be used for web scraping?

Yes. It is highly effective for extracting data from JavaScript-rendered single-page applications (SPAs). It does consume far more compute than standard HTTP requests, which makes it costly for large-scale batch operations.

How do I scrape a website using Selenium Python?

Install the library via pip, initialize webdriver.Chrome(), use .get(url) to load the page, apply WebDriverWait for dynamic content to render, and extract text using CSS selectors. Finally, export the parsed data to JSON or CSV.

Is Selenium good for scraping dynamic websites?

Yes. Because Selenium runs a real Chromium engine, it natively executes React, Vue, or Angular applications. It excels at rendering and interaction, though it stays slower for high-throughput extraction compared to API interception.

Selenium vs Beautiful Soup for web scraping: Which is better?

Beautiful Soup is strictly an HTML parser; it cannot render JavaScript or execute clicks. Selenium controls a browser to render JavaScript. They work best together: Selenium renders the dynamic DOM, and Beautiful Soup parses the resulting static HTML.

Do I still need ChromeDriver for Selenium Python?

No. Modern versions of Selenium (v4.6+) include Selenium Manager. It automatically detects your local Chrome installation, downloads the matching driver binary, and configures the system paths in the background.

About the Author

Arslan Ali

Co-Founder, Olostep · San Francisco, CA

Arslan is the co-founder of Olostep, a web data infrastructure platform that helps developers and teams access, extract, and structure web data at scale. He works closely on the product and technology behind Olostep, with a focus on building reliable infrastructure for web scraping, search APIs, and structured web data.

Read more