Everything you need to go from your first CAPTCHA solve to a production-grade pipeline.
Part 1: Understanding CAPTCHAs
What Is a CAPTCHA?
A CAPTCHA (Completely Automated Public Turing test to tell Computers and Humans Apart) is a challenge designed to block automated access while allowing human users through.
Types You'll Encounter
| Type | Examples | Challenge |
|---|---|---|
| Text/Image | Distorted letters, math expressions | Type what you see |
| Checkbox | reCAPTCHA v2 | Click checkbox, possibly solve image grid |
| Invisible | reCAPTCHA v3, Turnstile | No user interaction — behavioral scoring |
| Interactive | GeeTest slide, BLS grid | Drag, click, or order elements |
Why Sites Use CAPTCHAs
- Prevent automated account creation
- Block scraping and data harvesting
- Stop spam in forms and comments
- Rate-limit API access
Part 2: How CAPTCHA Solving Services Work
The Flow
Your Code → Submit CAPTCHA to API → Solving Service → Return Token/Text → Your Code Injects Result
Step-by-Step
- Extract CAPTCHA parameters from the target page (sitekey, challenge, image)
- Submit parameters to the solving API
- Poll for the result (token or text)
- Inject the result back into the page
- Submit the form
Part 3: Setting Up CaptchaAI
Install Dependencies
pip install requests
Core Solver Class
import time
import requests
class CaptchaAI:
BASE = "https://ocr.captchaai.com"
def __init__(self, api_key):
self.api_key = api_key
def submit(self, params):
params["key"] = self.api_key
params["json"] = 1
resp = requests.post(f"{self.BASE}/in.php", data=params)
data = resp.json()
if data["status"] != 1:
raise Exception(f"Submit failed: {data['request']}")
return data["request"]
def get_result(self, task_id, timeout=300, interval=5, initial_wait=10):
time.sleep(initial_wait)
deadline = time.time() + timeout
while time.time() < deadline:
resp = requests.get(
f"{self.BASE}/res.php",
params={
"key": self.api_key,
"action": "get",
"id": task_id,
"json": 1,
},
).json()
if resp["request"] == "CAPCHA_NOT_READY":
time.sleep(interval)
continue
if resp["status"] == 1:
return resp["request"]
raise Exception(f"Solve failed: {resp['request']}")
raise TimeoutError("Solve timed out")
def solve(self, params, **kwargs):
task_id = self.submit(params)
return self.get_result(task_id, **kwargs)
def balance(self):
resp = requests.get(
f"{self.BASE}/res.php",
params={"key": self.api_key, "action": "getbalance"},
)
return float(resp.text)
Part 4: Solving Each CAPTCHA Type
reCAPTCHA v2
solver = CaptchaAI("YOUR_API_KEY")
token = solver.solve({
"method": "userrecaptcha",
"googlekey": "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
"pageurl": "https://example.com/login",
})
reCAPTCHA v3
token = solver.solve({
"method": "userrecaptcha",
"googlekey": "SITE_KEY",
"pageurl": "https://example.com",
"version": "v3",
"action": "submit",
"min_score": "0.9",
}, initial_wait=20)
Cloudflare Turnstile
token = solver.solve({
"method": "turnstile",
"sitekey": "0x4AAAAAAAC3a...",
"pageurl": "https://example.com",
})
GeeTest v3
result = solver.solve({
"method": "geetest",
"gt": "GT_VALUE",
"challenge": "CHALLENGE_VALUE",
"pageurl": "https://example.com",
})
Image/OCR
import base64
with open("captcha.png", "rb") as f:
img_b64 = base64.b64encode(f.read()).decode()
text = solver.solve({
"method": "base64",
"body": img_b64,
"numeric": "1",
"minLen": "4",
"maxLen": "6",
})
Part 5: Extracting CAPTCHA Parameters
reCAPTCHA Sitekey
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
driver.get("https://example.com/login")
# Method 1: From div attribute
sitekey = driver.find_element(
By.CSS_SELECTOR, "[data-sitekey]"
).get_attribute("data-sitekey")
# Method 2: From iframe URL
import re
iframe = driver.find_element(By.CSS_SELECTOR, "iframe[src*='recaptcha']")
src = iframe.get_attribute("src")
sitekey = re.search(r"k=([^&]+)", src).group(1)
Turnstile Sitekey
sitekey = driver.find_element(
By.CSS_SELECTOR, "[data-sitekey], .cf-turnstile"
).get_attribute("data-sitekey")
GeeTest Parameters
import json
gt_data = driver.execute_script("""
return {
gt: document.querySelector('[data-gt]')?.getAttribute('data-gt'),
challenge: document.querySelector('[data-challenge]')?.getAttribute('data-challenge')
};
""")
Part 6: Injecting Solutions
Token-Based (reCAPTCHA, Turnstile)
driver.execute_script(f"""
document.querySelector('[name="g-recaptcha-response"]').value = '{token}';
document.querySelector('[name="cf-turnstile-response"]').value = '{token}';
""")
For callbacks
driver.execute_script(f"""
if (typeof ___grecaptcha_cfg !== 'undefined') {{
Object.keys(___grecaptcha_cfg.clients).forEach(function(key) {{
var client = ___grecaptcha_cfg.clients[key];
// Find and call the callback
}});
}}
""")
Part 7: Error Handling
Retry Logic
def solve_with_retry(solver, params, max_retries=3):
for attempt in range(max_retries):
try:
return solver.solve(params)
except Exception as e:
error = str(e)
if "ZERO_BALANCE" in error:
raise # Don't retry — need funds
if "UNSOLVABLE" in error:
print(f"Attempt {attempt + 1} failed, retrying...")
continue
raise
raise Exception(f"Failed after {max_retries} attempts")
Balance Monitoring
def check_balance_before_solve(solver, min_balance=0.10):
balance = solver.balance()
if balance < min_balance:
raise Exception(f"Low balance: ${balance:.2f}")
return balance
Part 8: Production Patterns
Connection Pooling
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
def create_session():
session = requests.Session()
retry = Retry(total=3, backoff_factor=1, status_forcelist=[500, 502, 503])
adapter = HTTPAdapter(max_retries=retry, pool_connections=10, pool_maxsize=20)
session.mount("https://", adapter)
return session
Concurrent Solving
from concurrent.futures import ThreadPoolExecutor, as_completed
def solve_batch(solver, captcha_list, max_workers=5):
results = {}
with ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = {
executor.submit(solver.solve, params): url
for url, params in captcha_list
}
for future in as_completed(futures):
url = futures[future]
try:
results[url] = future.result()
except Exception as e:
results[url] = f"ERROR: {e}"
return results
Rate Limiting
import threading
class RateLimiter:
def __init__(self, max_per_second=10):
self.interval = 1.0 / max_per_second
self.lock = threading.Lock()
self.last_call = 0
def wait(self):
with self.lock:
now = time.time()
wait_time = self.last_call + self.interval - now
if wait_time > 0:
time.sleep(wait_time)
self.last_call = time.time()
Part 9: Monitoring
Logging
import logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
logger = logging.getLogger("captcha")
def solve_logged(solver, params):
start = time.time()
logger.info(f"Submitting {params.get('method')} CAPTCHA")
try:
result = solver.solve(params)
elapsed = time.time() - start
logger.info(f"Solved in {elapsed:.1f}s")
return result
except Exception as e:
elapsed = time.time() - start
logger.error(f"Failed after {elapsed:.1f}s: {e}")
raise
Metrics Tracking
class SolveMetrics:
def __init__(self):
self.total = 0
self.success = 0
self.failures = 0
self.total_time = 0.0
def record(self, success, elapsed):
self.total += 1
self.total_time += elapsed
if success:
self.success += 1
else:
self.failures += 1
def summary(self):
rate = (self.success / self.total * 100) if self.total else 0
avg = (self.total_time / self.total) if self.total else 0
return {
"total": self.total,
"success_rate": f"{rate:.1f}%",
"avg_time": f"{avg:.1f}s",
}
Part 10: Checklist
| Step | Task |
|---|---|
| 1 | Install requests and get API key |
| 2 | Identify CAPTCHA type on target page |
| 3 | Extract sitekey/parameters |
| 4 | Submit to CaptchaAI with correct method |
| 5 | Poll with proper timing |
| 6 | Inject token and submit form |
| 7 | Add retry logic for production |
| 8 | Monitor success rate and costs |
| 9 | Scale with connection pooling and concurrency |
FAQ
How much does CAPTCHA solving cost?
Pricing varies by type. Check your dashboard for current rates. Image CAPTCHAs are cheapest; token-based types cost more.
Which CAPTCHA type is fastest to solve?
Image/OCR CAPTCHAs solve in 5-15 seconds. Turnstile solves quickly due to 100% success rate. reCAPTCHA v3 may take 20-30 seconds.
Can I solve CAPTCHAs without a browser?
Yes, for token-based CAPTCHAs you only need the sitekey and page URL — no browser required. Image CAPTCHAs need just the image data.
How do I handle token expiration?
Solve CAPTCHAs just before you need them. reCAPTCHA tokens expire in ~120 seconds, Turnstile in ~300 seconds. Don't pre-solve in bulk.
Related Guides
From basics to production in one guide — start with CaptchaAI.
Discussions (0)
Join the conversation
Sign in to share your opinion.
Sign InNo comments yet.