Back to blog

How to Bypass CAPTCHA With Selenium: Best Methods

-
Table of contents
-

Key takeaways

  • CAPTCHA in Selenium is usually a symptom of bot detection.
  • Start with prevention before solving.
  • Use proxies as part of a full identity profile, not as a standalone fix.

CAPTCHA blocks should never feel like a strange exception when running Selenium Python scripts. They usually indicate that the site has noticed signals that make your Selenium session look less like typical human browsing.

That's why you should understand CAPTCHA bypass techniques before a site's bot detection mechanism breaks your workflow. This article walks through the best methods to bypass CAPTCHA Selenium issues, including Selenium Stealth, clean proxy use, and CAPTCHA solver workflows.

Why Selenium triggers CAPTCHA

First, let’s look at why the website you are trying to scrape, or test, showed that annoying “prove you are human” box. Selenium can trigger CAPTCHA blocks when your browser setup, IP address, or browsing behavior starts to look automated.

Here are some of the possible reasons:

  • Browser fingerprinting and WebDriver signals

A browser fingerprint is the set of small details your browser reveals when interacting with a website. CAPTCHA becomes more likely when a session reports itself as Chrome on Windows, but other signals suggest automation, virtualization, or an inconsistent device setup.

Selenium can run Chrome in headless mode or headful mode. Headless mode is useful because the browser runs in the background. However, it can leave signals that websites recognize, such as odd window dimensions, virtual graphics rendering, or limited signs of normal user activity.

  • Unnatural behavior patterns

Real users don’t browse in perfectly even steps. They pause, scroll unevenly, read content, hesitate before clicking, and make small movements. A basic Selenium script opens a page, waits one second, clicks, types instantly, submits, and repeats this pattern across pages, making the session look automated.

  • Weak or overused IPs

Even if your browser setup looks clean, a bad IP address can trigger CAPTCHA before the site looks at your behavior. Free or overused proxies may already be tied to spam or repeated CAPTCHA challenges, so the session starts with a higher risk score.

  • Missing session continuity

Normal browsers store history in cookies, cache, local storage, and previous page paths. Basic Selenium setups start with a new browser profile on every visit, which can make each visit feel disposable. When that clean session also moves quickly or lands directly on important pages, CAPTCHA becomes more likely.

The best CAPTCHA bypass approach is layered. Start by making Selenium harder to detect, then improve proxy quality and behavior patterns, and only use a solver when a challenge still appears.

Method 1: Use a stealthier Selenium browser setup

Now that you know why Selenium triggers CAPTCHA, the next step is to reduce the signals that make your browser look automated in the first place.

undetected-chromedriver

A standard Selenium exposes signals such as:

  • navigator.webdriver === true
  • Missing or incomplete Chrome runtime objects
  • Automation flags in the DevTools Protocol

Instead of standard Selenium, install undetected-chromedriver. It patches parts of Chrome before the browser starts, so the session does not carry some of the most obvious Selenium fingerprints.

undetected-chromedriver can help by:

  • Removing the "cdc_" string from ChromeDriver, which is a known fingerprint
  • Overwriting navigator.webdriver behavior so it returns undefined
  • Adding missing Chrome objects, such as window.chrome and runtime properties
  • Disabling the “Chrome is being controlled by automated test software” infobar

The result is a browser that looks like a normal Chrome instance rather than one controlled by Selenium WebDriver.

Remove obvious automation signals

Tools like Selenium Stealth can help you remove obvious automation signals. Selenium Stealth is a Python package that reduces the likelihood of Chrome/Chromium being detected as a bot when controlled by Selenium. It does this by modifying browser fingerprint properties to prevent automation leaks.

The most common signals include:

  • navigator.webdriver === true
  • Missing window.chrome object in Chrome or Edge
  • Incomplete plugins and languages
  • Missing or abnormal navigator.connection
  • Chrome DevTools Protocol flags
  • The cdc_ string in ChromeDriver
  • User agent and platform mismatches

Method 2: Improve IP reputation with proxies

Once you’ve set up a stealthier Selenium browser, the next step is to pair it with reputable proxies. IP quality matters because websites can log addresses that have been used for scraping, spam, failed login attempts, or repeated CAPTCHA challenges.

If your Selenium session comes from an IP address that has already been flagged, you may keep hitting CAPTCHAs even after cleaning up your browser settings.

Residential vs datacenter vs mobile proxies

You will usually come across three options when choosing proxies for Selenium: residential, datacenter, and mobile proxies.

Residential proxies are particularly effective for avoiding CAPTCHAs because they route requests through real user devices. This makes them appear more legitimate to your target websites.

Use residential proxies when:

  • You are scraping websites with stricter bot detection
  • You need traffic that looks closer to normal user browsing
  • You are working with location-sensitive pages
  • You want to reduce the risk of repeated Google reCAPTCHA challenges
  • You need a better balance between trust and scalability

Datacenter proxies, while fast and affordable, are easier for websites to detect and block compared to residential proxies. Using them on vigilant websites can be counteractive, triggering more frequent CAPTCHA challenges.

Use datacenter proxies when:

  • You are scraping less-protected websites
  • You need faster response times
  • You are collecting public pages at a controlled pace
  • You are testing Selenium code before using higher-trust proxies
  • You don’t need the IP to look like residential traffic

Mobile proxies use IPs assigned by mobile carriers, which makes them look highly trusted. Use them when:

  • You are working with mobile-first websites
  • You need carrier-based IPs for location testing
  • Residential proxies are still being challenged too often
  • The target site is sensitive to datacenter traffic

Rotating vs sticky sessions

You can choose to rotate your proxies or use a single IP for a set duration, and that choice has a direct effect on whether your setup triggers a CAPTCHA. Using proxies to rotate IP addresses can help reduce the likelihood of triggering CAPTCHA challenges during web scraping, as it allows the scraper to appear as multiple organic users.

On top of that, your IP should be part of a larger behavior profile. When you rotate that IP, rotate everything else in that profile too. Otherwise, the session becomes inconsistent and easier to detect.

Here is what belongs in a complete profile:

  • Time zone
  • User agent
  • Viewport size
  • Hardware concurrency (navigator.hardwareConcurrency)
  • Touch points (navigator.maxTouchPoints)
  • Language and Accept-Language headers
  • WebGL vendor string
  • WebGL renderer string
  • Device memory (navigator.deviceMemory)

Rotating your proxy while leaving all these fingerprints intact will almost certainly trigger a CAPTCHA. Rotation works best for repetitive tasks such as Selenium web scraping, where spreading requests across multiple IPs can reduce pressure on a single address. Sticky sessions are better when logging into platforms or performing actions where a normal user would be expected to maintain a consistent identity.

Method 3: Improve session and behavior management

The way your Selenium script operates can impact how often you hit a CAPTCHA. All those tiny adjustments in how you handle sessions and navigate through pages add up.

Here are some specific tricks to help your scraper blend in a bit more.

Reuse those cookies and sessions

First, try reusing cookies and sessions across runs of your script. A browser that shows up with a familiar set of cookies looks a lot less like a brand-new disposable browser. Use these functions:

  • save_cookies(driver, filename) writes all cookies to a JSON file
  • load_cookies(driver, filename) reads that file back and adds the cookies to the browser before you navigate to the target domain

Your Selenium script should also use a persistent user data directory when it starts the browser via undetected-chromedriver. That way, you get to keep local storage, session storage, and all the other browser state intact from one run to the next.

Avoid repeated cold starts

A cold start happens when you fire up a brand-new browser session without any prior activity on the target site. Websites notice this.

A browser that just appears out of the blue, with no stored cookies, cache, or history, can look like a brand-new scraper getting set up, which on its own can trigger CAPTCHA pop-ups. The better approach is to keep your browser instance open and alive across multiple requests. If your Selenium WebDriver session just keeps going, the site thinks you're a real visitor popping in again.

When you need to restart the browser, do a little warm-up session first, don't just head straight to the page you want to scrape. Spend a minute or two browsing some neutral site - a news homepage, a Wikipedia article, etc.

Have the browser scroll around a bit to let the analytics scripts run. Then navigate to the domain you actually want to scrape. That warm-up period helps fill in some of the missing signals that bot detection systems look for.

Use realistic waits and navigation patterns

Writing time.sleep(2) has an automation giveaway written all over it. Your Selenium script pauses for exactly two seconds every single time. Let's be honest, no human does that.

We'd probably glance at our phone, maybe even click right away, because we already know where that button is. The thing is, humans are inconsistent, and your script should be too.

Here is what you should do:

  • Swap out fixed waits for some random timing. A wait like random.uniform(1.5, 8.0) feels a lot more natural than a hard stop at 2 seconds.
  • For elements that take a while to load, try using WebDriverWait with a flexible timeout. Add a small random jitter to the polling interval. Do not check every 0.1 seconds on the dot. Vary it.
  • Also, your Selenium script shouldn't click every link or button in the same old order every single run. Be unpredictable - randomize the sequence. If you're on a search results page, try having your script type out the search term with a few typos, then correct them. Sometimes it's okay to click the wrong thing, hit the back button, and try again.

Lower request volume and retry spikes

The single biggest factor in CAPTCHA avoidance is the number of requests you send over time. A low, steady rate is safer than bursts of activity.

If you need to scrape a large set of pages, spread the work across hours or days. Add random intervals between requests. For a site with strict bot detection systems, staying under 10 requests per minute per IP is a reasonable starting point.

Retry spikes are especially dangerous. When a request fails, many scripts retry immediately two or three times. That spike in traffic looks like an automated hammer.

Instead, implement exponential backoff. Wait five seconds after a first failure, 15 seconds after a second, then 45 seconds. If a proxy or session fails repeatedly, rotate to a fresh identity instead of using the same one.

Method 4: Use a CAPTCHA solver service

So far, the methods we have covered are about playing defense. Their goal is to reduce the signals that make your Selenium WebDriver session look automated, so you can avoid CAPTCHAs before they interrupt your workflow.

But what happens when your script still triggers a CAPTCHA?

That is where a CAPTCHA solver service comes in. This method gives your script a way to handle CAPTCHA challenges in Selenium after they appear.

Here is how the process usually works:

  • Step 1: Your script encounters a CAPTCHA
  • Step 2: You send the challenge to the solver service
  • Step 3: The service solves it using a human workforce or an automated solving system
  • Step 4: Your script receives the solution
  • Step 5: Your script submits the solution and continues the session

The important thing is to understand how these tools work before adding them to your Selenium setup.

Token-based vs interactive solving

There are two kinds of CAPTCHA challenges that your Selenium Python script might run up against while web scraping: the token-based type and the interactive type.

  • Token-based solving is typical with modern CAPTCHA systems like reCAPTCHA and hCaptcha. Here, the target website is more interested in whether the script has a valid token before deciding what to do next. Your script sends the CAPTCHA details to the API. The API then sends a token back to the script, which it can use to continue the session.
  • Interactive, click-based solving is a different story. Some of the more advanced CAPTCHAs need the user to click on parts of an image, move a slider, or do another interactive task. When your script runs into this kind of challenge, it sends the challenge details to the API. The API then returns the coordinates or a list of actions needed to complete the job.

Supported challenge types

The range of supported challenges can vary from one service provider to another, but most reputable CAPTCHA-solving services, like CapSolver, cover the common CAPTCHA types users encounter during Selenium automation.

  • Google reCAPTCHA v2 includes the familiar “I’m not a robot” checkbox. After the user clicks the checkbox, the system may allow access immediately or trigger an additional image-grid challenge if it is uncertain about the user’s identity. These follow-up challenges ask users to identify objects such as traffic lights, buses, crosswalks, or bicycles.
  • Google reCAPTCHA v3 is an invisible, score-based system that works in the background without requiring direct user interaction. Instead of showing a checkbox or image challenge, it monitors behavioral signals and assigns a score that helps the website decide whether the visitor appears human or automated. For a deeper breakdown of how this scoring system works, see our guide on how to bypass reCAPTCHA v3.
  • hCaptcha is a privacy-focused alternative to reCAPTCHA. It often presents image classification challenges and uses its own detection algorithms to evaluate whether the visitor is human. Many websites use hCaptcha as a replacement for Google’s reCAPTCHA system.
  • Image CAPTCHAs are the old-school text-from-image challenges where users must type distorted letters, numbers, or words correctly. They are less common on modern websites but still appear on some forms, login pages, and older platforms.
  • Audio CAPTCHAs provide an alternative for visually impaired users. Instead of solving a visual challenge, users listen to spoken characters, numbers, or words and type what they hear.
  • Cloudflare Turnstile is a CAPTCHA alternative that relies on token validation and background checks rather than traditional puzzle-solving. In many cases, it verifies visitors with little or no visible user interaction.

The trade-offs of using CAPTCHA solvers

Aspect
Reality and trade-off

Cost

CAPTCHA solvers are usually inexpensive per solve, but the cost depends on the challenge type. Simple image CAPTCHAs are cheaper, while reCAPTCHA v2, reCAPTCHA Enterprise, FunCaptcha, and similar challenges usually cost more.

Latency

Solving takes time. Some challenges may be solved in a few seconds, while others can take 20 to 30 seconds or longer. This can slow down a Selenium workflow, especially at scale.

Reliability

Solver services are not perfect. Simple challenges tend to have higher success rates, while complex image grids or interactive challenges can fail more often. Timeouts and API errors can also happen.

Scalability

Most solver APIs can handle parallel requests, but your script still needs retry logic and fallback handling when a solve fails or takes too long

A step-by-step workflow for CAPTCHA in Selenium

Working around CAPTCHAs in Selenium requires a multi-layered approach, including architectural changes for testing and integrating specialized AI solving services for production.

Now, let's go through a practical workflow for creating a Selenium script that aims to avoid CAPTCHAs from day one.

Getting started with a stealth browser setup

Here's a basic script that sets up a stealth browser using undetected_chromedriver and some injected CDP scripts.

import undetected_chromedriver as uc


def launch_stealth_browser():

    options = uc.ChromeOptions()

    options.add_argument("--disable-blink-features=AutomationControlled")

    options.add_argument("--window-size=1366,768")

    options.add_argument("--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/121.0.0.0 Safari/537.36")

    

    driver = uc.Chrome(options=options)

    

    # Inject stealth scripts before any page loads

    stealth_script = """

    Object.defineProperty(navigator, 'webdriver', { get: () => undefined });

    window.chrome = { runtime: {} };

    Object.defineProperty(navigator, 'plugins', { get: () => [1,2,3,4,5] });

    Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] });

    """

    driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {"source": stealth_script})

    

    # Override timezone and geolocation

    driver.execute_cdp_cmd("Emulation.setTimezoneOverride", {"timezoneId": "Europe/London"})

    driver.execute_cdp_cmd("Emulation.setGeolocationOverride", {"latitude": 51.5074, "longitude": -0.1278, "accuracy": 100})

    

    return driver


# Usage

driver = launch_stealth_browser()

driver.get("https://bot.sannysoft.com")

input("Check the page for automation flags, then press Enter to quit.")

driver.quit()

Add behavior tuning and session persistence

A stealth browser alone is not enough. Your script also needs to act like a person. That means reusing cookies, typing with natural delays, and adding random pauses.

Here is a basic script that shows how these pieces fit together.

import json

import random

import time

from selenium.webdriver.common.by import By

from selenium.webdriver.common.keys import Keys


def save_cookies(driver, filename):

    with open(filename, "w") as f:

        json.dump(driver.get_cookies(), f)


def load_cookies(driver, filename):

    driver.get("https://example.com")  # Navigate to domain first

    with open(filename, "r") as f:

        cookies = json.load(f)

    for cookie in cookies:

        driver.add_cookie(cookie)


def human_type(element, text):

    element.click()

    # Clear field with random backspaces

    for _ in range(random.randint(2, 8)):

        element.send_keys(Keys.BACKSPACE)

        time.sleep(random.uniform(0.03, 0.1))

    

    for char in text:

        # 7% chance of hesitation

        if random.random() < 0.07:

            time.sleep(random.uniform(0.2, 0.6))

        element.send_keys(char)

        time.sleep(random.uniform(0.08, 0.3))


def random_wait(min_sec=1, max_sec=5):

    time.sleep(random.uniform(min_sec, max_sec))


# Example usage

driver = launch_stealth_browser()  # from previous example

driver.get("https://google.com")

random_wait(2, 4)


search_box = driver.find_element(By.NAME, "q")

human_type(search_box, "hello world")

random_wait(1, 2)

search_box.send_keys(Keys.RETURN)


# Save cookies after session

save_cookies(driver, "my_cookies.json")

driver.quit()


# Next run: load cookies

driver = launch_stealth_browser()

load_cookies(driver, "my_cookies.json")

driver.refresh()

Add proxies if CAPTCHA still appears

Do not simply plug a proxy into your script and expect better results. Each IP address needs a matching identity profile. That means a unique user agent, viewport size, timezone, WebGL vendor, and language header. Here is a code snippet to show you how it is done:

import undetected_chromedriver as uc

import random

import time

from selenium.webdriver.common.by import By


# Proxy pool (residential proxies with auth)

PROXIES = [

    "http://user1:[email protected]:8080",

    "http://user2:[email protected]:8080",

]


# Simple identity profiles (just user agent and viewport for demo)

IDENTITIES = [

    {"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/121", "viewport": (1366,768)},

    {"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Safari/605", "viewport": (1440,900)},

]


def assign_proxy_and_identity():

    proxy = random.choice(PROXIES)

    identity = random.choice(IDENTITIES)

    return proxy, identity


def launch_browser_with_proxy(proxy, identity):

    options = uc.ChromeOptions()

    options.add_argument(f"--proxy-server={proxy}")

    options.add_argument(f"--window-size={identity['viewport'][0]},{identity['viewport'][1]}")

    options.add_argument(f"--user-agent={identity['user_agent']}")

    options.add_argument("--disable-blink-features=AutomationControlled")

    return uc.Chrome(options=options)


def captcha_detected(driver):

    # Basic detection – look for keywords in page text

    body = driver.find_element(By.TAG_NAME, "body").text.lower()

    indicators = ["captcha", "verify you are human", "recaptcha"]

    return any(ind in body for ind in indicators)


# Main retry loop

MAX_RETRIES = 3

for attempt in range(MAX_RETRIES):

    proxy, identity = assign_proxy_and_identity()

    print(f"Attempt {attempt+1}: Proxy={proxy.split('@')[1]}, Identity={identity['user_agent'][:30]}...")

    

    driver = launch_browser_with_proxy(proxy, identity)

    driver.get("https://target-site-that-may-show-captcha.com")

    time.sleep(3)

    

    if captcha_detected(driver):

        print("CAPTCHA detected. Rotating proxy and identity...")

        driver.quit()

        wait_time = (attempt + 1) * 10  # exponential backoff

        print(f"Waiting {wait_time} seconds before retry.")

        time.sleep(wait_time)

        continue  # next attempt with new proxy/identity

    

    # No CAPTCHA – proceed with scraping

    print("No CAPTCHA. Scraping successful.")

    # ... do your work ...

    driver.quit()

    break

else:

    print("All attempts failed due to CAPTCHA.")

Add a solver only when needed

Now, if you've implemented the undetected-chromedriver, behavioral changes, and added proxies, but your target is still asking your script to solve a CAPTCHA, you can fall back to the script below:

import time

import requests

from selenium.webdriver.common.by import By

from selenium.webdriver.support.ui import WebDriverWait

from selenium.webdriver.support import expected_conditions as EC


# === CONFIGURATION ===

CAPTCHA_SOLVER_API_KEY = "YOUR_API_KEY_HERE"

CAPTCHA_SOLVER_URL = "https://api.your-solver-service.com/in.php"  # Example endpoint

CAPTCHA_SOLVER_RESULT_URL = "https://api.your-solver-service.com/res.php"


# === CAPTCHA DETECTION ===

def is_captcha_present(driver):

    """Return True if a CAPTCHA iframe or text is detected."""

    try:

        # Check for common CAPTCHA elements

        if driver.find_elements(By.CSS_SELECTOR, "iframe[src*='recaptcha'], .g-recaptcha"):

            return True

        body_text = driver.find_element(By.TAG_NAME, "body").text.lower()

        indicators = ["captcha", "verify you are human", "recaptcha"]

        return any(ind in body_text for ind in indicators)

    except:

        return False


# === SOLVER INTEGRATION ===

def solve_captcha(driver):

    """

    Send the CAPTCHA challenge to a solver service and return the solution token.

    This example assumes a token‑based solver (reCAPTCHA v2/v3).

    """

    # Step 1: Extract the site key (for reCAPTCHA) – adjust selector as needed

    site_key = None

    try:

        # Look for the data-sitekey attribute on a div or iframe

        captcha_element = driver.find_element(By.CSS_SELECTOR, ".g-recaptcha")

        site_key = captcha_element.get_attribute("data-sitekey")

    except:

        # Fallback: some pages use a different structure

        try:

            iframe = driver.find_element(By.CSS_SELECTOR, "iframe[src*='recaptcha']")

            # You may need to parse the src URL to get site key, but simplified here

            pass

        except:

            pass


    if not site_key:

        raise Exception("Could not find site key for CAPTCHA")


    # Step 2: Send the challenge to the solver service

    payload = {

        "key": CAPTCHA_SOLVER_API_KEY,

        "method": "userrecaptcha",

        "googlekey": site_key,

        "pageurl": driver.current_url,

        "json": 1

    }

    response = requests.post(CAPTCHA_SOLVER_URL, data=payload)

    result = response.json()

    if result.get("status") != 1:

        raise Exception(f"Solver submission failed: {result}")


    request_id = result.get("request")

    print(f"CAPTCHA submitted, request ID: {request_id}")


    # Step 3: Poll for the solution (wait up to 60 seconds)

    for _ in range(30):  # 30 * 2 seconds = 60 seconds max

        time.sleep(2)

        poll_payload = {

            "key": CAPTCHA_SOLVER_API_KEY,

            "action": "get",

            "id": request_id,

            "json": 1

        }

        poll_resp = requests.get(CAPTCHA_SOLVER_RESULT_URL, params=poll_payload)

        poll_data = poll_resp.json()

        if poll_data.get("status") == 1:

            token = poll_data.get("request")

            print(f"CAPTCHA solved, token received: {token[:20]}...")

            return token

        elif poll_data.get("request") == "CAPCHA_NOT_READY":

            continue

        else:

            raise Exception(f"Solver error: {poll_data}")


    raise Exception("Timeout waiting for CAPTCHA solution")


# === INJECT THE SOLUTION ===

def inject_captcha_token(driver, token):

    """Inject the solution token into the page and submit."""

    # For reCAPTCHA, the token is usually injected into a hidden textarea

    try:

        driver.execute_script(f"document.getElementById('g-recaptcha-response').innerHTML = '{token}';")

    except:

        pass

    # Then submit the form (or call the callback)

    try:

        submit_button = driver.find_element(By.CSS_SELECTOR, "button[type='submit'], input[type='submit']")

        submit_button.click()

    except:

        # Some pages need to trigger the callback manually

        driver.execute_script("onSuccessCaptcha(arguments[0]);", token)

    time.sleep(3)  # wait for page to process


# === MAIN WORKFLOW WITH FALLBACK ===

def scrape_with_captcha_fallback(driver):

    """

    Perform your scraping. If a CAPTCHA appears, solve it and continue.

    """

    # ... your navigation and interaction code ...

    # After each major action, check for CAPTCHA

    if is_captcha_present(driver):

        print("CAPTCHA detected. Attempting to solve...")

        try:

            solution = solve_captcha(driver)

            inject_captcha_token(driver, solution)

            # Wait for the page to reload and re-check

            time.sleep(5)

            if is_captcha_present(driver):

                print("CAPTCHA still present after solving. Might need a retry.")

                return False

            else:

                print("CAPTCHA solved successfully. Continuing.")

        except Exception as e:

            print(f"CAPTCHA solving failed: {e}")

            return False

    # Proceed with scraping

    print("No CAPTCHA blocking – extracting data...")

    # ... your data extraction ...

    return True


# === EXAMPLE USAGE ===

if __name__ == "__main__":

    # Assume driver is already set up with stealth options

    from your_stealth_setup import launch_stealth_browser  # hypothetical import

    driver = launch_stealth_browser()

    driver.get("https://example.com/page-that-may-show-captcha")

    success = scrape_with_captcha_fallback(driver)

    if not success:

        print("Failed to bypass CAPTCHA. Consider rotating identity or proxy.")

    driver.quit()

Measure CAPTCHA rates and failures

At some point, even the best Selenium script is going to struggle with CAPTCHA prompts, especially if you're running your script at scale. That's why keeping an eye on failure rates is important. By tracking this data, you can fine-tune your retry logic and work out where your setup is letting you down.

The code snippet below logs each request: timestamp, the URL, whether a CAPTCHA appeared, and if so, whether the solver was successful, or if there was an error. It stores all this in a CSV file so you can go back and take a closer look later.

import csv

import time

import random

from datetime import datetime


class CAPTCHAMetrics:

    def __init__(self, filename="captcha_metrics.csv"):

        self.filename = filename

        self.stats = {

            "total_requests": 0,

            "captcha_encountered": 0,

            "captcha_solved": 0,

            "captcha_failed": 0,

            "other_errors": 0

        }

        # Write header if file doesn't exist

        try:

            with open(self.filename, 'x') as f:

                writer = csv.writer(f)

                writer.writerow(["timestamp", "url", "captcha_detected", "solver_used", 

                                 "solver_success", "time_to_solve_sec", "error_message"])

        except FileExistsError:

            pass


    def log_request(self, url, captcha_detected=False, solver_used=False, 

                    solver_success=False, time_to_solve=None, error=None):

        self.stats["total_requests"] += 1

        if captcha_detected:

            self.stats["captcha_encountered"] += 1

            if solver_success:

                self.stats["captcha_solved"] += 1

            else:

                self.stats["captcha_failed"] += 1

        if error:

            self.stats["other_errors"] += 1


        with open(self.filename, 'a', newline='') as f:

            writer = csv.writer(f)

            writer.writerow([

                datetime.now().isoformat(),

                url,

                captcha_detected,

                solver_used,

                solver_success,

                time_to_solve if time_to_solve else "",

                error if error else ""

            ])


    def get_captcha_rate(self):

        if self.stats["total_requests"] == 0:

            return 0

        return (self.stats["captcha_encountered"] / self.stats["total_requests"]) * 100


    def print_summary(self):

        print("\n=== CAPTCHA Metrics Summary ===")

        print(f"Total requests: {self.stats['total_requests']}")

        print(f"CAPTCHA encountered: {self.stats['captcha_encountered']}")

        print(f"CAPTCHA solved: {self.stats['captcha_solved']}")

        print(f"CAPTCHA failed: {self.stats['captcha_failed']}")

        print(f"Other errors: {self.stats['other_errors']}")

        print(f"CAPTCHA rate: {self.get_captcha_rate():.2f}%")

        if self.stats['captcha_encountered'] > 0:

            solve_rate = (self.stats['captcha_solved'] / self.stats['captcha_encountered']) * 100

            print(f"Solve success rate: {solve_rate:.2f}%")

        print("=============================\n")

Selenium CAPTCHA troubleshooting

Problem
Solution

CAPTCHA only appears in headless mode

Stay in headed mode for critical sites. If headless is required, use --headless=new (Chrome 109+), add a virtual display like Xvfb, and inject CDP scripts to override WebGL and canvas fingerprints.

CAPTCHA increases after scaling requests

Add random delays between requests. Spread workload across more IPs. Reduce per-IP request rate. Check if CAPTCHAs appear immediately (fingerprint issue) or after a few minutes (rate limit).

Solver returns token, but the challenge still fails

Manually solve the CAPTCHA while watching browser devtools. Note which fields change and which JavaScript functions run. Replicate those exact steps in your script. Test with headed mode first.

Proxy rotation makes things worse

Rotate the entire identity together – user agent, viewport, timezone, language, WebGL strings – not just the IP. Keep the same IP and fingerprint combination for several requests before rotating.

Browser looks normal locally, but fails in production

Use residential proxies on your production server. Ensure the server has common fonts installed. Run headed mode with a virtual display like Xvfb, or inject CDP scripts to fake missing GPU features.

Conclusion

Bypassing CAPTCHA with Selenium is a step-by-step process. First, try to avoid CAPTCHAs altogether. Use undetected-chromedriver for a stealthier browser setup and add human behavior techniques like random waits, varied scrolling and exponential backoff when retries fail.

Next, add proxies and make sure each IP address has its own complete identity profile, and rotate the whole set together, not just the IP alone. When all else fails, bring in a solver service as a fallback. Your script should detect the CAPTCHA, send it to the API, and inject the returned token.

And finally, track everything. Measure your CAPTCHA rate and failure patterns because, without data, you can’t tell which part of your setup needs attention.

Looking for more tips like this? Join our Discord channel.

FAQ

Can Selenium bypass CAPTCHA by itself?

No, Selenium alone can’t bypass CAPTCHA. A standard Selenium WebDriver session actually triggers more CAPTCHAs because it leaks automation signals like navigator.webdriver and missing Chrome objects. You need more.

Does undetected-chromedriver prevent CAPTCHA in Selenium?

It reduces CAPTCHA rates but doesn’t prevent them. undetected-chromedriver hides many automation fingerprints like the cdc_ string and navigator.webdriver flag. But behavioral patterns and IP reputation still trigger challenges sometimes.

Why does CAPTCHA appear more often in headless mode?

Headless mode uses software rendering like SwiftShader instead of a real GPU. It also defaults to 800x600 window size and misses normal fonts. Websites detect these differences and serve more CAPTCHAs to headless browsers.

What is the best proxy type for Selenium CAPTCHA issues?

Residential proxies are best because they come from real ISP addresses, not datacenter ranges. Datacenter IPs are easily flagged as non-residential traffic. Mobile proxies also work well, but cost more and have different geolocation characteristics.

When should I use a CAPTCHA solver with Selenium?

Use a solver only as a fallback after stealth setup, human behavior patterns, and residential proxies have failed. Don’t rely on solvers first - they add latency and cost. Reserve them for occasional challenges that bypass your primary defenses.

Learn more
-

Related articles