super-stealth
This commit is contained in:
1 parent
2a058623a4
commit
692f4c0dd1
5 files changed
+35525
-178
No files matched your search
@@ -19,6 +19,25 @@ REMOTE_DEBUGGING_ENABLED = os.getenv("REMOTE_DEBUGGING_ENABLED", "false").lower(
|
||||
# Модель по умолчанию из переменной окружения
|
||||
DEFAULT_MODEL = os.getenv("LLM_MODEL_NAME", "openai/gpt-4o-mini")
|
||||
|
||||
# ===========================================
|
||||
# Настройки прокси
|
||||
# ===========================================
|
||||
PROXY_URL = os.getenv("PROXY_URL", "")
|
||||
CLOUD_PROXY_COUNTRY = os.getenv("CLOUD_PROXY_COUNTRY", "")
|
||||
|
||||
# ===========================================
|
||||
# Настройки stealth
|
||||
# ===========================================
|
||||
BROWSER_HEADLESS = os.getenv("BROWSER_HEADLESS", "false").lower() == "true"
|
||||
USE_XVFB = os.getenv("USE_XVFB", "true").lower() == "true"
|
||||
VIEWPORT_SIZE = os.getenv("VIEWPORT_SIZE", "") # Формат: WIDTHxHEIGHT, напр. 1920x1080
|
||||
|
||||
# ===========================================
|
||||
# Настройки решения капчи (CapSolver)
|
||||
# ===========================================
|
||||
CAPSOLVER_API_KEY = os.getenv("CAPSOLVER_API_KEY", "")
|
||||
AUTO_SOLVE_CAPTCHA = os.getenv("AUTO_SOLVE_CAPTCHA", "false").lower() == "true"
|
||||
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
import logging
|
||||
@@ -114,7 +133,193 @@ try:
|
||||
except Exception as e2:
|
||||
return ActionResult(error=f"Failed to paste text: {e2}")
|
||||
|
||||
logger.info("Custom tools (paste_text) registered successfully")
|
||||
@custom_tools.action(description='Solve CAPTCHA using CapSolver API. Use this when you encounter reCAPTCHA, hCaptcha, or Cloudflare Turnstile. The agent will automatically detect and solve the captcha.')
|
||||
async def solve_captcha(browser_session: BrowserSession = None):
|
||||
"""
|
||||
Решает капчу (reCAPTCHA/hCaptcha/Turnstile) с помощью CapSolver API.
|
||||
|
||||
Этот action автоматически:
|
||||
1. Обнаруживает тип капчи на странице
|
||||
2. Отправляет задачу в CapSolver
|
||||
3. Ждёт решения
|
||||
4. Вставляет ответ в форму
|
||||
"""
|
||||
if not CAPSOLVER_API_KEY:
|
||||
return ActionResult(
|
||||
error="CapSolver API key not configured. Set CAPSOLVER_API_KEY environment variable.",
|
||||
include_in_memory=True
|
||||
)
|
||||
|
||||
try:
|
||||
page = await browser_session.get_current_page()
|
||||
|
||||
# Ищем sitekey для разных типов капчи
|
||||
sitekey = None
|
||||
captcha_type = None
|
||||
|
||||
# reCAPTCHA v2
|
||||
recaptcha_element = await page.query_selector('[data-sitekey]')
|
||||
if recaptcha_element:
|
||||
sitekey = await recaptcha_element.get_attribute('data-sitekey')
|
||||
if sitekey:
|
||||
# Проверяем reCAPTCHA vs hCaptcha
|
||||
iframe = await page.query_selector('iframe[src*="google.com/recaptcha"]')
|
||||
if iframe:
|
||||
captcha_type = "RecaptchaV2Task"
|
||||
|
||||
# hCaptcha
|
||||
if not sitekey:
|
||||
hcaptcha_element = await page.query_selector('[data-sitekey]')
|
||||
if hcaptcha_element:
|
||||
sitekey = await hcaptcha_element.get_attribute('data-sitekey')
|
||||
hcaptcha_frame = await page.query_selector('iframe[src*="hcaptcha.com"]')
|
||||
if hcaptcha_frame and sitekey:
|
||||
captcha_type = "HCaptchaTask"
|
||||
|
||||
# Cloudflare Turnstile
|
||||
if not sitekey:
|
||||
turnstile_element = await page.query_selector('[data-sitekey]')
|
||||
if turnstile_element:
|
||||
sitekey = await turnstile_element.get_attribute('data-sitekey')
|
||||
turnstile_frame = await page.query_selector('iframe[src*="turnstile"]')
|
||||
if turnstile_frame and sitekey:
|
||||
captcha_type = "TurnstileTask"
|
||||
|
||||
if not sitekey or not captcha_type:
|
||||
return ActionResult(
|
||||
extracted_content="No captcha found on current page",
|
||||
include_in_memory=True
|
||||
)
|
||||
|
||||
page_url = page.url
|
||||
logger.info(f"Found {captcha_type} with sitekey: {sitekey}")
|
||||
|
||||
# Отправляем задачу в CapSolver
|
||||
import httpx
|
||||
|
||||
async with httpx.AsyncClient(timeout=60.0) as client:
|
||||
# Создаём задачу
|
||||
task_create_response = await client.post(
|
||||
"https://api.capsolver.com/createTask",
|
||||
json={
|
||||
"clientKey": CAPSOLVER_API_KEY,
|
||||
"task": {
|
||||
"type": captcha_type,
|
||||
"websiteURL": page_url,
|
||||
"websiteKey": sitekey,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
task_data = task_create_response.json()
|
||||
|
||||
if task_data.get("errorCode"):
|
||||
logger.error(f"CapSolver createTask error: {task_data}")
|
||||
return ActionResult(
|
||||
error=f"CapSolver error: {task_data.get('errorDescription', 'Unknown error')}",
|
||||
include_in_memory=True
|
||||
)
|
||||
|
||||
task_id = task_data.get("taskId")
|
||||
logger.info(f"CapSolver task created: {task_id}")
|
||||
|
||||
# Ждём решения (максимум 60 секунд)
|
||||
solution = None
|
||||
for attempt in range(30):
|
||||
await asyncio.sleep(2)
|
||||
|
||||
result_response = await client.post(
|
||||
"https://api.capsolver.com/getTaskResult",
|
||||
json={
|
||||
"clientKey": CAPSOLVER_API_KEY,
|
||||
"taskId": task_id
|
||||
}
|
||||
)
|
||||
|
||||
result_data = result_response.json()
|
||||
|
||||
if result_data.get("status") == "ready":
|
||||
solution = result_data.get("solution", {})
|
||||
break
|
||||
elif result_data.get("errorCode"):
|
||||
logger.error(f"CapSolver error: {result_data}")
|
||||
break
|
||||
|
||||
if not solution:
|
||||
return ActionResult(
|
||||
error="CapSolver timeout - captcha not solved",
|
||||
include_in_memory=True
|
||||
)
|
||||
|
||||
# Вставляем решение
|
||||
if captcha_type == "RecaptchaV2Task":
|
||||
g_recaptcha_response = solution.get("gRecaptchaResponse")
|
||||
if g_recaptcha_response:
|
||||
await page.evaluate(f'''() => {{
|
||||
const textarea = document.getElementById('g-recaptcha-response');
|
||||
if (textarea) {{
|
||||
textarea.value = '{g_recaptcha_response}';
|
||||
textarea.style.display = 'block';
|
||||
}}
|
||||
}}''')
|
||||
logger.info("reCAPTCHA solved successfully!")
|
||||
return ActionResult(
|
||||
extracted_content="reCAPTCHA solved successfully!",
|
||||
include_in_memory=True
|
||||
)
|
||||
|
||||
elif captcha_type == "HCaptchaTask":
|
||||
h_captcha_response = solution.get("hCaptchaResponse")
|
||||
if h_captcha_response:
|
||||
await page.evaluate(f'''() => {{
|
||||
const textarea = document.querySelector('[name="h-captcha-response"]');
|
||||
if (textarea) {{
|
||||
textarea.value = '{h_captcha_response}';
|
||||
textarea.style.display = 'block';
|
||||
}}
|
||||
}}''')
|
||||
logger.info("hCaptcha solved successfully!")
|
||||
return ActionResult(
|
||||
extracted_content="hCaptcha solved successfully!",
|
||||
include_in_memory=True
|
||||
)
|
||||
|
||||
elif captcha_type == "TurnstileTask":
|
||||
token = solution.get("token")
|
||||
if token:
|
||||
await page.evaluate(f'''(token) => {{
|
||||
const textarea = document.querySelector('[name="cf-turnstile-response"]');
|
||||
if (textarea) {{
|
||||
textarea.value = token;
|
||||
textarea.style.display = 'block';
|
||||
}}
|
||||
// Also try to submit to Turnstile directly
|
||||
const turnstile = window.__cf_challenge_js;
|
||||
if (turnstile) {{
|
||||
try {{
|
||||
turnstile.submit(token);
|
||||
}} catch(e) {{}}
|
||||
}}
|
||||
}}''', token)
|
||||
logger.info("Turnstile solved successfully!")
|
||||
return ActionResult(
|
||||
extracted_content="Turnstile solved successfully!",
|
||||
include_in_memory=True
|
||||
)
|
||||
|
||||
return ActionResult(
|
||||
error="Failed to apply captcha solution",
|
||||
include_in_memory=True
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error solving captcha: {e}")
|
||||
return ActionResult(
|
||||
error=f"Error solving captcha: {str(e)}",
|
||||
include_in_memory=True
|
||||
)
|
||||
|
||||
logger.info("Custom tools (paste_text, solve_captcha) registered successfully")
|
||||
except ImportError as e:
|
||||
custom_tools = None
|
||||
logger.warning(f"Could not import Tools/BrowserSession from browser_use: {e}. Custom tools disabled.")
|
||||
@@ -146,6 +351,150 @@ def patch_chrome_binary():
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not patch {path}: {e}")
|
||||
|
||||
|
||||
async def solve_captcha_with_capsolver(page, capsolver_api_key: str) -> bool:
|
||||
"""
|
||||
Решает капчу (reCAPTCHA/hCaptcha/Turnstile) с помощью CapSolver API.
|
||||
|
||||
Args:
|
||||
page: Playwright page object
|
||||
capsolver_api_key: API ключ CapSolver
|
||||
|
||||
Returns:
|
||||
True если капча решена, False иначе
|
||||
"""
|
||||
import httpx
|
||||
|
||||
if not capsolver_api_key:
|
||||
logger.warning("CapSolver API key not provided")
|
||||
return False
|
||||
|
||||
try:
|
||||
# Ищем sitekey
|
||||
# reCAPTCHA v2
|
||||
recaptcha_element = await page.query_selector('*[name="recaptcha-sitekey"]')
|
||||
if not recaptcha_element:
|
||||
recaptcha_element = await page.query_selector('[data-sitekey]')
|
||||
|
||||
sitekey = None
|
||||
captcha_type = None
|
||||
|
||||
if recaptcha_element:
|
||||
sitekey = await recaptcha_element.get_attribute('data-sitekey')
|
||||
if sitekey:
|
||||
captcha_type = "RecaptchaV2Task"
|
||||
|
||||
# hCaptcha
|
||||
if not sitekey:
|
||||
hcaptcha_element = await page.query_selector('[data-sitekey]')
|
||||
if hcaptcha_element:
|
||||
sitekey = await hcaptcha_element.get_attribute('data-sitekey')
|
||||
if sitekey:
|
||||
# Проверяем что это hCaptcha
|
||||
hcaptcha_frame = await page.query_selector('iframe[src*="hcaptcha.com"]')
|
||||
if hcaptcha_frame:
|
||||
captcha_type = "HCaptchaTask"
|
||||
|
||||
# Cloudflare Turnstile
|
||||
if not sitekey:
|
||||
turnstile_element = await page.query_selector('[data-sitekey]')
|
||||
if turnstile_element:
|
||||
sitekey = await turnstile_element.get_attribute('data-sitekey')
|
||||
if sitekey:
|
||||
turnstile_frame = await page.query_selector('iframe[src*="turnstile"]')
|
||||
if turnstile_frame:
|
||||
captcha_type = "TurnstileTask"
|
||||
|
||||
if not sitekey or not captcha_type:
|
||||
logger.info("No captcha found on page")
|
||||
return False
|
||||
|
||||
page_url = page.url
|
||||
logger.info(f"Found {captcha_type} with sitekey: {sitekey}")
|
||||
|
||||
# Отправляем задачу в CapSolver
|
||||
async with httpx.AsyncClient(timeout=60.0) as client:
|
||||
# Создаём задачу
|
||||
task_create_response = await client.post(
|
||||
"https://api.capsolver.com/createTask",
|
||||
json={
|
||||
"clientKey": capsolver_api_key,
|
||||
"task": {
|
||||
"type": captcha_type,
|
||||
"websiteURL": page_url,
|
||||
"websiteKey": sitekey,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
task_data = task_create_response.json()
|
||||
|
||||
if task_data.get("errorCode"):
|
||||
logger.error(f"CapSolver createTask error: {task_data}")
|
||||
return False
|
||||
|
||||
task_id = task_data.get("taskId")
|
||||
logger.info(f"CapSolver task created: {task_id}")
|
||||
|
||||
# Ждём решения
|
||||
for attempt in range(30):
|
||||
await asyncio.sleep(2)
|
||||
|
||||
result_response = await client.post(
|
||||
"https://api.capsolver.com/getTaskResult",
|
||||
json={
|
||||
"clientKey": capsolver_api_key,
|
||||
"taskId": task_id
|
||||
}
|
||||
)
|
||||
|
||||
result_data = result_response.json()
|
||||
|
||||
if result_data.get("status") == "ready":
|
||||
solution = result_data.get("solution", {})
|
||||
|
||||
if captcha_type == "RecaptchaV2Task":
|
||||
# Вставляем решение в textarea
|
||||
g_recaptcha_response = solution.get("gRecaptchaResponse")
|
||||
if g_recaptcha_response:
|
||||
# Ищем textarea для ответа
|
||||
textarea = await page.query_selector('#g-recaptcha-response')
|
||||
if textarea:
|
||||
await textarea.fill(g_recaptcha_response)
|
||||
logger.info("reCAPTCHA solved successfully!")
|
||||
return True
|
||||
elif captcha_type == "HCaptchaTask":
|
||||
h_captcha_response = solution.get("hCaptchaResponse")
|
||||
if h_captcha_response:
|
||||
# Вставляем в h-captcha-response
|
||||
textarea = await page.query_selector('[name="h-captcha-response"]')
|
||||
if textarea:
|
||||
await textarea.fill(h_captcha_response)
|
||||
logger.info("hCaptcha solved successfully!")
|
||||
return True
|
||||
elif captcha_type == "TurnstileTask":
|
||||
token = solution.get("token")
|
||||
if token:
|
||||
# Вставляем токен
|
||||
textarea = await page.query_selector('[name="cf-turnstile-response"]')
|
||||
if textarea:
|
||||
await textarea.fill(token)
|
||||
logger.info("Turnstile solved successfully!")
|
||||
return True
|
||||
|
||||
return True
|
||||
|
||||
elif result_data.get("errorCode"):
|
||||
logger.error(f"CapSolver error: {result_data}")
|
||||
return False
|
||||
|
||||
logger.warning("CapSolver timeout - captcha not solved")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error solving captcha: {e}")
|
||||
return False
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
# Startup logic
|
||||
@@ -371,12 +720,63 @@ async def run_browser_task(task_id: str, task: str, max_steps: int, model: str):
|
||||
# Внутренний порт для Chrome
|
||||
CHROME_INTERNAL_PORT = REMOTE_DEBUGGING_PORT + 1 # 9223
|
||||
|
||||
# ===========================================
|
||||
# Настройка viewport (рандомизация)
|
||||
# ===========================================
|
||||
if VIEWPORT_SIZE and 'x' in VIEWPORT_SIZE:
|
||||
try:
|
||||
width, height = map(int, VIEWPORT_SIZE.split('x'))
|
||||
except:
|
||||
width, height = 1920, 1080
|
||||
else:
|
||||
# Рандомизация viewport для большей уникальности
|
||||
import random
|
||||
# Популярные разрешения
|
||||
viewports = [
|
||||
(1920, 1080),
|
||||
(1366, 768),
|
||||
(1536, 864),
|
||||
(1440, 900),
|
||||
(1280, 720),
|
||||
(2560, 1440),
|
||||
(3840, 2160),
|
||||
]
|
||||
width, height = random.choice(viewports)
|
||||
|
||||
window_size = f"{width},{height}"
|
||||
|
||||
# Рандомизация user-agent
|
||||
import random
|
||||
user_agents = [
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36",
|
||||
]
|
||||
user_agent = random.choice(user_agents)
|
||||
|
||||
# ===========================================
|
||||
# Настройка прокси
|
||||
# ===========================================
|
||||
proxy_args = []
|
||||
if PROXY_URL:
|
||||
# Добавляем прокси в аргументы Chrome
|
||||
proxy_args = [f"--proxy-server={PROXY_URL}"]
|
||||
logger.info(f"[{task_id}] Using proxy: {PROXY_URL}")
|
||||
|
||||
# Запускаем Chrome с remote debugging на внутреннем порту
|
||||
# Используем xvfb-run для запуска в "headful" режиме (более скрытно)
|
||||
# Добавляем расширенные флаги для обхода антифрода
|
||||
chrome_cmd = [
|
||||
"xvfb-run",
|
||||
"--server-args=-screen 0 1920x1080x24",
|
||||
chrome_cmd = []
|
||||
|
||||
# xvfb если включено
|
||||
if USE_XVFB:
|
||||
chrome_cmd.extend([
|
||||
"xvfb-run",
|
||||
f"--server-args=-screen 0 {width}x{height}x24",
|
||||
])
|
||||
|
||||
chrome_cmd.extend([
|
||||
chrome_exe,
|
||||
f"--remote-debugging-port={CHROME_INTERNAL_PORT}",
|
||||
"--remote-debugging-address=127.0.0.1",
|
||||
@@ -387,9 +787,9 @@ async def run_browser_task(task_id: str, task: str, max_steps: int, model: str):
|
||||
"--disable-dev-shm-usage",
|
||||
"--no-first-run",
|
||||
"--no-default-browser-check",
|
||||
"--disable-blink-features=AutomationControlled", # Скрывает navigator.webdriver
|
||||
"--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
|
||||
"--window-size=1920,1080",
|
||||
"--disable-blink-features=AutomationControlled",
|
||||
f"--user-agent={user_agent}",
|
||||
f"--window-size={window_size}",
|
||||
"--lang=ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7",
|
||||
"--disable-infobars",
|
||||
"--password-store=basic",
|
||||
@@ -413,8 +813,12 @@ async def run_browser_task(task_id: str, task: str, max_steps: int, model: str):
|
||||
"--test-type",
|
||||
"--load-extension=/app/stealth-extension",
|
||||
"--disable-extensions-except=/app/stealth-extension",
|
||||
"about:blank"
|
||||
]
|
||||
])
|
||||
|
||||
# Добавляем прокси если есть
|
||||
chrome_cmd.extend(proxy_args)
|
||||
|
||||
chrome_cmd.append("about:blank")
|
||||
|
||||
logger.info(f"[{task_id}] Executing Chrome: {' '.join(chrome_cmd)}")
|
||||
|
||||
|
||||
Reference in new issue
Block a user