Undetect browser SDK for Python with built-in captcha solving via funsolver.com
Project description
FunBrowser
Undetect / anti-detect browser SDK for Python. Drives real Chrome through the DevTools Protocol with built-in stealth patches, customisable browser fingerprints (UA, GPU, screen, timezone, CPU cores, …), full proxy support, human-like input timing, browser-pool / context-pool farming, an optional local web panel, and automatic captcha solving for Cloudflare Turnstile, reCAPTCHA v2 / v3, hCaptcha, FunCaptcha, and GeeTest through funsolver.com.
Built for web scraping, browser automation, and bypassing antibot services (Cloudflare, DataDome, PerimeterX, Akamai) without the Selenium / Playwright leaks that get scripts flagged.
pip install funbrowser # core: stealth + automation + solver
pip install funbrowser[panel] # + local web panel for the pool
pip install funbrowser[tls] # + browser-grade TLS impersonation
Quick start
import asyncio
import funbrowser
async def main():
async with await funbrowser.start(headless=True) as browser:
tab = await browser.get("https://example.com")
print(await tab.evaluate("document.title"))
# → Example Domain
print(await tab.evaluate("navigator.userAgent"))
# → Mozilla/5.0 ... Chrome/... — no "HeadlessChrome"
asyncio.run(main())
# Full automation: stealth + proxy + auto-solve captchas + humanly input
async with await funbrowser.start(
headless=True,
proxy="user:pass@us-1.proxy.io:8080", # any common format works
api_key="fs_xxx", # funsolver.com key
humanly=True, # Bezier mouse + typing rhythm
fingerprint=funbrowser.presets.windows_11_nvidia_rtx_4070(),
) as browser:
tab = await browser.get("https://target-with-captcha.com")
# captchas auto-solve in the background
await tab.fill("#email", "alice@example.com")
await tab.click("button[type=submit]")
Features
Stealth (passes 25/25 standard antidetect probes)
navigator.webdriver→ undefined, with nativetoStringcamouflage- UA + Client Hints with
HeadlessChromestripped chrome.runtime,plugins,languages, permissions consistency- Real GPU for WebGL (
--use-gl=angle, not SwiftShader) - Canvas + audio readout noise to break fingerprint tracking
- WebRTC IP leak blocked (flag-level + SDP filter)
- iframe stealth propagation
- Geo auto-couples timezone + locale to proxy exit IP
uv run python examples/detect_check.py # self-audit
Fingerprint customisation
17 ready presets (Windows × NVIDIA / Intel / AMD, macOS Apple Silicon /
Intel, Linux, plus 4 Android mobile) + arbitrary custom Fingerprint(...).
from funbrowser import Fingerprint, presets
fp = presets.macos_apple_silicon_m3_pro().merge(
Fingerprint(timezone="Asia/Tokyo", languages=("ja-JP", "ja", "en"))
)
async with await funbrowser.start(fingerprint=fp) as browser:
...
Humanly mode (Bezier-curve mouse + typing rhythm)
async with await funbrowser.start(humanly=True) as browser:
await tab.click("button") # cursor curves toward target
await tab.type("#email", "ada") # per-keystroke random delay
Presets: humanly.FAST, humanly.DEFAULT, humanly.CAREFUL, plus arbitrary
custom HumanBehavior(...).
Captcha auto-solve
Paste a funsolver.com API key. Five captcha families detected on-page and solved automatically:
| API method | Page integration | |
|---|---|---|
| Cloudflare Turnstile | solve_turnstile |
.cf-turnstile widgets |
| reCAPTCHA v2 (+ Enterprise) | solve_recaptcha_v2 |
.g-recaptcha widgets |
| reCAPTCHA v3 (+ Enterprise) | solve_recaptcha_v3 |
hooks grecaptcha.execute() |
| hCaptcha | solve_hcaptcha |
.h-captcha widgets |
| FunCaptcha / Arkose | solve_funcaptcha |
[data-pkey] elements |
| GeeTest v3 + v4 | solve_geetest |
hooks initGeetest / initGeetest4 |
Proxies (every format)
host:port, host:port:user:pass, user:pass@host:port, host:port@user:pass,
socks5://..., chrome URL form — all auto-parsed. HTTP/HTTPS auth flows
through CDP automatically.
Persistent profiles
from funbrowser import Profile
alice = Profile.ensure("alice") # ./funbrowser_profiles/alice
async with await funbrowser.start(user_data_dir=alice) as browser:
# cookies + localStorage + login state persist between runs
...
Browser farming — BrowserPool and ContextPool
from funbrowser import BrowserPool, ContextPool
# Process pool — full Chrome per slot (max isolation)
async with BrowserPool(
size=5,
proxies=["proxy1:8080", "proxy2:8080", "proxy3:8080"],
headless=True,
mini=True,
) as pool:
async def scrape(browser):
tab = await browser.get("https://example.com")
return await tab.evaluate("document.title")
results = await pool.run_all([scrape] * 100)
# Context pool — 1 Chrome + N isolated contexts (~7-10x less RAM)
async with ContextPool(size=10, headless=True, mini=True) as pool:
async def scrape(ctx):
tab = await ctx.get("https://example.com")
return await tab.evaluate("document.title")
results = await pool.run_all([scrape] * 100)
Memory comparison on the same 10-slot workload:
BrowserPool(size=10)→ ~1.0 GBContextPool(size=10)→ ~260 MB (one Chrome + 10 contexts)
mini=True mode
Curated set of Chrome flags that cut RAM / CPU / disk per browser
(~50% lower RSS): site isolation off, background throttling, audio
muted, extensions / sync / translate disabled, small disk caches,
V8 heap cap. Combines with stealth — does not turn off the real
GPU. Works on Browser, BrowserPool, and ContextPool alike.
TLS fingerprint impersonation (pip install funbrowser[tls])
Script-level HTTP that picks JA3/JA4 from real-browser profiles:
from funbrowser.tls import ImpersonatedHTTPClient
async with ImpersonatedHTTPClient(profile="chrome131") as http:
r = await http.get("https://protected-api.com/endpoint")
23 supported profiles: chrome99..chrome133a, safari15..safari18,
firefox133/135, Android variants. See
docs/M10_M11_DESIGN.md for the deeper
browser-traffic mitm proxy roadmap.
Local web panel (pip install funbrowser[panel])
from funbrowser import BrowserPool, Panel
async with BrowserPool(size=5) as pool:
async with Panel(pool) as panel:
print(panel.url) # http://127.0.0.1:8765
await long_running_task()
Black-and-white dashboard with: pool stats, FunSolver balance, browser
fleet table (proxy / geo / fingerprint / open tabs / per-row goto +
screenshot), activity log (panel actions + per-browser captcha solves),
quick actions, and a script runner — upload an async def main(browser) script and run it on one browser or fan it out across
the whole pool, with per-run stdout / return / traceback captured.
Documentation
- docs/stealth.md — anti-detect coverage in depth
- docs/captchas.md — solver integration guide
- docs/farming.md —
BrowserPoolvsContextPool - docs/panel.md — the local web dashboard
- docs/M10_M11_DESIGN.md — TLS + engine-layer roadmap
- examples/ — runnable demos for every feature
Self-audit
uv run python examples/detect_check.py
[navigator.* basics] 9/9 PASS
[chrome runtime + iframe] 6/6 PASS
[stealth-detection probes] 5/5 PASS
[WebGL / canvas / audio] 4/4 PASS
[WebRTC IP leak] 1/1 PASS
score: 25/25 (100%)
Roadmap
| Milestone | Status | |
|---|---|---|
| M0 | Bootstrap | done |
| M1 | CDP core + Tab API | done |
| M2 | Stealth Tier 1 + 2 | done |
| M2.5 | Fingerprint customisation | done |
| M3 | Solver bridge + Turnstile | done |
| M4 | reCAPTCHA / hCaptcha / FunCaptcha / GeeTest | done |
| M5 | Production hardening (proxies, profiles, retries, multi-tab) | done |
| M5.5 | DX Tier S (auto-wait + ElementHandle + cookies + block_urls) | done |
| M5.5+ | Humanly mode | done |
| M5.5++ | WebRTC + toString camouflage + geo auto-couple | done |
| M5.5+++ | Mobile presets + expanded catalog | done |
| M5.6 | BrowserPool | done |
| M5.7 | Web Panel (+ FunSolver balance + per-browser logs + scripts) | done |
| M5.8 | Mini mode | done |
| M6 | v0.1 release | in progress |
| M10a | TLS HTTP client (curl_cffi) | done |
| M11-alt | ContextPool (lightweight pool) | done |
| M7 | Fingerprint consistency (Tier 3) | post-v0.1 |
| M8 | Real fingerprint pool (Tier 4) | post-v0.1 |
| M9 | Deep WebGL / canvas / shader spoofing | post-v0.1 |
| M10b | mitm proxy for Chrome traffic (production-grade) | post-v0.1 |
| M11 | Browser fork (Camoufox / Chromium) | post-v0.1 |
| M12 | Tauri UI for manual mode | post-v0.1 |
Development
Uses uv for env + deps.
uv sync # install
uv run pytest -q # full test suite (173 tests)
uv run ruff check . && uv run ruff format --check .
uv run mypy funbrowser
uv run python examples/detect_check.py
Contributing
See CONTRIBUTING.md. Issues + PRs welcome.
License
MIT. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file funbrowser-0.1.17.tar.gz.
File metadata
- Download URL: funbrowser-0.1.17.tar.gz
- Upload date:
- Size: 77.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
91e0a741796b2958c1bcf01b366d6e47ff8cfa1a2bd9fb7462476c19e1f13e9b
|
|
| MD5 |
e5afdb5d7d6a32aba3c5e1f53a67f418
|
|
| BLAKE2b-256 |
187d9ec01bb63fc7e39fdf082ecd830a39e08d25efe3d82fb1126aa64a60a6ea
|
Provenance
The following attestation bundles were made for funbrowser-0.1.17.tar.gz:
Publisher:
release.yml on WhyY0u/funBrowser
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
funbrowser-0.1.17.tar.gz -
Subject digest:
91e0a741796b2958c1bcf01b366d6e47ff8cfa1a2bd9fb7462476c19e1f13e9b - Sigstore transparency entry: 1938244297
- Sigstore integration time:
-
Permalink:
WhyY0u/funBrowser@e1ad66970ebf266bada84a188191da66621ca734 -
Branch / Tag:
refs/tags/v0.1.17 - Owner: https://github.com/WhyY0u
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@e1ad66970ebf266bada84a188191da66621ca734 -
Trigger Event:
push
-
Statement type:
File details
Details for the file funbrowser-0.1.17-py3-none-any.whl.
File metadata
- Download URL: funbrowser-0.1.17-py3-none-any.whl
- Upload date:
- Size: 99.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
88bba1c8a121048de2faf59f6a0c041e999eea82c4f8c42ce783295d13641c48
|
|
| MD5 |
8f555c5dc8d5a21388c2a0eaabaf6c0e
|
|
| BLAKE2b-256 |
9af81382c821992f1ed24c65bf52e1c3154de6eaaef1700573a7bfc1810582dc
|
Provenance
The following attestation bundles were made for funbrowser-0.1.17-py3-none-any.whl:
Publisher:
release.yml on WhyY0u/funBrowser
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
funbrowser-0.1.17-py3-none-any.whl -
Subject digest:
88bba1c8a121048de2faf59f6a0c041e999eea82c4f8c42ce783295d13641c48 - Sigstore transparency entry: 1938244456
- Sigstore integration time:
-
Permalink:
WhyY0u/funBrowser@e1ad66970ebf266bada84a188191da66621ca734 -
Branch / Tag:
refs/tags/v0.1.17 - Owner: https://github.com/WhyY0u
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@e1ad66970ebf266bada84a188191da66621ca734 -
Trigger Event:
push
-
Statement type: