Опрашивать res.php каждые несколько секунд на десятках параллельных задач — рабочий, но затратный по трафику способ забрать токен. Параметр pingback в CaptchaAI решает это иначе: как только CAPTCHA решена, сервер сам отправляет GET-запрос на ваш URL с результатом, и опрос вообще не нужен. Ниже — рабочие схемы для продакшена: от простого хранилища результатов до маршрутизации уведомлений между обработчиками.
Из статьи вы получите:
- три готовых Python-паттерна приёма callback — от одиночной задачи до маршрутизации по обработчикам;
- чек-лист защиты pingback-эндпоинта от чужого трафика;
- критерий выбора между pingback и опросом
res.phpдля конкретно вашей нагрузки.
Как CaptchaAI отправляет уведомление
Прежде чем переходить к схемам, разберём базовый поток запросов:
1. Submit task with pingback=YOUR_CALLBACK_URL
2. CaptchaAI solves the CAPTCHA
3. CaptchaAI sends GET request to your callback:
YOUR_CALLBACK_URL?id=TASK_ID&code=TOKEN
4. Your server processes the result
На шаге 3 CaptchaAI сам обращается к вашему серверу — значит, эндпоинт должен быть доступен из интернета уже в момент отправки задачи, а не только к моменту готовности токена.
Прежде чем подключать pingback к продакшену, проверьте три вещи:
- эндпоинт слушает публичный порт (не
127.0.0.1, не адрес за NAT без проброса); - обработчик отвечает быстро (в идеале до 1–2 секунд) — долгая обработка внутри самого HTTP-хендлера задерживает подтверждение CaptchaAI;
- логика идемпотентна: одна и та же задача может прийти повторно, и повтор не должен ломать состояние.
Приём 1: fire-and-forget с потокобезопасным хранилищем
Самый простой вариант — отправить задачу и сохранить результат в словаре с блокировкой, как только придёт callback:
import requests
import threading
import time
from flask import Flask, request
class PingbackStore:
"""Store for results received via pingback."""
def __init__(self):
self.results = {}
self.events = {}
self.lock = threading.Lock()
def register(self, task_id):
"""Register a task ID we expect results for."""
with self.lock:
self.events[task_id] = threading.Event()
def store(self, task_id, token):
"""Store result from pingback callback."""
with self.lock:
self.results[task_id] = token
if task_id in self.events:
self.events[task_id].set()
def wait(self, task_id, timeout=120):
"""Wait for a specific result."""
event = self.events.get(task_id)
if not event:
return None
event.wait(timeout=timeout)
return self.results.get(task_id)
def get(self, task_id):
"""Get result without waiting (non-blocking)."""
return self.results.get(task_id)
# Global store
store = PingbackStore()
# Flask app for receiving callbacks
app = Flask(__name__)
@app.route("/pingback")
def receive_pingback():
"""Handle CaptchaAI pingback callback."""
task_id = request.args.get("id")
code = request.args.get("code")
if not task_id or not code:
return "Bad request", 400
store.store(task_id, code)
return "OK", 200
def submit_with_pingback(api_key, method, callback_url, **params):
"""Submit a task with pingback enabled."""
data = {
"key": api_key,
"method": method,
"pingback": callback_url,
"json": 1,
}
data.update(params)
resp = requests.post(
"https://ocr.captchaai.com/in.php",
data=data,
timeout=30,
)
result = resp.json()
if result.get("status") != 1:
raise RuntimeError(f"Submit error: {result.get('request')}")
task_id = result["request"]
store.register(task_id)
return task_id
# Usage
# Start Flask server in background thread
server = threading.Thread(
target=lambda: app.run(port=8080, debug=False),
daemon=True,
)
server.start()
# Submit task
task_id = submit_with_pingback(
"YOUR_API_KEY",
"userrecaptcha",
"https://yourserver.com/pingback",
googlekey="SITE_KEY",
pageurl="https://example.com",
)
# Wait for result via pingback
token = store.wait(task_id, timeout=120)
print(f"Token: {token[:50]}...")
Когда достаточно этой схемы
Такой схемы хватает, когда сервис решает задачи по одной — например, авторизация в веб-приложении по клику пользователя. Event из threading блокирует только тот запрос, который ждёт конкретный task_id, остальной процесс продолжает работать. Для десятков параллельных задач переходите к следующему приёму.
Приём 2: параллельная отправка задач (fan-out)
Когда нужно решить сразу пачку CAPTCHA, отправьте все задачи и собирайте токены по мере готовности — ждать очереди не придётся:
import requests
import threading
import time
class FanOutSolver:
"""Submit many tasks, collect results via pingback."""
def __init__(self, api_key, callback_url):
self.api_key = api_key
self.callback_url = callback_url
self.store = PingbackStore()
self.pending = []
def submit(self, method, **params):
"""Submit a task and track it."""
data = {
"key": self.api_key,
"method": method,
"pingback": self.callback_url,
"json": 1,
}
data.update(params)
resp = requests.post(
"https://ocr.captchaai.com/in.php",
data=data,
timeout=30,
)
result = resp.json()
if result.get("status") != 1:
raise RuntimeError(f"Submit error: {result.get('request')}")
task_id = result["request"]
self.store.register(task_id)
self.pending.append(task_id)
return task_id
def submit_batch(self, tasks):
"""Submit multiple tasks.
tasks: list of dicts with 'method' and params
"""
task_ids = []
for task in tasks:
method = task.pop("method")
task_id = self.submit(method, **task)
task_ids.append(task_id)
time.sleep(0.1) # Avoid rate limits
return task_ids
def collect_all(self, timeout=180):
"""Wait for all pending results."""
results = {}
deadline = time.time() + timeout
for task_id in self.pending:
remaining = max(1, deadline - time.time())
token = self.store.wait(task_id, timeout=remaining)
results[task_id] = token
self.pending.clear()
return results
# Usage
solver = FanOutSolver("YOUR_API_KEY", "https://yourserver.com/pingback")
# Submit 5 tasks
tasks = [
{
"method": "userrecaptcha",
"googlekey": "SITE_KEY",
"pageurl": f"https://example.com/page{i}",
}
for i in range(5)
]
task_ids = solver.submit_batch(tasks)
print(f"Submitted {len(task_ids)} tasks")
# Wait for all results
results = solver.collect_all(timeout=180)
for tid, token in results.items():
status = "solved" if token else "failed"
print(f" {tid}: {status}")
Почему пауза между отправками
time.sleep(0.1) в submit_batch — не декоративная строка. Без паузы пачка из сотен задач уходит на in.php одним залпом и упирается в ограничение частоты запросов на вашем тарифе; с паузой отправка растягивается на доли секунды на задачу и остаётся в пределах лимита, при этом решение всё равно идёт параллельно на стороне CaptchaAI.
Приём 3: маршрутизация уведомлений по обработчикам
Если один и тот же pingback-эндпоинт обслуживает разные сценарии — логин, парсинг, регистрацию, — распределяйте результаты по нужным обработчикам, а не разбирайте их в одном большом if:
import threading
from collections import defaultdict
class NotificationRouter:
"""Route pingback results to registered handlers."""
def __init__(self):
self.handlers = {}
self.default_handler = None
self.task_routes = {}
self.lock = threading.Lock()
def register_handler(self, name, handler_fn):
"""Register a named handler function."""
self.handlers[name] = handler_fn
def set_default(self, handler_fn):
"""Set a default handler for unrouted tasks."""
self.default_handler = handler_fn
def route(self, task_id, handler_name):
"""Route a task ID to a specific handler."""
with self.lock:
self.task_routes[task_id] = handler_name
def dispatch(self, task_id, token):
"""Dispatch a result to the correct handler."""
handler_name = self.task_routes.get(task_id)
if handler_name and handler_name in self.handlers:
self.handlers[handler_name](task_id, token)
elif self.default_handler:
self.default_handler(task_id, token)
# Usage
router = NotificationRouter()
# Register handlers
def login_handler(task_id, token):
print(f"Login flow got token from {task_id}")
# Submit token to login form
def scraping_handler(task_id, token):
print(f"Scraping pipeline got token from {task_id}")
# Continue scraping with token
router.register_handler("login", login_handler)
router.register_handler("scraping", scraping_handler)
# When submitting
task_id = submit_with_pingback(
"YOUR_API_KEY", "userrecaptcha",
"https://yourserver.com/pingback",
googlekey="KEY", pageurl="https://example.com",
)
router.route(task_id, "login")
# In pingback handler
# router.dispatch(task_id, token)
Когда нужен маршрутизатор
Если callback-эндпоинт один на всё приложение, а сценариев использования несколько (вход в аккаунт, парсинг карточек товара, регистрация), NotificationRouter избавляет от разрастающегося if task_type == "login": ... elif task_type == "scraping": ... прямо внутри обработчика Flask — обработчики регистрируются один раз при старте, а маршрутизация происходит по task_id.
Как защитить pingback-эндпоинт
Публичный URL, принимающий GET-запросы, — потенциальная цель для чужого трафика. Проверяйте формат параметров и не доверяйте телу запроса вслепую. Обязательный минимум проверок:
- параметры
idиcodeприсутствуют и не пустые; idсоответствует ожидаемому формату (у CaptchaAI это числовой идентификатор задачи);- запрос пришёл на известный вам
task_id— то есть задача была зарегистрирована на шаге отправки, а не появилась из ниоткуда; - по возможности — сверка IP-адреса отправителя со списком известных диапазонов CaptchaAI.
import hmac
import hashlib
from flask import Flask, request, abort
app = Flask(__name__)
API_KEY = "YOUR_API_KEY"
@app.route("/pingback")
def secure_pingback():
"""Validate pingback requests."""
task_id = request.args.get("id")
code = request.args.get("code")
ip = request.remote_addr
# Validate required parameters
if not task_id or not code:
abort(400)
# Validate IP (CaptchaAI server IPs)
# Add actual CaptchaAI IPs to allowlist
ALLOWED_IPS = {"0.0.0.0/0"} # Replace with real IPs
# Validate task ID format (numeric)
if not task_id.isdigit():
abort(400)
# Store result
store.store(task_id, code)
return "OK", 200
Если вы логируете IP-адрес и параметры запроса для отладки, учитывайте требования 152-ФЗ «О персональных данных» (для аудитории за пределами РФ — аналогичную дисциплину GDPR): храните только то, что нужно для диагностики, и не дольше срока ретенции логов.
Pingback или опрос: что выбрать
| Фактор | Pingback | Опрос res.php |
|---|---|---|
| Инфраструктура | Нужен публичный эндпоинт | Сервер не нужен |
| Задержка | Токен приходит сразу | Задержка до следующего интервала опроса (~5 с) |
| Масштаб | Комфортно от 100+ задач одновременно | Норма для <50 одновременных задач |
| Надёжность | Нужна идемпотентная обработка повторов | Простой цикл повтора |
| Сеть | Требуется входящий порт | Только исходящие запросы |
| Настройка | Выше сложность | Минимальная |
Команде на плане ADVANCE ($90/мес, 50 потоков) pingback особенно выгоден: поток не простаивает в ожидании ответа res.php, а сразу берёт следующую задачу — при том же тарифе через эндпоинт проходит больше решений в час, чем при постоянном опросе.
Коротко о выборе:
- Берите pingback, если у вас уже есть публичный сервер, задач много (десятки-сотни одновременно) и важна минимальная задержка получения токена.
- Оставайтесь на опросе
res.php, если проект небольшой, деплой без публичного порта (например, серверлесс-функция без входящего HTTP) или прототип, который ещё не готов к продакшен-инфраструктуре.
Типичные проблемы и решения
| Проблема | Причина | Как исправить |
|---|---|---|
| Callback не приходит | Эндпоинт недоступен извне | Убедитесь, что сервер публичный; проверьте firewall |
| Дублирующиеся callback | CaptchaAI повторяет отправку | Сделайте обработчик идемпотентным |
| Неверный ID задачи в callback | Устаревшее состояние на сервере | Проверьте момент регистрации задачи |
| Таймаут, хотя CAPTCHA решена | Callback URL недоступен | Сначала протестируйте эндпоинт через curl |
Если проблема не находится по таблице, начните с самого дешёвого теста: отправьте curl -X POST на ваш собственный /pingback с тестовыми id и code вручную. Если обработчик не отвечает 200 OK на прямой вызов, дело не в CaptchaAI и не в сети — ошибка внутри вашего кода, и её проще поймать в изоляции, чем через реальную задачу.
Часто задаваемые вопросы
Нужен ли HTTPS для callback URL?
Строгого требования нет, но HTTPS обязателен на практике: токен передаётся в query-параметре, и по HTTP его может перехватить кто угодно между CaptchaAI и вашим сервером.
Что делать, если pingback не пришёл вовремя?
Держите резервный опрос res.php по task_id с таймаутом — если callback не пришёл за разумное время (например, 60–120 секунд), заберите результат опросом и не блокируйте пайплайн бесконечным ожиданием.
Можно ли совмещать pingback и опрос res.php в одном проекте?
Да, и это обычная практика: pingback как основной канал для скорости, опрос — как страховка на случай сетевых сбоев между CaptchaAI и вашим эндпоинтом.
Можно ли тестировать pingback на localhost?
Нет, callback URL должен быть общедоступным. Для локальной разработки поднимите туннель (ngrok или аналог) и укажите его адрес в параметре pingback.
Нужно ли что-то дополнительно оплачивать за pingback?
Нет, pingback — это обычный параметр запроса к in.php, а не отдельная услуга: он включён в тот же тариф, что и опрос res.php, и не тарифицируется отдельно по потокам.
Итог: с чего начать
- Для одиночных задач достаточно Приёма 1 (fire-and-forget) — минимум кода, максимум пользы.
- Для пачек CAPTCHA переходите к Приёму 2 (fan-out) — экономит время на 5+ одновременных задачах.
- Для приложений с несколькими сценариями подключите Приём 3 (маршрутизатор) поверх любого из первых двух.
- В любом случае — разверните проверку параметров из раздела о защите эндпоинта до того, как включите pingback в продакшене, а не после первого инцидента.
Связанные руководства
Стройте событийно-ориентированные пайплайны — получите API-ключ CaptchaAI уже сейчас.