Solving CAPTCHAs with Python Requests: Complete Tutorial
Build a Python CAPTCHA solver using the requests library. Full tutorial with createTask, polling, error handling, and a reusable helper class.
A Python CAPTCHA solver built on the requests library is the fastest way to add automated CAPTCHA solving to your scripts, scrapers, and data pipelines. This tutorial builds a reusable UCaptchaClient class from scratch, demonstrates solving a reCAPTCHA v2 challenge, and covers the error handling patterns you need for reliable production use.
If you are new to CAPTCHA solver APIs in general, read the CAPTCHA Solver API Integration Tutorial first for the full conceptual overview.
Prerequisites
You need Python 3.8+ and the requests library:
pip install requests
Set your uCaptcha API key as an environment variable:
export UCAPTCHA_KEY="ucap_your_key_here"
The UCaptchaClient Class
Rather than writing one-off HTTP calls everywhere, wrap the API in a class you can import into any project. Here is the complete implementation:
import os
import time
import logging
from typing import Optional
import requests
from requests.exceptions import RequestException
logger = logging.getLogger(__name__)
class UCaptchaError(Exception):
"""Raised when the uCaptcha API returns an error."""
def __init__(self, code: str, description: str = ""):
self.code = code
self.description = description
super().__init__(f"{code}: {description}")
class UCaptchaClient:
"""Reusable client for the uCaptcha CAPTCHA solving API."""
API_URL = "https://api.ucaptcha.net"
RETRYABLE_ERRORS = {"ERROR_NO_SLOT_AVAILABLE"}
def __init__(
self,
client_key: Optional[str] = None,
max_retries: int = 3,
poll_interval: int = 4,
timeout: int = 120,
):
self.client_key = client_key or os.environ["UCAPTCHA_KEY"]
self.max_retries = max_retries
self.poll_interval = poll_interval
self.timeout = timeout
self.session = requests.Session()
self.session.headers.update({"Content-Type": "application/json"})
def _post(self, endpoint: str, payload: dict) -> dict:
"""Make a POST request with retry logic."""
url = f"{self.API_URL}/{endpoint}"
last_error = None
for attempt in range(self.max_retries):
try:
resp = self.session.post(url, json=payload, timeout=15)
resp.raise_for_status()
data = resp.json()
if data.get("errorId", 0) != 0:
code = data.get("errorCode", "UNKNOWN")
desc = data.get("errorDescription", "")
if code in self.RETRYABLE_ERRORS:
wait = 3 * (attempt + 1)
logger.warning(f"{code} on attempt {attempt + 1}, retrying in {wait}s")
time.sleep(wait)
continue
raise UCaptchaError(code, desc)
return data
except RequestException as e:
last_error = e
if attempt < self.max_retries - 1:
time.sleep(2 * (attempt + 1))
raise last_error or Exception("Max retries exceeded")
def create_task(self, task: dict) -> str:
"""Submit a CAPTCHA task and return the task ID."""
data = self._post("createTask", {
"clientKey": self.client_key,
"task": task,
})
task_id = data["taskId"]
logger.info(f"Created task {task_id}")
return task_id
def get_result(self, task_id: str) -> dict:
"""Poll for a task result until ready or timeout."""
deadline = time.time() + self.timeout
time.sleep(5) # initial wait before first poll
while time.time() < deadline:
data = self._post("getTaskResult", {
"clientKey": self.client_key,
"taskId": task_id,
})
if data.get("status") == "ready":
logger.info(f"Task {task_id} solved")
return data["solution"]
time.sleep(self.poll_interval)
raise TimeoutError(f"Task {task_id} timed out after {self.timeout}s")
def solve(self, task: dict) -> dict:
"""Create a task and wait for the result in one call."""
task_id = self.create_task(task)
return self.get_result(task_id)
def get_balance(self) -> float:
"""Check account balance."""
data = self._post("getBalance", {"clientKey": self.client_key})
return data["balance"]
The class gives you three levels of usage:
solve(task)— One-liner. Pass the task dict, get the solution back.create_task(task)+get_result(task_id)— Two-step when you want to do other work while waiting.get_balance()— Monitor your funds programmatically.
Solving a reCAPTCHA v2
Here is how you solve a reCAPTCHA v2 using the client:
client = UCaptchaClient()
solution = client.solve({
"type": "RecaptchaV2TaskProxyless",
"websiteURL": "https://example.com/login",
"websiteKey": "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
})
recaptcha_token = solution["gRecaptchaResponse"]
print(f"Token: {recaptcha_token[:50]}...")
You then include recaptcha_token in the form submission to the target site, typically as the g-recaptcha-response POST parameter:
login_resp = requests.post("https://example.com/login", data={
"username": "user@example.com",
"password": "hunter2",
"g-recaptcha-response": recaptcha_token,
})
Solving Other CAPTCHA Types
The solve() method works with any task type. Change the type and fields:
hCaptcha:
solution = client.solve({
"type": "HardCaptchaTaskProxyless",
"websiteURL": "https://example.com/signup",
"websiteKey": "a5f74b19-9e45-40e0-b45d-47ff91b7a6c2",
})
token = solution["gRecaptchaResponse"] # hCaptcha uses the same field name
Cloudflare Turnstile:
solution = client.solve({
"type": "TurnstileTaskProxyless",
"websiteURL": "https://example.com",
"websiteKey": "0x4AAAAAAABS7vwvV6VFfMcD",
})
token = solution["token"]
Image to text:
import base64
with open("captcha.png", "rb") as f:
b64_image = base64.b64encode(f.read()).decode()
solution = client.solve({
"type": "ImageToTextTask",
"body": b64_image,
})
text = solution["text"]
Error Handling in Practice
The UCaptchaClient already handles retryable errors and network failures internally. But your calling code should still handle the exceptions the class raises:
from ucaptcha_client import UCaptchaClient, UCaptchaError
client = UCaptchaClient(timeout=90)
try:
solution = client.solve({
"type": "RecaptchaV2TaskProxyless",
"websiteURL": "https://example.com/login",
"websiteKey": "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
})
token = solution["gRecaptchaResponse"]
except UCaptchaError as e:
if e.code == "ERROR_ZERO_BALANCE":
print("Account balance is empty. Top up at ucaptcha.net")
elif e.code == "ERROR_CAPTCHA_UNSOLVABLE":
print("CAPTCHA could not be solved. Retrying with a new task...")
# Retry logic here
else:
print(f"API error: {e}")
except TimeoutError:
print("Solve timed out. The CAPTCHA may be unusually complex.")
except Exception as e:
print(f"Unexpected error: {e}")
The key errors to watch for:
ERROR_ZERO_BALANCE— Stop processing and alert. No point retrying.ERROR_CAPTCHA_UNSOLVABLE— The CAPTCHA image or parameters were invalid, or no solver could crack it. Retrying with a fresh task often works.ERROR_NO_SLOT_AVAILABLE— Temporary capacity issue. The client retries this automatically.TimeoutError— The solve took longer than your timeout. Increase the timeout or investigate whether the CAPTCHA parameters are correct.
Async Alternative with aiohttp
If your project is async, swap requests for aiohttp. The structure is identical:
import aiohttp
import asyncio
async def solve_async(task: dict) -> dict:
async with aiohttp.ClientSession() as session:
# Create task
async with session.post(
"https://api.ucaptcha.net/createTask",
json={"clientKey": os.environ["UCAPTCHA_KEY"], "task": task},
) as resp:
data = await resp.json()
task_id = data["taskId"]
# Poll
await asyncio.sleep(5)
while True:
async with session.post(
"https://api.ucaptcha.net/getTaskResult",
json={"clientKey": os.environ["UCAPTCHA_KEY"], "taskId": task_id},
) as resp:
data = await resp.json()
if data.get("status") == "ready":
return data["solution"]
await asyncio.sleep(4)
This lets you solve multiple CAPTCHAs concurrently with asyncio.gather(), which is useful for high-throughput scraping.
Putting It All Together
Save the UCaptchaClient class as ucaptcha_client.py in your project, then import it wherever you need CAPTCHA solving:
from ucaptcha_client import UCaptchaClient
client = UCaptchaClient()
# Check balance on startup
balance = client.get_balance()
print(f"Balance: ${balance:.2f}")
# Solve as needed
solution = client.solve({
"type": "RecaptchaV2TaskProxyless",
"websiteURL": "https://target-site.com/page",
"websiteKey": "SITE_KEY_HERE",
})
The class handles retries, timeouts, and error classification so your business logic stays clean. For the full conceptual overview of the API, including callback-based workflows and routing configuration, see the CAPTCHA Solver API Integration Tutorial.
uCaptcha routes your tasks across six providers to optimize for speed, cost, or reliability depending on your routing configuration. The Python client above works the same regardless of which provider ultimately solves the CAPTCHA — that routing is handled server-side, transparently. Get your API key at ucaptcha.net and start solving in minutes.
Related Articles
Pillar Guide
CAPTCHA Solver API Integration: Step-by-Step Tutorial
Step-by-step tutorial for integrating a CAPTCHA solver API into any project. Covers authentication, task creation, polling, error handling, and production best practices.
Browser Automation CAPTCHA Solving: Puppeteer & Playwright Guide
Complete guide to solving CAPTCHAs during browser automation with Puppeteer and Playwright — detecting CAPTCHAs, extracting parameters, solving via API, and injecting tokens.