15 KiB
name, description, category
| name | description | category |
|---|---|---|
| anti-detection-browser | 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. | 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:
{
"manifest_version": 3,
"name": "Turnstile Patch",
"version": "1.0",
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["script.js"],
"run_at": "document_start",
"world": "MAIN"
}]
}
script.js:
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
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 режиме:
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-поиск по тексту:
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):
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()
Запуск:
python tcp_forwarder.py &
# Затем использовать http://127.0.0.1:9129 как browser_proxy
Geo-Blocking
Сервисы типа x.ai гео-блокируют по стране выхода прокси. Проверять перед использованием:
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
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)— выполнить JSel.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-регистрации:
- Зайти на
accounts.x.ai/sign-up?redirect=grok-com - Нажать «Continue with Google» (не «Use email»)
- Google OAuth-страница загружается через прокси (Google принимает bpproxy)
- Ввести Gmail + пароль + TOTP (если 2FA)
- Пройти consent screen
- Редирект обратно на Grok
Требования: Gmail аккаунты с 2FA-секретами (формат как в tusk_gmail_accounts.json).
# Пример: поиск кнопки 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-прокси:
- Убить старый forwarder:
taskkill /F /PID <pid> - Проверить что порт свободен:
netstat -ano | grep <port> - Запустить новый forwarder на уникальном порту
Рекомендация: использовать разные порты для разных upstream-прокси (9128, 9129, 9130...).
Nodriver browser.stop() не awaitable
browser.stop() в nodriver возвращает None — не использовать await:
# ❌ ОШИБКА: 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=<fresh> (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) с DrissionPagescripts/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)