Quick verdict: FlareSolverr is the right tool when Cloudscraper fails — specifically against Cloudflare Turnstile, Bot Fight Mode, and JavaScript-heavy challenges. It runs a real headless Chrome (via undetected-chromedriver) inside Docker, so it is heavier than Cloudscraper but covers cases Cloudscraper cannot. The workflow: spin up FlareSolverr as a Docker container on localhost:8191, POST your target URL to it, get back the solved HTML + clearance cookies, then continue with normal requests.
| Scenario | Tool |
|---|---|
| Old "Just a moment..." JS challenge | Cloudscraper (lighter) |
| TLS fingerprint blocks | curl_cffi with Chrome impersonation |
| Cloudflare Turnstile | FlareSolverr |
| Bot Fight Mode (aggressive) | FlareSolverr + residential proxies |
| Interactive "click to verify" | FlareSolverr with CAPTCHA solver |
| Heavy JS rendering / SPAs | Playwright directly |
FlareSolverr runs as a Docker container. Install Docker first (docs.docker.com/get-docker), then:
docker run -d --name flaresolverr -p 8191:8191 -e LOG_LEVEL=info -e TZ=UTC ghcr.io/flaresolverr/flaresolverr:latestVerify it is running:
curl http://localhost:8191/
# {"msg":"FlareSolverr is ready!","version":"3.x.x","userAgent":"..."}For docker-compose (recommended for stable deployments):
version: "3"
services:
flaresolverr:
image: ghcr.io/flaresolverr/flaresolverr:latest
container_name: flaresolverr
environment:
- LOG_LEVEL=info
- TZ=UTC
- CAPTCHA_SOLVER=none
ports:
- "8191:8191"
restart: unless-stoppedimport requests
r = requests.post("http://localhost:8191/v1", json={
"cmd": "request.get",
"url": "https://target-with-cloudflare.com",
"maxTimeout": 60000,
})
data = r.json()
print(data["status"]) # ok | error
print(data["solution"]["status"]) # 200
print(data["solution"]["url"]) # final URL after challenge
print(data["solution"]["response"][:500]) # HTML
print(data["solution"]["cookies"]) # list of {name, value, domain, path}The full response shape:
{
"status": "ok",
"message": "Challenge solved!",
"solution": {
"url": "https://target.com",
"status": 200,
"headers": {...},
"response": "...full HTML...",
"cookies": [{"name": "cf_clearance", "value": "...", "domain": ".target.com", "path": "/", ...}],
"userAgent": "Mozilla/5.0 ..."
},
"startTimestamp": 1715353200000,
"endTimestamp": 1715353215000,
"version": "3.x.x"
}Solving the challenge takes 5-15 seconds. For multiple requests to the same site, reuse the session:
# 1. Create a session
r = requests.post("http://localhost:8191/v1", json={"cmd": "sessions.create"})
session_id = r.json()["session"]
# 2. Use it for multiple requests
for url in urls:
r = requests.post("http://localhost:8191/v1", json={
"cmd": "request.get",
"url": url,
"session": session_id,
"maxTimeout": 60000,
})
print(r.json()["solution"]["status"])
# 3. Cleanup when done
requests.post("http://localhost:8191/v1", json={
"cmd": "sessions.destroy", "session": session_id
})The session keeps the browser open between requests, so subsequent calls skip the challenge solving (cookies remain valid for ~30 minutes typically).
r = requests.post("http://localhost:8191/v1", json={
"cmd": "request.post",
"url": "https://target.com/api/data",
"postData": "[email protected]",
"maxTimeout": 60000,
})JSON body:
r = requests.post("http://localhost:8191/v1", json={
"cmd": "request.post",
"url": "https://target.com/api/data",
"postData": '{"name":"alice"}',
"headers": [{"name": "Content-Type", "value": "application/json"}],
"maxTimeout": 60000,
})To route the headless Chrome through a proxy (e.g., residential IPs for IP rotation):
r = requests.post("http://localhost:8191/v1", json={
"cmd": "request.get",
"url": "https://target.com",
"proxy": {
"url": "http://gw.spyderproxy.com:8000",
"username": "USER",
"password": "PASS"
},
"maxTimeout": 60000,
})For Cloudflare-protected sites, route through Premium Residential ($2.75/GB) for clean IPs. For the toughest targets (PerimeterX + Cloudflare), LTE Mobile ($2/IP) has the lowest detection rate.
Per-session proxy (set once, used for all requests in that session):
r = requests.post("http://localhost:8191/v1", json={
"cmd": "sessions.create",
"proxy": {"url": "http://USER:[email protected]:8000"},
})
session_id = r.json()["session"]FlareSolverr can hand off Cloudflare Turnstile to 2Captcha/CapMonster automatically. Set CAPTCHA_SOLVER env var:
docker run -d --name flaresolverr -p 8191:8191 -e LOG_LEVEL=info -e CAPTCHA_SOLVER=hcaptcha-solver -e CAPTCHA_SOLVER_API_KEY=your_2captcha_key ghcr.io/flaresolverr/flaresolverr:latestCost: ~$2.99/1,000 Turnstile solves. Required when the challenge is interactive (not just JS).
{"status": "error", "message": "Challenge not detected!"} — site is not protected by Cloudflare, or returned a non-challenge response. FlareSolverr returns the raw HTML anyway in solution.response."message": "Timeout while solving the challenge" — bump maxTimeout (default 60000ms). Some sites take 30+ seconds."message": "Error: target_closed" — the headless Chrome crashed. Usually fixed by restarting the container.| Tool | Memory | Speed | Handles Turnstile |
|---|---|---|---|
| Cloudscraper | ~20 MB | Fast (1-2s) | No |
| curl_cffi | ~30 MB | Fastest (<1s) | No (TLS impersonation only) |
| FlareSolverr | ~250 MB / session | Slow (5-15s) | Yes |
| Playwright direct | ~300 MB / instance | Slow (5-15s) | Yes (with stealth) |
maxTimeout generously — 60s default is fine; bump to 120s for stubborn sites.--shm-size=2g for the Chrome shared memory if you see crashes.Related: Cloudscraper tutorial, Bypass DataDome, How to bypass Cloudflare.