Selenium Grid распределяет сеансы браузера по нескольким машинам. В сочетании с CaptchaAI вы можете решать CAPTCHA в большом масштабе — параллельно запуская десятки или сотни экземпляров браузера, каждый из которых решает CAPTCHA независимо через один и тот же API.
В этом руководстве описывается настройка Grid (Selenium 4), конфигурация узлов, интеграция CaptchaAI и шаблоны масштабирования для высокопроизводительной автоматизации CAPTCHA.
Обзор архитектуры
┌─────────────┐ ┌──────────────┐ ┌──────────────┐
│ Test Script │────▶│ Grid Hub │────▶│ Node 1 │
│ (Client) │ │ (Router) │ │ Chrome x 5 │
└─────────────┘ └──────────────┘ └──────────────┘
│ ┌──────────────┐
├─────────────▶│ Node 2 │
│ │ Chrome x 5 │
│ └──────────────┘
│ ┌──────────────┐
└─────────────▶│ Node 3 │
│ Chrome x 5 │
└──────────────┘
All nodes share ──▶ CaptchaAI API (single API key)
Настройка Selenium Grid 4
Докер Составление
version: "3"
services:
selenium-hub:
image: selenium/hub:4.21.0
container_name: selenium-hub
ports:
- "4442:4442"
- "4443:4443"
- "4444:4444"
chrome-node-1:
image: selenium/node-chrome:4.21.0
depends_on:
- selenium-hub
environment:
- SE_EVENT_BUS_HOST=selenium-hub
- SE_EVENT_BUS_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
- SE_NODE_MAX_SESSIONS=5
- SE_NODE_OVERRIDE_MAX_SESSIONS=true
chrome-node-2:
image: selenium/node-chrome:4.21.0
depends_on:
- selenium-hub
environment:
- SE_EVENT_BUS_HOST=selenium-hub
- SE_EVENT_BUS_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
- SE_NODE_MAX_SESSIONS=5
- SE_NODE_OVERRIDE_MAX_SESSIONS=true
chrome-node-3:
image: selenium/node-chrome:4.21.0
depends_on:
- selenium-hub
environment:
- SE_EVENT_BUS_HOST=selenium-hub
- SE_EVENT_BUS_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
- SE_NODE_MAX_SESSIONS=5
- SE_NODE_OVERRIDE_MAX_SESSIONS=true
docker-compose up -d
CaptchaAI Клиент для Grid
import requests
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from concurrent.futures import ThreadPoolExecutor, as_completed
class GridCaptchaSolver:
CAPTCHAAI_URL = "https://ocr.captchaai.com"
def __init__(self, api_key, grid_url="http://localhost:4444"):
self.api_key = api_key
self.grid_url = grid_url
def create_session(self):
"""Create a new browser session on the Grid."""
options = webdriver.ChromeOptions()
options.add_argument("--no-sandbox")
options.add_argument("--disable-blink-features=AutomationControlled")
options.add_argument("--window-size=1920,1080")
driver = webdriver.Remote(
command_executor=self.grid_url,
options=options,
)
return driver
def solve_recaptcha_v2(self, site_url, sitekey):
"""Solve reCAPTCHA v2 via CaptchaAI API."""
# Submit
resp = requests.post(f"{self.CAPTCHAAI_URL}/in.php", data={
"key": self.api_key,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": site_url,
"json": 1,
})
data = resp.json()
if data["status"] != 1:
raise Exception(f"Submit: {data['request']}")
task_id = data["request"]
# Poll
for _ in range(60):
time.sleep(5)
resp = requests.get(f"{self.CAPTCHAAI_URL}/res.php", params={
"key": self.api_key, "action": "get",
"id": task_id, "json": 1,
})
data = resp.json()
if data["request"] == "CAPCHA_NOT_READY":
continue
if data["status"] != 1:
raise Exception(f"Solve: {data['request']}")
return data["request"]
raise Exception("Timeout")
def solve_turnstile(self, site_url, sitekey):
resp = requests.post(f"{self.CAPTCHAAI_URL}/in.php", data={
"key": self.api_key, "method": "turnstile",
"key": sitekey, "pageurl": site_url, "json": 1,
})
data = resp.json()
if data["status"] != 1:
raise Exception(f"Submit: {data['request']}")
task_id = data["request"]
for _ in range(60):
time.sleep(5)
resp = requests.get(f"{self.CAPTCHAAI_URL}/res.php", params={
"key": self.api_key, "action": "get",
"id": task_id, "json": 1,
})
data = resp.json()
if data["request"] == "CAPCHA_NOT_READY":
continue
if data["status"] != 1:
raise Exception(f"Solve: {data['request']}")
return data["request"]
raise Exception("Timeout")
def process_task(self, task):
"""Process a single CAPTCHA-protected task on a Grid node."""
driver = self.create_session()
try:
driver.get(task["url"])
time.sleep(2)
# Detect sitekey
sitekey = task.get("sitekey")
if not sitekey:
sitekey = driver.execute_script(
"return document.querySelector('[data-sitekey]')?.getAttribute('data-sitekey')"
)
if not sitekey:
return {"url": task["url"], "status": "no_captcha", "data": driver.page_source[:500]}
# Solve
token = self.solve_recaptcha_v2(task["url"], sitekey)
# Inject
driver.execute_script(f"""
document.querySelector('#g-recaptcha-response').value = '{token}';
document.querySelectorAll('[name="g-recaptcha-response"]').forEach(
el => el.value = '{token}'
);
""")
# Fill form and submit
if task.get("form_data"):
for field, value in task["form_data"].items():
driver.find_element(By.NAME, field).send_keys(value)
if task.get("submit_selector"):
driver.find_element(By.CSS_SELECTOR, task["submit_selector"]).click()
time.sleep(3)
return {
"url": task["url"],
"status": "success",
"result_url": driver.current_url,
"data": driver.page_source[:1000],
}
except Exception as e:
return {"url": task["url"], "status": "error", "error": str(e)}
finally:
driver.quit()
Параллельное выполнение
def run_parallel_tasks(api_key, tasks, max_workers=10):
"""Run CAPTCHA tasks in parallel across Grid nodes."""
solver = GridCaptchaSolver(api_key)
results = []
with ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = {
executor.submit(solver.process_task, task): task
for task in tasks
}
for future in as_completed(futures):
task = futures[future]
try:
result = future.result(timeout=600)
results.append(result)
print(f"[{result['status']}] {result['url']}")
except Exception as e:
results.append({
"url": task["url"],
"status": "exception",
"error": str(e),
})
return results
# Usage
tasks = [
{
"url": "https://site-a.com/form",
"sitekey": "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
"form_data": {"name": "Test User", "email": "test@example.com"},
"submit_selector": "#submit",
},
{
"url": "https://site-b.com/register",
"sitekey": "6LdKlZEpAAAAAAOQjzC2v_mJ-",
"form_data": {"username": "testuser"},
"submit_selector": "button[type='submit']",
},
# Add more tasks...
]
results = run_parallel_tasks("YOUR_API_KEY", tasks, max_workers=15)
# Summary
success = sum(1 for r in results if r["status"] == "success")
print(f"\nCompleted: {success}/{len(results)} successful")
Мониторинг состояния сети
import requests
def check_grid_status(grid_url="http://localhost:4444"):
"""Check Selenium Grid status and available nodes."""
try:
resp = requests.get(f"{grid_url}/status")
data = resp.json()
nodes = data.get("value", {}).get("nodes", [])
total_slots = 0
available_slots = 0
print(f"Grid Status: {data['value']['ready']}")
print(f"Nodes: {len(nodes)}")
for i, node in enumerate(nodes):
slots = node.get("slots", [])
free = sum(1 for s in slots if not s.get("session"))
total_slots += len(slots)
available_slots += free
print(f" Node {i+1}: {free}/{len(slots)} slots available")
print(f"Total capacity: {available_slots}/{total_slots} available")
return available_slots
except Exception as e:
print(f"Grid check failed: {e}")
return 0
# Adjust workers based on grid capacity
available = check_grid_status()
optimal_workers = min(available, 20)
print(f"Optimal workers: {optimal_workers}")
Автомасштабирование с помощью Kubernetes
# selenium-grid-k8s.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: selenium-chrome-node
spec:
replicas: 5
selector:
matchLabels:
app: selenium-chrome
template:
metadata:
labels:
app: selenium-chrome
spec:
containers:
- name: chrome
image: selenium/node-chrome:4.21.0
env:
- name: SE_EVENT_BUS_HOST
value: selenium-hub
- name: SE_EVENT_BUS_PUBLISH_PORT
value: "4442"
- name: SE_EVENT_BUS_SUBSCRIBE_PORT
value: "4443"
- name: SE_NODE_MAX_SESSIONS
value: "3"
resources:
limits:
memory: "2Gi"
cpu: "1"
requests:
memory: "1Gi"
cpu: "500m"
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: chrome-node-hpa
spec:
scaleRef:
apiVersion: apps/v1
kind: Deployment
name: selenium-chrome-node
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
Java-интеграция
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import java.net.URL;
import java.net.http.*;
import java.net.URI;
import java.util.concurrent.*;
public class GridCaptchaSolver {
private final String apiKey;
private final String gridUrl;
private final HttpClient httpClient;
public GridCaptchaSolver(String apiKey, String gridUrl) {
this.apiKey = apiKey;
this.gridUrl = gridUrl;
this.httpClient = HttpClient.newHttpClient();
}
public WebDriver createSession() throws Exception {
ChromeOptions options = new ChromeOptions();
options.addArguments("--no-sandbox", "--window-size=1920,1080");
return new RemoteWebDriver(new URL(gridUrl), options);
}
public List<Map<String, String>> runParallel(
List<Map<String, String>> tasks, int workers
) throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(workers);
List<Future<Map<String, String>>> futures = new ArrayList<>();
for (Map<String, String> task : tasks) {
futures.add(executor.submit(() -> processTask(task)));
}
List<Map<String, String>> results = new ArrayList<>();
for (Future<Map<String, String>> future : futures) {
results.add(future.get(600, TimeUnit.SECONDS));
}
executor.shutdown();
return results;
}
}
Поиск неисправностей
| Проблема | Причина | Исправить |
|---|---|---|
SessionNotCreated |
Нет доступных слотов | Увеличить количество узлов или SE_NODE_MAX_SESSIONS |
| Тайм-аут в сетке | Узел перегружен | Уменьшите количество одновременных сеансов на узел |
WebDriverException |
Узел отключен | Добавить логику повтора для создания сеанса |
| Истощение памяти | Слишком много экземпляров браузера | Установите ограничения ресурсов и максимальное количество сеансов |
| Тайм-аут решения CAPTCHA | API под нагрузкой | Увеличьте время ожидания опроса, добавьте повторы |
| Устаревшие сеансы | Задержка очистки сетки | Установить SE_SESSION_TIMEOUT |
Часто задаваемые вопросы
Сколько одновременных сессий я могу запустить?
Каждый узел Grid обычно обрабатывает 3–5 сеансов Chrome. При 10 узлах вы получаете 30-50 параллельных сессий. CaptchaAI управляет параллелизмом на стороне API.
Должен ли я запускать одно решение за сеанс или использовать сеансы повторно?
Создайте новый сеанс для каждой задачи для изоляции. Повторно используйте сеансы, только если задачи используют один и тот же домен и файлы cookie.
Поддерживает ли Selenium Grid 4 динамическое масштабирование?
Да. Благодаря Kubernetes и HPA (Horizontal Pod Autoscaler) узлы автоматически масштабируются в зависимости от загрузки ЦП.
Могу ли я смешивать типы браузеров в Grid?
Да. Добавьте узлы Firefox или Edge рядом с Chrome. API CaptchaAI не зависит от браузера — ему нужны только ключ сайта и URL-адрес.
Связанные руководства
- Руководство по Python Selenium + CaptchaAI
- Selenium Wire + CaptchaAI
- Рабочие потоки Node.js для параллельного решения
- Масштабируйте решение CAPTCHA в распределенных экземплярах браузера —получите ключ CaptchaAIи развернуть с помощью Selenium Grid.*