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.
- Headless browser detection
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
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
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.