Tutorial 7 min read

How to Handle CAPTCHAs When Web Scraping

Learn about different CAPTCHA types (reCAPTCHA, hCaptcha, Turnstile), how they detect bots, and strategies to handle them in your scraping pipeline.

FT
FineData Engineering · Editorial Policy
| | Updated July 28, 2026

How to Handle CAPTCHAs When Web Scraping in 2026

CAPTCHAs are the most visible obstacle in web scraping. You write a scraper that works perfectly in testing, deploy it to production, and within hours you’re getting CAPTCHA challenges instead of data. The global CAPTCHA market is projected to exceed $20 billion by 2027, which tells you how seriously the industry takes bot detection.

This guide covers the major CAPTCHA types you’ll encounter, how they work under the hood, and practical strategies for handling them in your scraping pipeline.

How CAPTCHAs Actually Work

Modern CAPTCHAs don’t just test whether you can identify traffic lights. They build a risk score based on dozens of signals:

  • IP reputation — Is this IP from a datacenter? A VPN? Has it made suspicious requests before?
  • Browser fingerprint — Does the browser have normal fonts, plugins, screen resolution, and WebGL rendering?
  • TLS fingerprint — Does the TLS handshake match a real browser, or a bot library like requests or curl?
  • Behavioral patterns — Does the user move the mouse naturally? How fast do they click?
  • Request patterns — Is this the 100th request from this IP in the last minute?

If the risk score is low (you look like a real human), you get through without a challenge. If it’s high, you see a CAPTCHA. If it’s very high, you get blocked entirely.

This is why the same CAPTCHA behaves differently for different scrapers — it’s not just about solving the puzzle.

Types of CAPTCHAs You’ll Encounter

Google reCAPTCHA v2 (Checkbox)

The classic “I’m not a robot” checkbox. When Google is confident you’re human (based on cookies, browsing history, and behavioral signals), clicking the checkbox is enough. When it’s suspicious, it shows image selection challenges (“Select all squares with traffic lights”).

Where you’ll see it: Forms, login pages, e-commerce checkouts

Difficulty to handle: Moderate — can be solved with CAPTCHA solving services, but Google continuously updates the image challenges.

Google reCAPTCHA v3 (Invisible)

reCAPTCHA v3 runs entirely in the background with no user interaction. It assigns a score from 0.0 (bot) to 1.0 (human) based on behavioral analysis. The website owner decides what score threshold to enforce.

Where you’ll see it: Running silently on many sites, often without any visible indicator

Difficulty to handle: High — there’s no puzzle to solve. You need to make your scraper look behaviorally human.

hCaptcha

The most popular CAPTCHA for web scraping targets. hCaptcha presents image classification challenges similar to reCAPTCHA v2 but uses its own machine learning models. Many sites migrated to hCaptcha because it’s free for website owners (Cloudflare uses it by default).

Where you’ll see it: Cloudflare-protected sites, job boards, ticketing platforms

Difficulty to handle: Moderate — solvable with CAPTCHA solving services, but has aggressive rate limiting.

Cloudflare Turnstile

Cloudflare’s newest CAPTCHA replacement. Turnstile aims to be invisible — it verifies humanity through browser challenges (JavaScript execution, proof-of-work) without requiring user interaction. It’s now the default challenge on millions of Cloudflare-protected websites.

Where you’ll see it: Any Cloudflare-protected website

Difficulty to handle: High — requires a real browser environment with correct TLS fingerprinting. See our guide on how Cloudflare, DataDome, and PerimeterX detect bots for the underlying signals.

Custom CAPTCHAs

Some sites implement their own CAPTCHA systems — math problems, text puzzles, drag-and-drop challenges, or audio challenges. These are less common but can be harder to handle because there’s no standard solving service.

Where you’ll see it: Banking sites, government portals, legacy systems

Difficulty to handle: Varies — may require custom solving logic.

Strategy 1: Avoid CAPTCHAs Entirely

The best CAPTCHA strategy is to never see one. Here’s how to minimize your CAPTCHA encounter rate:

Use Residential Proxies

Most CAPTCHAs are triggered by IP reputation. Datacenter IPs have an extremely high CAPTCHA rate (often 80-100% on protected sites). Residential proxies use real consumer IP addresses with clean reputations:

import requests

response = requests.post(
    "https://api.finedata.ai/api/v1/scrape",
    headers={
        "x-api-key": "fd_your_api_key",
        "Content-Type": "application/json"
    },
    json={
        "url": "https://protected-site.com/data",
        "use_residential": True,
        "tls_profile": "chrome136",
        "timeout": 30
    }
)

Residential proxies alone can reduce CAPTCHA encounters from 80%+ down to single-digit percentages.

Fix Your TLS Fingerprint

Every TLS library has a unique fingerprint based on how it performs the TLS handshake — the cipher suites it offers, the extensions it uses, and their order. Python’s requests library has a fingerprint that screams “I’m not a browser.”

FineData’s tls_profile parameter rotates through real browser fingerprints:

{
    "url": "https://example.com",
    "tls_profile": "chrome136"  # Matches real Chrome 124 fingerprint
}

Available profiles include chrome136, chrome131, chrome124, firefox133, safari184, and VIP profiles that auto-rotate.

Pace Your Requests

Humans don’t make 100 requests per second. Add realistic delays:

import time
import random

urls = ["https://example.com/page/1", "https://example.com/page/2", ...]

for url in urls:
    result = scrape(url)
    process(result)
    time.sleep(random.uniform(2, 5))  # Random 2-5 second delay

Rotate User Agents and Headers

Send headers that match what a real browser sends:

{
    "url": "https://example.com",
    "tls_profile": "chrome136"
    # FineData automatically sets matching headers for the TLS profile
}

Strategy 2: Session Management

CAPTCHA challenges are typically triggered once per session — after that initial check passes, you get a cookie that grants access for subsequent requests on the same IP. Reusing a sticky session avoids re-triggering the challenge on every page:

# First request establishes the session
response1 = requests.post(
    "https://api.finedata.ai/api/v1/scrape",
    headers={
        "x-api-key": "fd_your_api_key",
        "Content-Type": "application/json"
    },
    json={
        "url": "https://protected-site.com/page/1",
        "use_js_render": True,
        "use_residential": True,
        "session_id": "my-session-123",  # Sticky session
        "timeout": 60
    }
)

# Subsequent requests reuse the same session and proxy IP
for page in range(2, 11):
    response = requests.post(
        "https://api.finedata.ai/api/v1/scrape",
        headers={
            "x-api-key": "fd_your_api_key",
            "Content-Type": "application/json"
        },
        json={
            "url": f"https://protected-site.com/page/{page}",
            "use_residential": True,
            "session_id": "my-session-123",  # Same session
            "timeout": 30
        }
    )

The session_id parameter ensures all requests use the same proxy IP and share cookies. Combined with residential proxies and correct TLS fingerprinting, this is enough to keep most protected sites in the “no challenge shown” bucket for the duration of the session.

Best Practices Summary

Do:

  • Layer your defenses — residential proxies + TLS fingerprinting + pacing reduces CAPTCHA rate to near zero
  • Use session persistence — pass the same session_id across requests instead of establishing a fresh connection each time
  • Monitor your CAPTCHA rate — track what percentage of requests encounter CAPTCHAs and adjust your proxy/fingerprint configuration when it climbs
  • Fix the fingerprint first — a mismatched TLS or browser fingerprint is the most common root cause of a high CAPTCHA rate

Don’t:

  • Retry infinitely — if a site is consistently showing CAPTCHAs, something is wrong with your configuration, not your retry count
  • Hammer a single session — reusing an IP and cookie jar helps, but pair it with realistic pacing rather than firing requests as fast as possible
  • Skip residential proxies — this is the single highest-impact setting for CAPTCHA avoidance
  • Treat the CAPTCHA as the problem — it’s a symptom. Fix the underlying signal (bad IP, wrong fingerprint, no pacing) rather than working around the individual challenge

What to Remember

  • Modern CAPTCHAs (reCAPTCHA v3, Turnstile) work on risk scores, not just puzzle-solving. Reducing your risk score is more effective than treating each individual challenge as a separate problem.
  • Residential proxies are the single most impactful setting for reducing CAPTCHA encounters — datacenter IPs can see 80-100% challenge rates on protected sites, residential IPs typically see single digits.
  • TLS fingerprinting is the second most important factor. A Python requests fingerprint is immediately identifiable as non-browser, independent of the IP behind it.
  • Session management (a consistent session_id and proxy IP) keeps you inside the “already verified” bucket for the life of the session, so you’re not re-triggering a check on every single page.

Need to scrape JavaScript-heavy sites that combine CAPTCHA challenges with dynamic rendering? See our guide on scraping JavaScript-heavy websites and SPAs.

#captcha #recaptcha #hcaptcha #turnstile #anti-bot #tutorial

Related Articles