Compare commits
12
Commits
7625b3a9ad
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b74efb3e6 | ||
|
|
7256e215bb | ||
|
|
4e75beaeca | ||
|
|
87b8bbb075 | ||
|
|
6959670729 | ||
|
|
d436d6aaca | ||
|
|
e8143e2627 | ||
|
|
a3c3d65c02 | ||
|
|
84ccb7bac4 | ||
|
|
b3d31cb2d2 | ||
|
|
80760b9dd8 | ||
|
|
729ada2a6f |
No files matched your search
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
# ===========================================
|
# ===========================================
|
||||||
# Browser Use MCP Server Configuration
|
# Browser Use MCP Server Configuration
|
||||||
# ===========================================
|
# ===========================================
|
||||||
|
#
|
||||||
# OpenRouter API Key (обязательно)
|
# OpenRouter API Key (обязательно)
|
||||||
# Получить на https://openrouter.ai/keys
|
# Получить на https://openrouter.ai/keys
|
||||||
OPENROUTER_API_KEY=your_openrouter_api_key_here
|
OPENROUTER_API_KEY=your_openrouter_api_key_here
|
||||||
|
|||||||
+3
-1
@@ -1,10 +1,12 @@
|
|||||||
FROM mcr.microsoft.com/playwright/python:v1.49.0-noble
|
FROM mcr.microsoft.com/playwright/python:v1.49.0-noble
|
||||||
|
|
||||||
# Установка системных зависимостей
|
# Установка системных зависимостей
|
||||||
# socat — проброс портов для CDP
|
# socat — проброс портов для CDP_
|
||||||
# Видеокодеки и мультимедийные библиотеки для воспроизведения видео в Chrome
|
# Видеокодеки и мультимедийные библиотеки для воспроизведения видео в Chrome
|
||||||
RUN apt-get update && apt-get install -y \
|
RUN apt-get update && apt-get install -y \
|
||||||
socat \
|
socat \
|
||||||
|
wget \
|
||||||
|
unzip \
|
||||||
libgstreamer1.0-0 \
|
libgstreamer1.0-0 \
|
||||||
gstreamer1.0-plugins-base \
|
gstreamer1.0-plugins-base \
|
||||||
gstreamer1.0-plugins-good \
|
gstreamer1.0-plugins-good \
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ CAPSOLVER_API_KEY = os.getenv("CAPSOLVER_API_KEY", "")
|
|||||||
AUTO_SOLVE_CAPTCHA = os.getenv("AUTO_SOLVE_CAPTCHA", "false").lower() == "true"
|
AUTO_SOLVE_CAPTCHA = os.getenv("AUTO_SOLVE_CAPTCHA", "false").lower() == "true"
|
||||||
|
|
||||||
# ===========================================
|
# ===========================================
|
||||||
# Настройки обхода reCAPTCHA
|
# Настройки обхода reCAPTCHA_
|
||||||
# ===========================================
|
# ===========================================
|
||||||
RECAPTCHA_BYPASS = os.getenv("RECAPTCHA_BYPASS", "false").lower() == "true"
|
RECAPTCHA_BYPASS = os.getenv("RECAPTCHA_BYPASS", "false").lower() == "true"
|
||||||
|
|
||||||
@@ -59,8 +59,28 @@ logger.info(f"USE_XVFB: {USE_XVFB}")
|
|||||||
logger.info(f"REMOTE_DEBUGGING_ENABLED: {REMOTE_DEBUGGING_ENABLED}")
|
logger.info(f"REMOTE_DEBUGGING_ENABLED: {REMOTE_DEBUGGING_ENABLED}")
|
||||||
logger.info(f"RECAPTCHA_BYPASS: {RECAPTCHA_BYPASS}")
|
logger.info(f"RECAPTCHA_BYPASS: {RECAPTCHA_BYPASS}")
|
||||||
logger.info(f"AUTO_SOLVE_CAPTCHA: {AUTO_SOLVE_CAPTCHA}")
|
logger.info(f"AUTO_SOLVE_CAPTCHA: {AUTO_SOLVE_CAPTCHA}")
|
||||||
|
logger.info(f"CAPSOLVER_API_KEY: {'set' if CAPSOLVER_API_KEY else 'not set'}")
|
||||||
|
logger.info(f"PROXY_URL: {PROXY_URL if PROXY_URL else 'not set'}")
|
||||||
logger.info(f"===========================")
|
logger.info(f"===========================")
|
||||||
|
|
||||||
|
# Настраиваем CapSolver расширение с API ключом
|
||||||
|
if CAPSOLVER_API_KEY:
|
||||||
|
capsolver_config_path = "/app/capsolver-extension/assets/config.js"
|
||||||
|
if os.path.exists(capsolver_config_path):
|
||||||
|
try:
|
||||||
|
with open(capsolver_config_path, 'r') as f:
|
||||||
|
config_content = f.read()
|
||||||
|
# Заменяем apiKey на реальный
|
||||||
|
import re
|
||||||
|
config_content = re.sub(r'apiKey:\s*"[^"]*"', f'apiKey: "{CAPSOLVER_API_KEY}"', config_content)
|
||||||
|
with open(capsolver_config_path, 'w') as f:
|
||||||
|
f.write(config_content)
|
||||||
|
logger.info("CapSolver API key configured in extension")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Failed to configure CapSolver API key: {e}")
|
||||||
|
else:
|
||||||
|
logger.warning(f"CapSolver config not found at {capsolver_config_path}")
|
||||||
|
|
||||||
# Создаём Tools с кастомными действиями
|
# Создаём Tools с кастомными действиями
|
||||||
try:
|
try:
|
||||||
from browser_use import Tools, BrowserSession
|
from browser_use import Tools, BrowserSession
|
||||||
@@ -198,7 +218,7 @@ try:
|
|||||||
sitekey = await turnstile_element.get_attribute('data-sitekey')
|
sitekey = await turnstile_element.get_attribute('data-sitekey')
|
||||||
turnstile_frame = await page.query_selector('iframe[src*="turnstile"]')
|
turnstile_frame = await page.query_selector('iframe[src*="turnstile"]')
|
||||||
if turnstile_frame and sitekey:
|
if turnstile_frame and sitekey:
|
||||||
captcha_type = "TurnstileTask"
|
captcha_type = "AntiTurnstileTaskProxyLess"
|
||||||
|
|
||||||
if not sitekey or not captcha_type:
|
if not sitekey or not captcha_type:
|
||||||
return ActionResult(
|
return ActionResult(
|
||||||
@@ -299,7 +319,7 @@ try:
|
|||||||
include_in_memory=True
|
include_in_memory=True
|
||||||
)
|
)
|
||||||
|
|
||||||
elif captcha_type == "TurnstileTask":
|
elif captcha_type == "AntiTurnstileTaskProxyLess":
|
||||||
token = solution.get("token")
|
token = solution.get("token")
|
||||||
if token:
|
if token:
|
||||||
await page.evaluate(f'''(token) => {{
|
await page.evaluate(f'''(token) => {{
|
||||||
@@ -418,7 +438,7 @@ async def solve_captcha_with_capsolver(page, capsolver_api_key: str) -> bool:
|
|||||||
if sitekey:
|
if sitekey:
|
||||||
turnstile_frame = await page.query_selector('iframe[src*="turnstile"]')
|
turnstile_frame = await page.query_selector('iframe[src*="turnstile"]')
|
||||||
if turnstile_frame:
|
if turnstile_frame:
|
||||||
captcha_type = "TurnstileTask"
|
captcha_type = "AntiTurnstileTaskProxyLess"
|
||||||
|
|
||||||
if not sitekey or not captcha_type:
|
if not sitekey or not captcha_type:
|
||||||
logger.info("No captcha found on page")
|
logger.info("No captcha found on page")
|
||||||
@@ -487,7 +507,7 @@ async def solve_captcha_with_capsolver(page, capsolver_api_key: str) -> bool:
|
|||||||
await textarea.fill(h_captcha_response)
|
await textarea.fill(h_captcha_response)
|
||||||
logger.info("hCaptcha solved successfully!")
|
logger.info("hCaptcha solved successfully!")
|
||||||
return True
|
return True
|
||||||
elif captcha_type == "TurnstileTask":
|
elif captcha_type == "AntiTurnstileTaskProxyLess":
|
||||||
token = solution.get("token")
|
token = solution.get("token")
|
||||||
if token:
|
if token:
|
||||||
# Вставляем токен
|
# Вставляем токен
|
||||||
@@ -791,6 +811,7 @@ async def run_browser_task(task_id: str, task: str, max_steps: int, model: str):
|
|||||||
f"--server-args=-screen 0 {width}x{height}x24",
|
f"--server-args=-screen 0 {width}x{height}x24",
|
||||||
])
|
])
|
||||||
|
|
||||||
|
# Всегда добавляем --no-sandbox
|
||||||
chrome_cmd.extend([
|
chrome_cmd.extend([
|
||||||
chrome_exe,
|
chrome_exe,
|
||||||
f"--remote-debugging-port={CHROME_INTERNAL_PORT}",
|
f"--remote-debugging-port={CHROME_INTERNAL_PORT}",
|
||||||
@@ -823,18 +844,23 @@ async def run_browser_task(task_id: str, task: str, max_steps: int, model: str):
|
|||||||
"--enable-unsafe-swiftshader",
|
"--enable-unsafe-swiftshader",
|
||||||
"--ignore-gpu-blocklist",
|
"--ignore-gpu-blocklist",
|
||||||
"--enable-webgl",
|
"--enable-webgl",
|
||||||
"--use-gl=angle",
|
"--use-gl=desktop",
|
||||||
"--use-angle=swiftshader",
|
"--ignore-gpu-blocklist",
|
||||||
"--test-type",
|
"--enable-accelerated-2d-canvas",
|
||||||
# WebGPU флаги для Cloudflare Turnstile
|
"--enable-gpu-rasterization",
|
||||||
"--enable-webgpu",
|
"--enable-zero-copy",
|
||||||
"--enable-webgpu-developer-features",
|
"--ignore-webgl-blacklist",
|
||||||
"--use-angle=swiftshader",
|
# Добавляем экспериментальные флаги для WebGL
|
||||||
# Отключено: stealth-расширение вызывает ошибки и ломает Cloudflare Turnstile
|
"--enable-experimental-webgl",
|
||||||
# "--load-extension=/app/stealth-extension",
|
"--enable-webgl2-compute-context",
|
||||||
# "--disable-extensions-except=/app/stealth-extension",
|
|
||||||
])
|
])
|
||||||
|
|
||||||
|
# Убираем загрузку расширения CapSolver - используем API вместо него
|
||||||
|
# Расширение конфликтует с Cloudflare Turnstile
|
||||||
|
# Решение капчи происходит через функцию solve_captcha_with_capsolver
|
||||||
|
if AUTO_SOLVE_CAPTCHA and CAPSOLVER_API_KEY:
|
||||||
|
logger.info(f"[{task_id}] CapSolver will solve CAPTCHA via API (not extension)")
|
||||||
|
|
||||||
# Добавляем специальные флаги для обхода reCAPTCHA если включено
|
# Добавляем специальные флаги для обхода reCAPTCHA если включено
|
||||||
if RECAPTCHA_BYPASS:
|
if RECAPTCHA_BYPASS:
|
||||||
chrome_cmd.extend([
|
chrome_cmd.extend([
|
||||||
@@ -848,6 +874,14 @@ async def run_browser_task(task_id: str, task: str, max_steps: int, model: str):
|
|||||||
"--disable-ipc-flooding-protection",
|
"--disable-ipc-flooding-protection",
|
||||||
])
|
])
|
||||||
logger.info(f"[{task_id}] Added reCAPTCHA bypass flags")
|
logger.info(f"[{task_id}] Added reCAPTCHA bypass flags")
|
||||||
|
else:
|
||||||
|
# Всегда добавляем --no-sandbox для работы в контейнере
|
||||||
|
# и --disable-blink-features=AutomationControlled для обхода Cloudflare Turnstile
|
||||||
|
chrome_cmd.extend([
|
||||||
|
"--no-sandbox",
|
||||||
|
"--disable-blink-features=AutomationControlled",
|
||||||
|
])
|
||||||
|
logger.info(f"[{task_id}] Added minimal stealth flags for Cloudflare Turnstile")
|
||||||
|
|
||||||
# Добавляем прокси если есть
|
# Добавляем прокси если есть
|
||||||
chrome_cmd.extend(proxy_args)
|
chrome_cmd.extend(proxy_args)
|
||||||
@@ -913,39 +947,70 @@ async def run_browser_task(task_id: str, task: str, max_steps: int, model: str):
|
|||||||
logger.info(f"[{task_id}] Using new Chrome on internal port {CHROME_INTERNAL_PORT}")
|
logger.info(f"[{task_id}] Using new Chrome on internal port {CHROME_INTERNAL_PORT}")
|
||||||
|
|
||||||
# Инжектируем stealth-скрипты через CDP для дополнительной защиты
|
# Инжектируем stealth-скрипты через CDP для дополнительной защиты
|
||||||
# Отключено: stealth-скрипты вызывают ошибки и ломают Cloudflare Turnstile
|
try:
|
||||||
# try:
|
import json
|
||||||
# import json
|
stealth_script_path = "/app/stealth-extension/stealth.js"
|
||||||
# stealth_script_path = "/app/stealth-extension/stealth.js"
|
if os.path.exists(stealth_script_path):
|
||||||
# if os.path.exists(stealth_script_path):
|
with open(stealth_script_path, 'r') as f:
|
||||||
# with open(stealth_script_path, 'r') as f:
|
stealth_js = f.read()
|
||||||
# stealth_js = f.read()
|
|
||||||
#
|
# Получаем список целей (вкладок) через CDP
|
||||||
# # Получаем список целей (вкладок) через CDP
|
response = urllib.request.urlopen(f"{cdp_url}/json/list", timeout=5)
|
||||||
# response = urllib.request.urlopen(f"{cdp_url}/json/list", timeout=5)
|
targets = json.loads(response.read().decode())
|
||||||
# targets = json.loads(response.read().decode())
|
|
||||||
#
|
for target in targets:
|
||||||
# for target in targets:
|
if target.get('type') == 'page':
|
||||||
# if target.get('type') == 'page':
|
ws_url = target.get('webSocketDebuggerUrl')
|
||||||
# ws_url = target.get('webSocketDebuggerUrl')
|
if ws_url:
|
||||||
# if ws_url:
|
logger.info(f"[{task_id}] Injecting stealth scripts via CDP into {target.get('url', 'unknown')}")
|
||||||
# logger.info(f"[{task_id}] Injecting stealth scripts via CDP into {target.get('url', 'unknown')}")
|
|
||||||
#
|
logger.info(f"[{task_id}] Stealth extension loaded + CDP stealth injection ready")
|
||||||
# logger.info(f"[{task_id}] Stealth extension loaded + CDP stealth injection ready")
|
else:
|
||||||
# else:
|
logger.warning(f"[{task_id}] Stealth script not found at {stealth_script_path}")
|
||||||
# logger.warning(f"[{task_id}] Stealth script not found at {stealth_script_path}")
|
except Exception as e:
|
||||||
# except Exception as e:
|
logger.warning(f"[{task_id}] CDP stealth injection failed (extension should still work): {e}")
|
||||||
# logger.warning(f"[{task_id}] CDP stealth injection failed (extension should still work): {e}")
|
|
||||||
|
|
||||||
# Подключаемся через CDP
|
# Подключаемся через CDP
|
||||||
browser = Browser(cdp_url=cdp_url)
|
browser = Browser(cdp_url=cdp_url)
|
||||||
else:
|
else:
|
||||||
# Обычный запуск
|
# Обычный запуск
|
||||||
|
# Добавляем флаги для работы в контейнере
|
||||||
|
extra_chromium_args = [
|
||||||
|
"--no-sandbox",
|
||||||
|
"--disable-dev-shm-usage",
|
||||||
|
"--disable-gpu",
|
||||||
|
"--enable-webgl",
|
||||||
|
"--use-gl=desktop",
|
||||||
|
"--ignore-gpu-blocklist",
|
||||||
|
"--enable-accelerated-2d-canvas",
|
||||||
|
"--enable-gpu-rasterization",
|
||||||
|
"--enable-zero-copy",
|
||||||
|
"--ignore-webgl-blacklist",
|
||||||
|
"--enable-experimental-webgl",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Используем Google Chrome если доступен
|
||||||
|
chrome_path = os.getenv("CHROME_PATH", "/usr/bin/google-chrome")
|
||||||
|
if os.path.exists(chrome_path):
|
||||||
|
logger.info(f"Using Google Chrome at {chrome_path}")
|
||||||
|
else:
|
||||||
|
logger.info("Google Chrome not found, using default Chromium")
|
||||||
|
chrome_path = None
|
||||||
|
|
||||||
|
# Убираем загрузку расширения CapSolver - используем API
|
||||||
|
# Расширение конфликтует с Cloudflare Turnstile
|
||||||
|
extensions = []
|
||||||
|
if AUTO_SOLVE_CAPTCHA and CAPSOLVER_API_KEY:
|
||||||
|
logger.info("CapSolver will solve CAPTCHA via API (not extension)")
|
||||||
|
|
||||||
browser_profile = BrowserProfile(
|
browser_profile = BrowserProfile(
|
||||||
user_data_dir="/app/browser-data/main_profile",
|
user_data_dir="/app/browser-data/main_profile",
|
||||||
profile_directory="Default",
|
profile_directory="Default",
|
||||||
headless=False, # Всегда false для поддержки reCAPTCHA
|
headless=False, # Всегда false для поддержки reCAPTCHA
|
||||||
keep_alive=True,
|
keep_alive=True,
|
||||||
|
extra_chromium_args=extra_chromium_args,
|
||||||
|
executable_path=chrome_path,
|
||||||
|
extensions=extensions,
|
||||||
)
|
)
|
||||||
browser = Browser(browser_profile=browser_profile)
|
browser = Browser(browser_profile=browser_profile)
|
||||||
|
|
||||||
|
|||||||
+6
-5
@@ -6,23 +6,24 @@ services:
|
|||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
image: browser-use-mcp:latest
|
image: browser-use-mcp:latest
|
||||||
container_name: browser-use
|
|
||||||
security_opt:
|
security_opt:
|
||||||
- seccomp:unconfined
|
- seccomp:unconfined
|
||||||
- apparmor:unconfined
|
- apparmor:unconfined
|
||||||
ports:
|
ports:
|
||||||
- "8383:8383"
|
- "${APP_PORT_8383}:${CONTAINER_PORT_8383}"
|
||||||
- "9222:9222"
|
- "${APP_PORT_9222}:${CONTAINER_PORT_9222}"
|
||||||
environment:
|
environment:
|
||||||
- OPENROUTER_API_KEY=${OPENROUTER_API_KEY}
|
- OPENROUTER_API_KEY=${OPENROUTER_API_KEY}
|
||||||
- LLM_MODEL_NAME=${LLM_MODEL_NAME:-arcee-ai/trinity-large-preview:free}
|
- LLM_MODEL_NAME=${LLM_MODEL_NAME:-arcee-ai/trinity-large-preview:free}
|
||||||
- REMOTE_DEBUGGING_ENABLED=true
|
- REMOTE_DEBUGGING_ENABLED=true
|
||||||
- REMOTE_DEBUGGING_PORT=9222
|
- REMOTE_DEBUGGING_PORT=${CONTAINER_PORT_9222}
|
||||||
# CapSolver для автоматического решения капчи (Turnstile, reCAPTCHA, hCaptcha)
|
# CapSolver для автоматического решения капчи (Turnstile, reCAPTCHA, hCaptcha)
|
||||||
- CAPSOLVER_API_KEY=${CAPSOLVER_API_KEY:-}
|
- CAPSOLVER_API_KEY=${CAPSOLVER_API_KEY:-}
|
||||||
- AUTO_SOLVE_CAPTCHA=${AUTO_SOLVE_CAPTCHA:-false}
|
- AUTO_SOLVE_CAPTCHA=${AUTO_SOLVE_CAPTCHA:-false}
|
||||||
|
# ВАЖНО! Прокси обязательна для Turnstile! Без прокси Cloudflare детектит бота
|
||||||
|
- PROXY_URL=${PROXY_URL:-}
|
||||||
volumes:
|
volumes:
|
||||||
- /home/nafer/dokploy_data/browser-use:/app/browser-data
|
- "${APP_DATA_PATH}:/app/browser-data"
|
||||||
shm_size: '4gb'
|
shm_size: '4gb'
|
||||||
ipc: host
|
ipc: host
|
||||||
deploy:
|
deploy:
|
||||||
|
|||||||
Reference in new issue
Block a user