# Web Scraping with Selenium and Python - updated guide for 2026 > Source: https://scrape.do/blog/selenium-web-scraping/ Published: 2024-10-15 · Updated: 2025-01-13 · Authors: Fimber (Kasarachi) Elemuwa · Categories: Headless Browser, Scraping Basics Selenium is one of the top headless browsers used for scraping and testing automations, and it **enables scrapers with powerful tools to get their tasks done.** Unlike static scrapers that can only retrieve pre-rendered HTML, Selenium WebDriver fully engages with the rendered Document Object Model (DOM), enabling data extraction from JavaScript-heavy websites. Browser automation through Selenium is also crucial for handling complex scraping scenarios where JavaScript renders essential content, like e-commerce product listings or social media feeds. This article will act as your handbook through scraping the web using Selenium in Python. **_Without further ado, let's dive right in!_** ## How Selenium Works with WebDriver **Selenium** facilitates browser automation by integrating with **WebDriver**, a standardized API and protocol for controlling web browsers. Each major browser has its own driver (like ChromeDriver for Chrome or GeckoDriver for Firefox) that translates Selenium commands into actions within the browser. This architecture allows Selenium to send instructions to WebDriver, which communicates directly with the browser to perform tasks such as: - clicking buttons, - scrolling, - or waiting for JavaScript content to load. By using browser-specific drivers, Selenium enables cross-browser and cross-platform automation, ensuring scripts behave consistently across different environments. Browser vendors typically maintain their drivers, ensuring they are optimized for each browser's unique features and updates. For cases where vendor-supported drivers are not available, the Selenium project provides its own driver support to maintain functionality. This modular design not only supports transparent interaction across multiple browsers but also provides developers with flexibility for testing and scraping applications that demand high compatibility and responsiveness across platforms. ## Environment Setup for Selenium Web Scraping You can use Selenium with various languages like NodeJS - Selenium with Ruby is also a good option. **For this article, we'll go exclusively with Python.** To get started with Selenium in Python, the first step is installing the Selenium package. Use pip to install it: ```sh pip install selenium ``` Selenium supports various languages, including Python, Java, C#, and JavaScript. While Python is popular for web scraping, the installation process is similar across languages, involving adding Selenium's libraries to your project. For this article, we’ll be using Python for everything. Selenium requires a browser driver to communicate with your browser. For Chrome, use **ChromeDriver**, and for Firefox, use **GeckoDriver**. You can download the appropriate driver version for your browser from [ChromeDriver](https://googlechromelabs.github.io/chrome-for-testing/) or[GeckoDriver](https://github.com/mozilla/geckodriver/releases). After downloading, ensure the driver’s path is correctly linked in your script. ```py from selenium import webdriver from selenium.webdriver.chrome.service import Service # Provide the path to your ChromeDriver service = Service('/path/to/chromedriver') driver = webdriver.Chrome(service=service) ``` An important part of using Selenium is headless browsing. Headless browsing enables Selenium to run the browser in the background without displaying the graphical interface, making it faster and more resource-efficient. This is especially useful for large-scale scraping tasks where performance is key. Here’s how to set it up: ```py from selenium.webdriver.chrome.options import Options # Configure headless mode chrome_options = Options() chrome_options.add_argument("--headless") driver = webdriver.Chrome(service=Service('/path/to/chromedriver'), options=chrome_options) ``` Headless mode speeds up the scraping process by reducing the overhead associated with rendering the browser UI, which is particularly beneficial when scraping at scale. To optimize scraping performance, it’s crucial to configure timeouts to avoid waiting unnecessarily for page elements. Selenium lets you use implicit waits (global delay) and explicit waits (targeted delays). Implicit waits tell Selenium to wait for a set time before throwing an exception if elements are not found, while explicit wait gives more precise control by waiting for specific conditions to be met (e.g., element presence). ## Handling Dynamic Content with Selenium Today, JavaScript is the backbone of the internet, and most websites built with JavaScript frameworks like React, Angular, or Vue dynamically render content after the initial page load. Traditional static scraping methods struggle here because the HTML is often empty or incomplete until the JavaScript has fully executed. Selenium, however, can render the entire DOM by interacting with the browser, making it ideal for scraping JavaScript-heavy websites. Selenium's WebDriverWait is essential when scraping JavaScript-rendered pages, as it allows you to wait for specific elements to appear before attempting to extract data. By combining this with conditions like presence_of_elements_located, Selenium ensures you scrape only once the data is fully loaded. Here’s an example: ```py 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 # Set up Selenium driver_path = '/path/to/chromedriver' driver = webdriver.Chrome() driver.get('https://www.scrapingcourse.com/ecommerce/') try: # Wait for multiple elements to be present WebDriverWait(driver, 20).until( EC.presence_of_all_elements_located((By.CLASS_NAME, 'product-name')) ) # Extract text from all elements with the specified class elements = driver.find_elements(By.CLASS_NAME, 'product-name') # Loop through each element and print the text for element in elements: print("Product name:", element.text) except Exception as e: print("Error occurred:", str(e)) finally: # Ensure the browser is closed driver.quit() ``` In this example, the script waits until the JavaScript has rendered a product name before extracting the text, ensuring you scrape the right data. ### Handling Lazy Loading and Infinite Scroll Many modern websites, particularly those with long product lists or social media feeds, use lazy loading or infinite scroll to improve performance. Instead of loading all the content at once, new content is loaded dynamically as the user scrolls. Selenium can handle these types of interactions by simulating scrolling. To ensure all data is loaded, you can repeatedly scroll to the bottom of the page, triggering additional content loads, until no new content appears. Here’s how to do that: ```py import time 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 from selenium.common.exceptions import TimeoutException # Set up Selenium driver_path = '/path/to/chromedriver' # Replace with your actual chromedriver path options = webdriver.ChromeOptions() options.add_argument('--headless') # Run in headless mode to speed up the scraping options.add_argument('--disable-gpu') # Disable GPU acceleration driver = webdriver.Chrome(executable_path=driver_path, options=options) # Define function to scroll to the bottom of the page def scroll_to_bottom(driver): """ Scroll to the bottom of the page to trigger lazy loading. """ driver.execute_script("window.scrollTo(0, document.body.scrollHeight);") time.sleep(2) # Adjust sleep to wait for new content to load try: # Open the e-commerce page driver.get('https://www.scrapingcourse.com/infinite-scrolling') # Initialize product list product_names = set() # Scroll multiple times to load more content dynamically scroll_pause_time = 2 # Seconds to wait for new products to load after scrolling last_height = driver.execute_script("return document.body.scrollHeight") while True: # Scroll down to the bottom of the page scroll_to_bottom(driver) # Wait for new elements to be loaded try: WebDriverWait(driver, 20).until( EC.presence_of_all_elements_located((By.CLASS_NAME, 'product-name')) ) except TimeoutException: print("Timeout waiting for products to load.") break # Extract product names after each scroll products = driver.find_elements(By.CLASS_NAME, 'product-name') for product in products: product_names.add(product.text) # Use a set to avoid duplicates # Calculate new scroll height and compare with last scroll height new_height = driver.execute_script("return document.body.scrollHeight") if new_height == last_height: # If the scroll height hasn't changed, exit the loop (end of page) break last_height = new_height time.sleep(scroll_pause_time) # Print all collected product names for name in product_names: print("Product name:", name) except Exception as e: print("Error occurred:", str(e)) finally: # Ensure the browser is closed properly driver.quit() ``` This script scrolls to the bottom of the page, waits for more content to load, and repeats the process until no new content is detected, which is essential for pages that rely on infinite scroll or lazy loading. However, you might want to add a timer to avoid scraping infinitely. Scrape.do makes handling dynamic content, lazy loading, and infinite scrolling significantly easier by offering a fully managed scraping API that automatically deals with JavaScript rendering and dynamic data loading. Instead of manually using Selenium to wait for elements, simulate scrolling, and manage JavaScript-heavy websites, Scrape.do renders the full DOM, handling all dynamic content in the background. This eliminates the need for complex scrolling scripts or WebDriver waits, allowing you to scrape data from JavaScript frameworks like React or Angular effortlessly. Additionally, Scrape.do optimizes the scraping process by managing timeouts, avoiding infinite loops, and ensuring efficient data extraction from any dynamic site. ## Dealing with CAPTCHA, Anti-bot Measures, and Throttling Web scrapping can sometimes be a furious dance in which you try to get data from a website while the website attempts to stop you from doing that using anti-bot mechanisms like CAPTCHAs, rate-limiting, and IP blocking. While these are formidable defences, there are a couple of ways you can use to get past them. ### Handling CAPTCHA CAPTCHAs are one of the most common anti-bot mechanisms, used to ensure the interaction is coming from a human. CAPTCHA types can range from simple image selection to more complex versions like reCAPTCHA and Cloudflare's JavaScript challenges. Common Anti-Bot Mechanisms include: - **Simple CAPTCHAs**: These might ask the user to enter text from an image. - **reCAPTCHA v2/v3**: Google’s reCAPTCHA that often includes clicking images or is invisible to the user but detects bot-like behavior. - **Cloudflare Challenges**: These include JavaScript-based challenges that test whether requests are coming from a bot. CAPTCHAs can be solved manually or automatically by integrating with third-party CAPTCHA-solving services such as [2Captcha](https://2captcha.com/), [AntiCaptcha](https://anti-captcha.com/), or [DeathByCaptcha](https://deathbycaptcha.com/). These services allow you to send the CAPTCHA to their API, where human solvers or AI resolve it and return the solution. Here’s an example of using 2Captcha to bypass CAPTCHAs: ```py import requests API_KEY = 'your-2captcha-api-key' captcha_image_url = 'https://example.com/captcha-image-url' # Step 1: Send CAPTCHA image to 2Captcha captcha_data = { 'key': API_KEY, 'method': 'base64', 'body': captcha_image_url, 'json': 1 } response = requests.post('http://2captcha.com/in.php', data=captcha_data) captcha_id = response.json().get('request') # Step 2: Poll for the solution solution_url = f'http://2captcha.com/res.php?key={API_KEY}&action=get&id={captcha_id}&json=1' while True: result = requests.get(solution_url).json() if result.get('status') == 1: print(f"Captcha Solved: {result['request']}") break else: time.sleep(5) # Wait and retry until solved ``` This approach is often used to bypass simple image CAPTCHAs. For reCAPTCHA and Cloudflare challenges, more advanced techniques are required. reCAPTCHA(V2) requires solving a visual challenge and you can pass the g-recaptcha-response token from 2Captcha to solve this. Cloudflare, on the other hand, uses **JavaScript challenges** or CAPTCHA forms to detect bots, and these challenges may require a special tool like **CloudScraper** to bypass. To do that, you first have to install cloudscraper: ````py pip install cloudscrape``` Then, you can run the following script: ```py import cloudscraper # Using CloudScraper to bypass Cloudflare challenges scraper = cloudscraper.create_scraper() response = scraper.get('https://example.com') print(response.text) ```` ### Randomization and Throttling to Avoid IP Blocking To avoid IP blocking and throttling, it’s essential to simulate human behavior by rotating user agents, adding random delays, and interacting with the page in a non-uniform way (such as random mouse movements or keystrokes). Here’s how to do that: - **Rotating User Agents**: Changing the user-agent string for each request can help prevent detection by mimicking different browsers. Selenium’s webdriver.ChromeOptions() allows you to set a custom user-agent. ```py from selenium import webdriver import random # List of user-agents user_agents = [ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.2 Safari/605.1.15', 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:89.0) Gecko/20100101 Firefox/89.0' ] # Set up Selenium with a random user-agent options = webdriver.ChromeOptions() user_agent = random.choice(user_agents) options.add_argument(f'user-agent={user_agent}') driver = webdriver.Chrome(options=options) driver.get('https://example.com') ``` - **Adding Random Delays:** Adding random delays between actions mimics human behavior and reduces the chance of being flagged as a bot. ```py import time import random # Random delay between actions time.sleep(random.uniform(2, 5)) # Wait between 2 and 5 seconds ``` - **Simulating Human Interaction (Mouse Movements, Keystrokes):** Interacting with the page using mouse movements and keystrokes can further reduce bot detection. You can use Selenium’s actions to simulate user interaction. ```py from selenium.webdriver.common.action_chains import ActionChains # Move the mouse to a random position actions = ActionChains(driver) element = driver.find_element(By.ID, 'some-element') actions.move_to_element(element).perform() # Simulate typing in a text box text_box = driver.find_element(By.NAME, 'search') text_box.send_keys('Selenium Web Scraping') ``` - **IP Rotation and Proxies:** Using proxies or rotating IPs is another method to avoid detection. Services like **ScraperAPI** or **Bright Data** allow you to rotate IPs for each request. ```py from selenium import webdriver # Set up proxy in Chrome options options = webdriver.ChromeOptions() options.add_argument('--proxy-server=http://myproxy:1234') driver = webdriver.Chrome(options=options) driver.get('https://example.com') ``` Scrape.do simplifies overcoming these challenges by offering a fully managed web scraping API that handles anti-bot measures, CAPTCHAs, and throttling without needing manual intervention. With Scrape.do, you don’t have to worry about integrating CAPTCHA-solving services, rotating user agents, or managing proxies. The platform automatically takes care of these, ensuring uninterrupted data extraction while simulating human-like behavior to avoid detection. Our built-in support for IP rotation and CAPTCHA bypass means you can scrape even the most secure websites without getting blocked, significantly reducing the complexity and overhead of managing your scraping infrastructure. ## Advanced DOM Manipulation and Interaction When it comes to web scraping, sometimes you need to interact with forms, submit search queries, or click buttons to retrieve specific data. Selenium’s ability to interact with a webpage like a real user makes it perfect for scraping such dynamic content. To extract data after interacting with forms, you can simulate filling out form fields, selecting dropdowns, and clicking buttons. Let’s break this down with some examples. ### Submitting a Search Query Suppose you want to scrape search results from a website where you need to submit a search term first. Here’s how you’d do that: ```py from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys import time # Set up Selenium WebDriver driver_path = '/path/to/chromedriver' driver = webdriver.Chrome(executable_path=driver_path) # Open the search page driver.get('https://example.com/search') # Find the search box and input a search term search_box = driver.find_element(By.NAME, 'q') # Assuming 'q' is the name of the search box search_box.send_keys('Selenium Web Scraping') # Submit the search form by simulating an Enter key press search_box.send_keys(Keys.RETURN) # Wait for results to load time.sleep(2) # Scrape search results results = driver.find_elements(By.CLASS_NAME, 'result') for result in results: print(result.text) # Close the browser driver.quit() ``` In this example, You first locate the search box using its HTML attributes (e.g., name="q"). The send_keys() method sends input to the form, simulating typing, and you can then submit the form by simulating an **Enter** key press using Keys.RETURN. In some cases, you need to explicitly click the "Submit" button after filling out the form. Here's how to handle that: ```py from selenium.webdriver.common.by import By # Locate the submit button by its ID and click it submit_button = driver.find_element(By.ID, 'submit-button-id') submit_button.click() ``` ### Interacting with Dropdowns and Radio Buttons When forms contain dropdown menus or radio buttons, Selenium can also select these elements. ```py from selenium.webdriver.support.ui import Select # Locate the dropdown menu and select an option dropdown = Select(driver.find_element(By.ID, 'dropdown-id')) dropdown.select_by_value('option_value') # You can select by value, index, or visible text ``` The Select class in Selenium is used to interact with dropdown menus. You can select an option by its **value**, **visible text**, or **index**. ### Advanced Interaction: Dealing with Dynamic Forms Some forms dynamically load additional content (e.g., dependent dropdowns or CAPTCHA after submission). In such cases, it’s essential to use **explicit waits** to ensure the new elements are loaded before interacting with them. ```py from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC # Wait for the dynamically loaded element (e.g., search results) WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.CLASS_NAME, 'dynamic-results')) ) ``` ## Data Extraction and Cleaning Data extraction is one of the most critical tasks in web scraping, and Selenium provides various ways to locate and extract elements from a webpage. You can extract text, links, images, and attributes using different Selenium selectors like By.ID, By.CLASS_NAME, By.XPATH, and others. Text content is often the most important data when scraping web pages. You can extract the text of an HTML element using the text attribute in Selenium. ```py from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.chrome.service import Service # Set up Selenium WebDriver with Service driver_path = '/path/to/chromedriver' service = Service() driver = webdriver.Chrome() # Open the target page driver.get('https://books.toscrape.com/') # Extract text using different selectors element_by_id = driver.find_element(By.ID, 'element-id').text element_by_class = driver.find_element(By.CLASS_NAME, 'element-class').text element_by_xpath = driver.find_element(By.XPATH, '//div[@class="element-class"]').text # Print the extracted text print("Text by ID:", element_by_id) print("Text by Class:", element_by_class) print("Text by XPath:", element_by_xpath) # Close the browser driver.quit() ``` You can also extract links from anchor (<a>) elements by retrieving their href attributes. ```py # Extract all links on the page links = driver.find_elements(By.TAG_NAME, 'a') for link in links: href = link.get_attribute('href') print("Link:", href) ``` Similar to extracting links, you can extract image URLs from <img> tags by retrieving their src attributes. ```py # Extract all images on the page images = driver.find_elements(By.TAG_NAME, 'img') for image in images: src = image.get_attribute('src') print("Image URL:", src) ``` ### Cleaning Extracted Data Once data has been extracted, it often needs cleaning to make it useful. You may encounter whitespace, non-printable characters, or incomplete data. Python offers several methods to clean the data after extraction. - **Removing Whitespace and Line Breaks:** HTML often includes unnecessary whitespace or new lines that you don’t need in your data. ```py raw_text = driver.find_element(By.ID, 'element-id').text clean_text = raw_text.strip() # Removes leading and trailing whitespace clean_text = ' '.join(clean_text.split()) # Removes extra spaces and line breaks print("Cleaned text:", clean_text) ``` - **Removing Non-Printable Characters**: Sometimes text extracted from web pages may include hidden characters that need to be removed. ```py import re from selenium.webdriver.common.by import By # Extract text from the element try: raw_text = driver.find_element(By.CLASS_NAME, 'element-class').text if raw_text: # Clean the text by removing non-printable characters clean_text = re.sub(r'[^\x20-\x7E]', '', raw_text) print("Cleaned text:", clean_text) else: print("No text found in the element.") except Exception as e: print(f"An error occurred: {e}") ``` **Extracting and Cleaning Multiple Elements**: When extracting data from a list of elements (e.g., product listings, blog posts), it’s important to structure the data efficiently. Here’s an example of extracting and cleaning a list of items from a page: ```py # Extract and clean product names and prices from a product listing page products = driver.find_elements(By.CLASS_NAME, 'product') for product in products: name = product.find_element(By.CLASS_NAME, 'product-name').text.strip() price = product.find_element(By.CLASS_NAME, 'product-price').text.strip() print(f"Product: {name}, Price: {price}") ``` ### Using XPath for Advanced Selection XPath is a powerful language for selecting elements based on complex conditions, such as finding elements based on their text content, attributes, or hierarchical relationships. ```py # Extract elements that contain specific text using XPath elements = driver.find_elements(By.XPATH, "//div[contains(text(), 'Special Offer')]") for element in elements: print("Offer:", element.text) ``` ### Extracting Data from HTML Tables HTML tables are common for displaying structured data, such as product listings, financial data, or rankings. Selenium allows you to navigate the DOM to extract table rows and cells easily. Let's assume you have an HTML table with products and prices, and you want to extract this information. ```py
| Product | Price |
|---|---|
| Product A | $20 |
| Product B | $30 |