Если API CaptchaAI возвращает ERROR_PAGEURL, токен почти наверняка не пройдёт проверку на стороне сайта. Решатель сверяет домен из параметра pageurl с доменом, на котором CAPTCHA реально показывается пользователю, и при малейшем расхождении отклоняет запрос.
Разбираемся, откуда берётся это расхождение и как проверить pageurl ещё до отправки — а не после того, как токен уже отклонён и поток потрачен впустую.
Причины ERROR_PAGEURL и быстрая диагностика
Типичные причины
На практике причина почти всегда одна из этих:
- Нет протокола —
example.comвместоhttps://example.com. - Домен указан неточно —
www.example.comвместоexample.com(или наоборот). - Редирект изменил адрес — форма на
/login, а сервер фактически отдаёт/auth/login. - Маршрут SPA не совпадает с адресом на сервере — JS-роут
/app/login≠ URL, который видит бэкенд. - Спецсимволы или пробелы не закодированы — незакодированные пробелы, кириллица в пути и т.п.
- CAPTCHA рендерится в iframe с другого (под)домена — виджет реально грузится не с того домена, что указан в
pageurl. - Лишний слеш или query-параметр трекинга —
/login/вместо/login,?utm_source=...в адресе.
Прежде чем разбирать код ниже, быстро сверьтесь со списком — в девяти случаях из десяти проблема находится там за минуту.
Таблица диагностики: симптом → причина → решение
Если беглый просмотр причин выше не помог, сверьтесь с симптомами, которые видит разработчик:
| Симптом | Причина | Решение |
|---|---|---|
| Ошибка, хотя URL выглядит верным | Расхождение www / без www | Сверьте адресную строку посимвольно |
| URL то работает, то нет | На странице несколько вариантов адреса (A/B-тест) | Захватывайте адрес именно в момент решения CAPTCHA |
| Токен решён, но сайт его отклоняет | Домен в pageurl не совпадает с доменом сайта |
Домен токена должен совпадать с доменом сайта |
| В браузере всё работает, в коде — ошибка | Скрипт не отследил редирект | Используйте get_final_url() |
| В адресе есть query-параметры | Часть параметров может быть обязательной | Сохраняйте параметры, кроме трекинговых |
Как определить и провалидировать pageurl
Правило: адрес из адресной строки, не из конфига
pageurl — это в точности то, что показывает адресная строка браузера в момент, когда CAPTCHA видна пользователю.
# WRONG — incomplete URL
pageurl = "https://staging.example.com/qa-login"
# WRONG — wrong protocol
pageurl = "http://https://staging.example.com/qa-login"
# CORRECT — full URL with protocol
pageurl = "https://staging.example.com/qa-login"
# CORRECT — with www if that's what the page uses
pageurl = "https://www.https://staging.example.com/qa-login"
Проверяйте это вручную при первой интеграции — копируйте адрес из адресной строки, а не из конфига фронтенда: там нередко остаётся старый или тестовый URL.
Валидация pageurl перед отправкой запроса
Чтобы не гонять заведомо некорректный pageurl через API и не тратить поток впустую, проверяйте его локально до вызова in.php:
from urllib.parse import urlparse
def validate_pageurl(url):
"""Validate pageurl before API submission."""
parsed = urlparse(url)
if not parsed.scheme:
raise ValueError(f"Missing protocol: {url}. Use https://")
if parsed.scheme not in ("http", "https"):
raise ValueError(f"Invalid protocol: {parsed.scheme}")
if not parsed.netloc:
raise ValueError(f"Missing domain: {url}")
# Remove fragment (hash) — not sent to server
clean = f"{parsed.scheme}://{parsed.netloc}{parsed.path}"
if parsed.query:
clean += f"?{parsed.query}"
return clean
# Usage
url = validate_pageurl("https://staging.example.com/qa-login#section")
# Returns: "https://staging.example.com/qa-login"
Функция отсекает отсутствующий протокол, посторонние схемы вроде ftp:// и URL без домена, а заодно убирает хэш-фрагмент — он не долетает до сервера и не должен попадать в pageurl.
Редиректы и SPA: какой URL передавать
Что делать с редиректами
Если форма логина сама уводит пользователя на другой адрес — например, /login перенаправляет на /auth/login — в pageurl идёт тот адрес, где CAPTCHA реально отрисовалась, а не тот, с которого начался переход:
import requests
def get_final_url(url):
"""Follow redirects to get the actual page URL."""
resp = requests.get(url, allow_redirects=True, timeout=15)
return resp.url
# If the login page redirects
original = "https://staging.example.com/qa-login"
final = get_final_url(original)
print(f"Final URL: {final}")
# Use final URL as pageurl
Особенности SPA
В одностраничных приложениях адрес в браузере меняется через JavaScript без перезагрузки страницы, а бэкенд об этом может ничего не знать. Решателю нужен именно домен, на котором показана CAPTCHA:
# For SPAs, use the domain root + the route shown in the address bar
# NOT the API endpoint that the form submits to
# WRONG — API endpoint
pageurl = "https://api.example.com/v1/auth/login"
# CORRECT — the page URL shown in browser
pageurl = "https://staging.example.com/qa-login"
iframe и финальный запрос к API
CAPTCHA внутри iframe: чей URL использовать
Если виджет рендерится внутри iframe с другого домена — частый случай для встроенных форм логина или чекаута, — в pageurl всё равно идёт адрес родительской страницы, а не src самого iframe:
# If the CAPTCHA is on the MAIN page
pageurl = "https://example.com/register" # Main page URL
# If the CAPTCHA is in an IFRAME with a different domain
# Still use the main page URL, not the iframe src
pageurl = "https://example.com/register"
# NOT: "https://captcha-frame.example.com/challenge"
Итоговый запрос к in.php
Собираем всё вместе: сначала валидируем URL, и только потом отправляем задачу решателю reCAPTCHA/Turnstile:
import requests
# Validate URL first
pageurl = validate_pageurl("https://staging.example.com/qa-login")
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": "YOUR_API_KEY",
"method": "userrecaptcha",
"googlekey": "SITE_KEY",
"pageurl": pageurl,
"json": 1,
})
result = resp.json()
if result.get("status") == 1:
print(f"Task ID: {result['request']}")
else:
print(f"Error: {result.get('request')}")
Тестовые стенды за VPN и туннелями
Отдельный случай — QA-стенды, закрытые логином и доступные только через VPN или туннель вроде ngrok или Cloudflare Tunnel.
- Адрес в браузере тестировщика (
https://random-id.ngrok-free.app/qa-login) может отличаться от внутреннего адреса, который знает бэкенд. - Правило остаётся тем же: в
pageurlидёт адрес из адресной строки в момент решения CAPTCHA, даже если для внешнего мира это временный туннельный домен. - Если такой адрес меняется при каждом перезапуске стенда, получайте его динамически в начале прогона автотестов, а не храните захардкоженным в конфиге.
Если это упустить, задачи начнут падать с
ERROR_PAGEURLуже на второй день — как только туннель перезапустится с новым поддоменом.
Частые вопросы
Почему ERROR_PAGEURL появляется, даже если адрес в браузере выглядит правильно?
Чаще всего дело не в самом URL, а в моменте, когда он был захвачен: скрипт мог взять адрес до редиректа или до того, как SPA дорисовала маршрут. Сверяйте pageurl строго с тем, что показано на экране в момент решения CAPTCHA, включая протокол и www/не-www.
Как передавать pageurl для стенда за VPN или туннелем (ngrok, Cloudflare Tunnel)?
Так же, как для обычного адреса — берите то, что показывает адресная строка в момент решения CAPTCHA. Если туннельный домен генерируется заново при каждом запуске, получайте его динамически в начале прогона, а не храните в конфиге.
Нужно ли передавать в pageurl параметры запроса (query string)?
Только если они входят в видимый адрес страницы. Служебные параметры вроде utm_source, которые не влияют на контент страницы, можно и нужно убирать.
Как быть с pageurl, если CAPTCHA встроена в iframe с чужого домена?
Указывайте адрес родительской страницы, а не src iframe — решатель проверяет домен, на котором находится сама форма, а не домен виджета внутри неё.
Что читать дальше
Проверьте pageurl перед отправкой — решите CAPTCHA через CaptchaAI.