# How to Bypass CAPTCHAs in Web Scraping

[Web Scraping](/blog/categories/web-scraping)
5 May 2025
8 min read

## What Are CAPTCHAs and Why Do Websites Use Them?

CAPTCHAs (Completely Automated Public Turing Test to Tell Computers and Humans Apart) are security measures designed to prevent automated access to websites. While they serve important security purposes, they can pose challenges for legitimate web scraping operations.

**Quick answer:** Handle CAPTCHAs by preventing them first, using human-like browsing patterns, session management and proxy rotation. When solving is unavoidable, third-party services such as 2captcha can solve image CAPTCHAs via API. Always review the website's terms of service and robots.txt, apply rate limiting, and consider API access before scraping.

### Types of CAPTCHAs

- **Text-based CAPTCHAs:** Distorted text that users must read and type
- **Image CAPTCHAs:** Select images matching specific criteria
- **Audio CAPTCHAs:** Audio challenges for accessibility
- **reCAPTCHA:** Google's advanced CAPTCHA system
- **hCaptcha:** Privacy-focused alternative to reCAPTCHA
- **Invisible CAPTCHAs:** Background behaviour analysis

## Ethical Considerations

### Legal and Ethical Framework

Before implementing CAPTCHA handling techniques, consider:

- **Terms of Service:** Review website terms regarding automated access
- **robots.txt:** Respect site crawling guidelines
- **Rate Limiting:** Avoid overwhelming servers
- **Data Usage:** Ensure compliance with data protection laws
- **Business Purpose:** Have legitimate reasons for data collection

### Ethical scraping guidelines

- Contact website owners for API access when possible
- Implement respectful delays between requests
- Use proper user agents and headers
- Avoid scraping personal or sensitive data
- Consider the impact on website performance

## How Can You Avoid Triggering CAPTCHAs?

### Avoiding CAPTCHAs Through Good Practices

The best approach to CAPTCHA handling is prevention:

#### 1. Behavioural Mimicking

```
import random
import time
from selenium import webdriver

def human_like_browsing():
    driver = webdriver.Chrome()
    
    # Random delays between actions
    def random_delay():
        time.sleep(random.uniform(1, 3))
    
    # Simulate human scrolling
    def scroll_slowly():
        total_height = driver.execute_script("return document.body.scrollHeight")
        for i in range(1, int(total_height/100)):
            driver.execute_script(f"window.scrollTo(0, {i*100});")
            time.sleep(random.uniform(0.1, 0.3))
    
    # Mouse movement patterns
    def random_mouse_movement():
        from selenium.webdriver.common.action_chains import ActionChains
        actions = ActionChains(driver)
        
        # Random cursor movements
        for _ in range(random.randint(2, 5)):
            x_offset = random.randint(-50, 50)
            y_offset = random.randint(-50, 50)
            actions.move_by_offset(x_offset, y_offset)
            actions.perform()
            time.sleep(random.uniform(0.1, 0.5))

# Usage example
def scrape_with_human_behavior(url):
    driver = webdriver.Chrome()
    driver.get(url)
    
    # Simulate reading time
    time.sleep(random.uniform(3, 7))
    
    # Random scrolling
    scroll_slowly()
    
    # Random mouse movements
    random_mouse_movement()
    
    # Extract data after human-like interaction
    data = driver.find_element("tag", "content").text
    
    driver.quit()
    return data
```

#### 2. Session Management

```
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

class SessionManager:
    def __init__(self):
        self.session = requests.Session()
        self.setup_session()
    
    def setup_session(self):
        # Retry strategy
        retry_strategy = Retry(
            total=3,
            backoff_factor=1,
            status_forcelist=[429, 500, 502, 503, 504],
        )
        
        adapter = HTTPAdapter(max_retries=retry_strategy)
        self.session.mount("http://", adapter)
        self.session.mount("https://", adapter)
        
        # Human-like headers
        self.session.headers.update({
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
            'Accept-Language': 'en-US,en;q=0.5',
            'Accept-Encoding': 'gzip, deflate',
            'Connection': 'keep-alive',
        })
    
    def get_with_delay(self, url, delay_range=(1, 3)):
        time.sleep(random.uniform(*delay_range))
        return self.session.get(url)
```

#### 3. Proxy Rotation

```
import itertools
import random

class ProxyRotator:
    def __init__(self, proxy_list):
        self.proxies = itertools.cycle(proxy_list)
        self.current_proxy = None
        self.failed_proxies = set()
    
    def get_proxy(self):
        """Get next working proxy"""
        for _ in range(len(self.proxy_list)):
            proxy = next(self.proxies)
            if proxy not in self.failed_proxies:
                self.current_proxy = proxy
                return {
                    'http': f'http://{proxy}',
                    'https': f'https://{proxy}'
                }
        
        # If all proxies failed, reset and try again
        self.failed_proxies.clear()
        return self.get_proxy()
    
    def mark_proxy_failed(self):
        """Mark current proxy as failed"""
        if self.current_proxy:
            self.failed_proxies.add(self.current_proxy)
    
    def test_proxy(self, proxy_dict):
        """Test if proxy is working"""
        try:
            response = requests.get(
                'http://httpbin.org/ip', 
                proxies=proxy_dict, 
                timeout=10
            )
            return response.status_code == 200
        except:
            return False
```

## CAPTCHA Detection

### Identifying CAPTCHA Presence

```
from selenium.webdriver.common.by import By
from selenium.common.exceptions import NoSuchElementException

def detect_captcha(driver):
    """Detect various types of CAPTCHAs"""
    captcha_indicators = [
        # reCAPTCHA
        (By.CLASS_NAME, "g-recaptcha"),
        (By.ID, "g-recaptcha"),
        (By.XPATH, "//iframe[contains(@src, 'recaptcha')]"),
        
        # hCaptcha
        (By.CLASS_NAME, "h-captcha"),
        (By.XPATH, "//iframe[contains(@src, 'hcaptcha')]"),
        
        # Generic CAPTCHA indicators
        (By.XPATH, "//*[contains(text(), 'captcha')]"),
        (By.XPATH, "//*[contains(text(), 'CAPTCHA')]"),
        (By.XPATH, "//img[contains(@alt, 'captcha')]"),
        
        # Common form names
        (By.NAME, "captcha"),
        (By.ID, "captcha"),
        (By.CLASS_NAME, "captcha"),
    ]
    
    for locator_type, locator_value in captcha_indicators:
        try:
            element = driver.find_element(locator_type, locator_value)
            if element.is_displayed():
                return True, locator_type, locator_value
        except NoSuchElementException:
            continue
    
    return False, None, None

# Usage
def check_for_captcha_and_handle(driver):
    has_captcha, locator_type, locator_value = detect_captcha(driver)
    
    if has_captcha:
        print(f"CAPTCHA detected: {locator_type} = {locator_value}")
        # Implement handling strategy here
        return True
    
    return False
```

## How Do Automated CAPTCHA Solving Services Work?

### Third-Party CAPTCHA Solving Services

When legitimate automation requires CAPTCHA solving:

#### Popular Services

- **2captcha:** Supports most CAPTCHA types
- **Anti-Captcha:** High success rates
- **DeathByCaptcha:** Established service
- **CapMonster:** Software-based solution

#### Implementation Example

```
import base64
import time
import requests

class CaptchaSolver:
    def __init__(self, api_key, service_url):
        self.api_key = api_key
        self.service_url = service_url
    
    def solve_image_captcha(self, image_path):
        """Solve image-based CAPTCHA"""
        
        # Encode image
        with open(image_path, 'rb') as f:
            image_data = base64.b64encode(f.read()).decode()
        
        # Submit CAPTCHA
        submit_url = f"{self.service_url}/in.php"
        data = {
            'key': self.api_key,
            'method': 'base64',
            'body': image_data
        }
        
        response = requests.post(submit_url, data=data)
        
        if response.text.startswith('OK|'):
            captcha_id = response.text.split('|')[1]
            return self.get_captcha_result(captcha_id)
        else:
            raise Exception(f"CAPTCHA submission failed: {response.text}")
    
    def get_captcha_result(self, captcha_id):
        """Poll for CAPTCHA solution"""
        result_url = f"{self.service_url}/res.php"
        
        for _ in range(30):  # Wait up to 5 minutes
            time.sleep(10)
            
            response = requests.get(result_url, params={
                'key': self.api_key,
                'action': 'get',
                'id': captcha_id
            })
            
            if response.text == 'CAPCHA_NOT_READY':
                continue
            elif response.text.startswith('OK|'):
                return response.text.split('|')[1]
            else:
                raise Exception(f"CAPTCHA solving failed: {response.text}")
        
        raise Exception("CAPTCHA solving timeout")

# Usage
def solve_captcha_if_present(driver):
    has_captcha, _, _ = detect_captcha(driver)
    
    if has_captcha:
        # Take screenshot of CAPTCHA
        captcha_element = driver.find_element(By.CLASS_NAME, "captcha-image")
        captcha_element.screenshot("captcha.png")
        
        # Solve CAPTCHA
        solver = CaptchaSolver("your_api_key", "https://2captcha.com")
        solution = solver.solve_image_captcha("captcha.png")
        
        # Input solution
        captcha_input = driver.find_element(By.NAME, "captcha")
        captcha_input.send_keys(solution)
        
        return True
    
    return False
```

## Advanced Techniques

### reCAPTCHA v2 Handling

```
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

def handle_recaptcha_v2(driver):
    """Handle reCAPTCHA v2 checkbox"""
    try:
        # Wait for reCAPTCHA iframe to load
        wait = WebDriverWait(driver, 10)
        
        # Switch to reCAPTCHA iframe
        recaptcha_iframe = wait.until(
            EC.presence_of_element_located((By.XPATH, "//iframe[contains(@src, 'recaptcha')]"))
        )
        driver.switch_to.frame(recaptcha_iframe)
        
        # Click the checkbox
        checkbox = wait.until(
            EC.element_to_be_clickable((By.ID, "recaptcha-anchor"))
        )
        checkbox.click()
        
        # Switch back to main content
        driver.switch_to.default_content()
        
        # Wait for challenge to complete or appear
        time.sleep(2)
        
        # Check if challenge appeared
        try:
            challenge_iframe = driver.find_element(By.XPATH, "//iframe[contains(@src, 'bframe')]")
            if challenge_iframe.is_displayed():
                print("reCAPTCHA challenge appeared - manual intervention needed")
                return False
        except NoSuchElementException:
            pass
        
        return True
        
    except Exception as e:
        print(f"reCAPTCHA handling failed: {e}")
        return False
```

### Invisible reCAPTCHA

Invisible reCAPTCHAs analyse user behaviour. Key strategies:

- **Mouse Movement:** Simulate natural cursor patterns
- **Keyboard Timing:** Vary typing speeds and patterns
- **Scroll Behaviour:** Implement human-like scrolling
- **Page Interaction:** Click on non-essential elements

## Monitoring and Debugging

### CAPTCHA Detection Logging

```
import logging
from datetime import datetime

class CaptchaLogger:
    def __init__(self):
        logging.basicConfig(
            level=logging.INFO,
            format='%(asctime)s - %(levelname)s - %(message)s',
            handlers=[
                logging.FileHandler('captcha_log.txt'),
                logging.StreamHandler()
            ]
        )
        self.logger = logging.getLogger(__name__)
    
    def log_captcha_encounter(self, url, captcha_type):
        self.logger.info(f"CAPTCHA encountered: {captcha_type} at {url}")
    
    def log_captcha_solved(self, url, solve_time):
        self.logger.info(f"CAPTCHA solved in {solve_time}s at {url}")
    
    def log_captcha_failed(self, url, error):
        self.logger.error(f"CAPTCHA solving failed at {url}: {error}")

# Usage in scraping script
logger = CaptchaLogger()

def scrape_with_captcha_logging(url):
    driver = webdriver.Chrome()
    driver.get(url)
    
    if check_for_captcha_and_handle(driver):
        logger.log_captcha_encounter(url, "reCAPTCHA")
        
        start_time = time.time()
        success = solve_captcha_if_present(driver)
        solve_time = time.time() - start_time
        
        if success:
            logger.log_captcha_solved(url, solve_time)
        else:
            logger.log_captcha_failed(url, "Solution timeout")
```

## Legal and Compliance Considerations

### UK Legal Framework

- **Computer Misuse Act 1990:** Avoid unauthorised access
- **GDPR:** Handle personal data appropriately
- **Copyright Laws:** Respect intellectual property
- **Contract Law:** Adhere to terms of service

### Best Practice Checklist

- ✅ Review website terms of service
- ✅ Check robots.txt compliance
- ✅ Implement rate limiting
- ✅ Use proper attribution
- ✅ Respect CAPTCHA purposes
- ✅ Consider alternative data sources
- ✅ Document legitimate business purposes

## Alternative Approaches

### API-First Strategy

Before implementing CAPTCHA handling:

- Contact website owners for API access
- Check for existing public APIs
- Explore data partnerships
- Consider paid data services

### Headless Browser Alternatives

- **HTTP Libraries:** Faster for simple data extraction
- **API Reverse Engineering:** Direct endpoint access
- **RSS/XML Feeds:** Structured data sources
- **Open Data Initiatives:** Government and public datasets

### Related Articles

Web Scraping

#### [Complete Guide to Web Scraping Compliance in the UK](web-scraping-compliance-uk-guide)

12 min read

Technology

#### [Selenium vs Playwright 2026: Speed Tests & Honest Comparison](selenium-vs-playwright-comparison)

9 min read

Web Scraping

#### [Python Scrapy Enterprise Guide: Scaling Web Scraping Operations](python-scrapy-enterprise-guide)

12 min read

---
Source: https://ukdataservices.co.uk/blog/articles/handling-captchas-scraping
