--- name: anti-detection-browser description: Browser automation with Cloudflare Turnstile & CDP-detection bypass. DrissionPage + Turnstile patch extension + proxy auth forwarder. Use when headless browsers are blocked by Cloudflare or anti-bot detection. category: automation --- # Anti-Detection Browser Automation Обход Cloudflare Turnstile и CDP-детекции при автоматизации браузера. ## Когда использовать - Cloudflare блокирует headless-браузеры (Playwright, Puppeteer, Selenium) - Страница показывает "Attention Required! | Cloudflare" или "Sorry, you have been blocked" - `MouseEvent.screenX/screenY = 0` детектится как CDP-браузер - Нужна регистрация/логин на сайтах с Turnstile (x.ai, Clerk, TokenHarbor) ## Почему Playwright не работает Playwright использует CDP (Chrome DevTools Protocol). Cloudflare Turnstile детектит CDP-браузеры через: - `MouseEvent.screenX/screenY = 0` (в headless-режиме координаты мыши не эмулируются) - `navigator.webdriver = true` - Отсутствие реальных движений мыши перед кликом ## Решение: DrissionPage + Turnstile Patch **DrissionPage** — библиотека управления Chromium, которая не использует WebDriver и не выставляет `navigator.webdriver`. **Turnstile patch** — Chrome extension (Manifest V3), который инжектит скрипт в `MAIN` world на `document_start`, переопределяя `MouseEvent.prototype.screenX/Y` случайными реалистичными значениями. ### Структура расширения ``` turnstilePatch/ ├── manifest.json └── script.js ``` **manifest.json**: ```json { "manifest_version": 3, "name": "Turnstile Patch", "version": "1.0", "content_scripts": [{ "matches": [""], "js": ["script.js"], "run_at": "document_start", "world": "MAIN" }] } ``` **script.js**: ```javascript Object.defineProperty(MouseEvent.prototype, 'screenX', { get: function() { return Math.floor(Math.random() * 400) + 800; } }); Object.defineProperty(MouseEvent.prototype, 'screenY', { get: function() { return Math.floor(Math.random() * 200) + 400; } }); ``` ### Настройка DrissionPage ```python from DrissionPage import Chromium, ChromiumOptions co = ChromiumOptions() co.auto_port() co.set_argument("--no-sandbox") co.set_argument("--disable-gpu") co.set_argument("--disable-dev-shm-usage") co.set_argument("--disable-software-rasterizer") # Загрузка Turnstile patch EXTENSION_PATH = os.path.abspath("turnstilePatch") co.add_extension(EXTENSION_PATH) # Прокси (если без аутентификации) co.set_proxy("http://proxy:port") browser = Chromium(co) ``` ### Ожидание Turnstile После навигации нужно ждать 15-30 секунд, пока Turnstile пройдёт в non-interactive режиме: ```python page.get("https://target.com/sign-up") run_logger.info("Waiting for Cloudflare Turnstile...") deadline = time.time() + 30 while time.time() < deadline: has_challenge = page.run_js( "return document.title.includes('Attention Required') || " "document.querySelector('#challenge-running') !== null || " "document.body.innerText.includes('verifying you are human')" ) if not has_challenge: break time.sleep(2) ``` ### Поиск кнопок с fallback На странице может быть разный язык. Использовать JS-поиск по тексту: ```javascript const candidates = Array.from(document.querySelectorAll('button, a, [role="button"]')); const target = candidates.find(node => { const text = (node.innerText || node.textContent || '').replace(/\s+/g, '').toLowerCase(); return text.includes('signupwithemail') || text.includes('continue with email') || text.includes('sign up with email') || text.includes('use email') || text.includes('email'); }); if (target) target.click(); ``` ## Proxy Auth Forwarder DrissionPage и nodriver НЕ поддерживают прокси с аутентификацией. Ошибка: "你似乎在设置使用账号密码的代理,暂时不支持这种代理". Решение: локальный forward proxy, который добавляет `Proxy-Authorization` header. ### ⚠️ HTTP-based forwarder НЕ работает с Chrome `http.server.HTTPServer` + `BaseHTTPRequestHandler` ломает CONNECT-туннели для Chrome — браузер получает `chrome-error://chromewebdata/`. Curl работает, Chrome — нет. **Использовать RAW TCP forwarder** (см. `scripts/tcp_forwarder.py`): ```python import socket, threading, select, base64 UPSTREAM_HOST = 'residential-x.bpproxy.at' UPSTREAM_PORT = 1000 AUTH = base64.b64encode(b'user:pass').decode() LOCAL_PORT = 9129 def handle_client(client_sock): data = b'' while b'\r\n\r\n' not in data: chunk = client_sock.recv(4096) if not chunk: return data += chunk request = data.decode('latin-1', errors='replace') first_line = request.split('\r\n')[0] method, target, _ = first_line.split(' ', 2) if method == 'CONNECT': host, port_str = target.rsplit(':', 1) port = int(port_str) remote = socket.create_connection((UPSTREAM_HOST, UPSTREAM_PORT), timeout=10) connect_req = f'CONNECT {host}:{port} HTTP/1.1\r\nHost: {host}:{port}\r\nProxy-Authorization: Basic {AUTH}\r\n\r\n' remote.send(connect_req.encode()) resp = b'' while b'\r\n\r\n' not in resp: resp += remote.recv(4096) if b'200' not in resp.split(b'\r\n')[0]: client_sock.send(b'HTTP/1.1 502 Bad Gateway\r\n\r\n') remote.close() return client_sock.send(b'HTTP/1.1 200 Connection Established\r\n\r\n') client_sock.settimeout(30) remote.settimeout(30) socks = [client_sock, remote] while True: r, _, _ = select.select(socks, [], [], 30) if not r: break for s in r: d = s.recv(8192) if not d: return dst = remote if s is client_sock else client_sock dst.sendall(d) server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server.bind(('127.0.0.1', LOCAL_PORT)) server.listen(50) while True: client, addr = server.accept() threading.Thread(target=handle_client, args=(client,), daemon=True).start() ``` Запуск: ```bash python tcp_forwarder.py & # Затем использовать http://127.0.0.1:9129 как browser_proxy ``` ## Geo-Blocking Сервисы типа x.ai гео-блокируют по стране выхода прокси. Проверять перед использованием: ```bash curl -x "http://user:pass@proxy:port" -s http://ip-api.com/json # Проверить поле "country" — x.ai доступен из US, CA, UK, EU, но НЕ из IQ, RU, CN ``` ## Nodriver: Fallback When DrissionPage Fails DrissionPage может быть детектирован Cloudflare даже с Turnstile patch. Симптом: `PageDisconnectedError: 与页面的连接已断开` — Cloudflare активно рвёт CDP-соединение. **Nodriver** (`pip install nodriver`) — альтернатива с встроенной антидетект-защитой. Не требует отдельного Turnstile patch. ### Настройка nodriver ```python import nodriver as uc browser = await uc.start( headless=False, browser_args=[ "--window-size=1280,900", "--no-first-run", "--no-default-browser-check", "--disable-blink-features=AutomationControlled", "--proxy-server=http://127.0.0.1:9129", # через proxy forwarder "--ignore-certificate-errors", ], ) tab = await browser.get("https://accounts.x.ai/sign-up?redirect=grok-com") ``` ### API ключевые отличия от DrissionPage - **Async**: все методы `await` - `tab.find(text)` — поиск элемента по тексту (аналог `page.run_js(...find text...)`) - `tab.select(selector)` — CSS-селектор - `tab.evaluate(js)` — выполнить JS - `el.click()`, `el.send_keys(text)` — взаимодействие - Обернуть в `asyncio.wait_for()` для таймаутов ### Google OAuth Bypass для accounts.x.ai **Проблема**: `accounts.x.ai` блокирует ВСЕ bpproxy residential IP (403) напрямую, но Google OAuth (`accounts.google.com`) принимает те же прокси (302). **Решение**: использовать Google OAuth flow вместо прямой email-регистрации: 1. Зайти на `accounts.x.ai/sign-up?redirect=grok-com` 2. Нажать «Continue with Google» (не «Use email») 3. Google OAuth-страница загружается через прокси (Google принимает bpproxy) 4. Ввести Gmail + пароль + TOTP (если 2FA) 5. Пройти consent screen 6. Редирект обратно на Grok **Требования**: Gmail аккаунты с 2FA-секретами (формат как в `tusk_gmail_accounts.json`). ```python # Пример: поиск кнопки Google clicked = await click_text(tab, [ "Continue with Google", "Sign in with Google", "Sign up with Google", "Google", ], timeout=10) ``` ### Port Conflicts: Multiple Proxy Forwarders Никогда не запускать несколько экземпляров proxy forwarder на одном порту — они конфликтуют и порт становится недоступным. При смене upstream-прокси: 1. Убить старый forwarder: `taskkill /F /PID ` 2. Проверить что порт свободен: `netstat -ano | grep ` 3. Запустить новый forwarder на **уникальном** порту **Рекомендация**: использовать разные порты для разных upstream-прокси (9128, 9129, 9130...). ### Nodriver `browser.stop()` не awaitable `browser.stop()` в nodriver возвращает `None` — не использовать `await`: ```python # ❌ ОШИБКА: TypeError: object NoneType can't be used in 'await' expression await browser.stop() # ✅ ПРАВИЛЬНО browser.stop() ``` ### TCP Forwarder: всегда вместо HTTP-based HTTP-based forwarder (`http.server.HTTPServer`) не работает с Chrome — CONNECT-туннели ломаются. Всегда использовать RAW TCP forwarder (`scripts/tcp_forwarder.py`). ## Pitfalls ### Playwright Headless Blocked by Cloudflare (General) Playwright's headless Chromium is detected by Cloudflare on many sites, not just those with Turnstile. Symptoms: `page.goto()` times out, empty page content, or Cloudflare challenge page. Use `headless=False` with a persistent Chrome user profile (`launch_persistent_context`), or connect via CDP to an existing Chrome instance (`connect_over_cdp`). ### Playwright CDP Works, Headless Doesn't `connect_over_cdp("http://127.0.0.1:9222")` to an existing Chrome with remote debugging works reliably because it uses the real Chrome profile. Headless Playwright (`launch()`) is detected. For automation that needs Cloudflare bypass, always prefer CDP connection over headless launch. ### Nodriver Windows Asyncio Crashes (2026-08-21) Nodriver (`uc.start()`) crashes on Windows with `ValueError: I/O operation on closed pipe` in `asyncio.proactor_events`. This is a Windows-specific asyncio issue. Fall back to Playwright with `headless=False` or CDP connection. ### MCP Browser as Reliable Fallback When Playwright and nodriver both fail, the Chrome DevTools MCP (`mcp__chrome_devtools__*` tools) works reliably. It uses the existing Chrome instance with the user's profile, avoiding Cloudflare detection entirely. Use `evaluate_script` with nativeInputSetter for fastest form interactions. ### Standalone Chrome + CDP: самопроходящий Turnstile (2026-09-10) Когда все MCP/headless-пути висят на SPA-бандле (белая страница, бандл pending, хотя curl качает 200), а firecrawl_interact рвёт сессии — поднять отдельный Chrome с `--remote-debugging-port=9333 --user-data-dir=` (background=true), подключиться Playwright `connect_over_cdp`, заполнить форму и **поллить скрытое поле `cf-turnstile-response` до length>10** — в реальном Chrome non-interactive Turnstile решается сам за 10-20 сек. Полный разбор (RainProxy): `references/rainproxy-cdp-turnstile.md` — там же pitfalls: `has-text("Login")` ловит "Login with Google", Gmail-алиасы с `+` отклоняются, UTF-7 имена папок Gmail при русской локали. ### accounts.x.ai blocks ALL bpproxy residential IPs (2026-08-14) Все 25 bpproxy residential прокси возвращают `403` при прямом обращении к `accounts.x.ai`. Google (`accounts.google.com`) принимает те же прокси. Использовать Google OAuth flow вместо прямого email signup. ### DrissionPage CDP disconnection Cloudflare может детектировать и разрывать CDP-соединение DrissionPage (`PageDisconnectedError`). Переключиться на nodriver. ### DrissionPage proxy auth DrissionPage не поддерживает `user:pass@host:port` в URL прокси. Использовать proxy forwarder. ### Turnstile timing Non-interactive Turnstile требует 5-30 секунд. Недостаточное ожидание → кнопки не найдены (страница ещё не загрузилась после challenge). ### Geo-blocking bpproxy bpproxy residential выходят из разных стран (Ирак, Канада, etc.). Всегда проверять `ip-api.com/json` перед использованием с гео-зависимыми сервисами. ### bpproxy hardsession истекает Hardsession-прокси могут умереть через несколько часов. Периодически перегенерировать пул через `bpproxy` skill. ## Связанные скиллы - `bpproxy` — генерация и управление прокси - `bulk-account-registration` — пайплайн массовой регистрации ## Связанные файлы - `references/grok-registration-pipeline.md` — конкретный пайплайн регистрации Grok (x.ai) с DrissionPage - `scripts/proxy_forwarder.py` — HTTP forward proxy (DEPRECATED — не работает с Chrome, используй tcp_forwarder.py) - `scripts/tcp_forwarder.py` — RAW TCP forward proxy (работает с Chrome, nodriver, DrissionPage) - `templates/turnstile-patch/` — шаблон Chrome extension для обхода CDP-детекции (manifest.json + script.js)