autoreg-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
333
skills/anti-detection-browser/SKILL.md
Normal file
333
skills/anti-detection-browser/SKILL.md
Normal file
@@ -0,0 +1,333 @@
|
||||
---
|
||||
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": ["<all_urls>"],
|
||||
"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 <pid>`
|
||||
2. Проверить что порт свободен: `netstat -ano | grep <port>`
|
||||
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=<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) с 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)
|
||||
Reference in New Issue
Block a user