Быстрое решение капчи: как сократить задержку в конвейере
Быстрый решатель капчи — это тот, который вы вызываете в правильный момент, параллельно и без лишних запросов. Вызов Scarnox возвращается, когда токен готов, поэтому его длительность зависит от типа капчи, сайта и текущей нагрузки. Этот гайд не обещает фиксированного времени. Он показывает, что контролирует ваш код, как это измерить и как запускать много решений сразу, чтобы время, которое вы тратите, шло с пользой.
Что определяет время решения
Важны три вещи, и только одна из них в ваших руках:
- Сама капча. Одни типы проверок занимают больше времени, чем другие. Это свойство капчи конкретного сайта, а не вашего кода.
- Нагрузка на решатель. В пиковые периоды одиночный вызов может быть медленнее. Логика повторов должна с этим справляться, а не прерывать вызов раньше времени.
- Ваш собственный конвейер. Решение слишком рано, последовательная обработка всех запросов и ожидание посторонней работы добавляют время, которое вы можете убрать.
Сосредоточьтесь на третьем пункте. Его вы можете измерить и изменить.
Сначала измерьте, потом оптимизируйте
Оберните вызов таймером и записывайте результат. Нужно знать и время решения, и время, которое ваш код тратит вокруг него:
import time
import requests
API_KEY = "scarnox_YOURAPIKEY"
URL = "https://api.scarnox.com/api/tasks/create"
def timed_solve(sitekey: str, page_url: str) -> dict:
started = time.perf_counter()
r = requests.post(
URL,
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"captcha_type": "PopularCaptchaTaskProxyless",
"site_key": sitekey,
"site_url": page_url,
},
timeout=185,
)
elapsed = time.perf_counter() - started
data = r.json()
if data.get("errorId") != 0:
return {"ok": False, "error": data.get("errorCode"), "seconds": elapsed}
return {"ok": True, "token": data["solution"]["token"], "seconds": elapsed}
result = timed_solve("10000000-ffff-ffff-ffff-000000000001", "https://example.com/signup")
print(result["ok"], round(result["seconds"], 2), "s")
Запускайте это на страницах, которые важны для вас, в те часы, когда вы запускаете задачи. Сохраняйте цифры. Они покажут, действительно ли изменение помогло.
Запускайте решения параллельно
Решения независимы друг от друга. Если у вас десять sitekey, ожидание каждого по очереди тратит время, которое можно потратить на остальные. Для большинства скриптов достаточно пула потоков, потому что каждое решение — это один HTTP-запрос, который большую часть времени просто ждёт:
from concurrent.futures import ThreadPoolExecutor
jobs = [
("10000000-ffff-ffff-ffff-000000000001", "https://example.com/signup"),
("10000000-ffff-ffff-ffff-000000000001", "https://example.com/register"),
("10000000-ffff-ffff-ffff-000000000001", "https://example.com/contact"),
]
def run(job):
sitekey, page_url = job
return timed_solve(sitekey, page_url)
# Начинайте с малого. Увеличивайте число воркеров, только пока ошибок мало.
with ThreadPoolExecutor(max_workers=5) as pool:
results = list(pool.map(run, jobs))
for r in results:
status = "ok" if r["ok"] else r["error"]
print(f"{r['seconds']:.2f} s {status}")
Если при увеличении числа воркеров появляются ошибки, похожие на проблемы с лимитом или ёмкостью, уменьшите его. Параллельность помогает только тогда, когда сервер за ней успевает.
Не решайте заранее
Самый быстрый код — тот, который не ждёт токена, которым ещё не воспользуется. Запрашивать токены заранее и хранить их — частая ошибка. Токены истекают примерно через две минуты после выдачи, поэтому сохранённый токен часто устаревает к моменту, когда он нужен. Решайте, когда форма вот-вот будет отправлена. Подробные причины описаны в истечении токена капчи.
Держите остальной путь коротким
- Держите одну HTTP-сессию на сайт, чтобы не переподключаться на каждый запрос.
- Не выполняйте медленный парсинг или работу с базой данных между решением и отправкой.
- Используйте разумный таймаут для своих запросов. Вызов решения занимает около 185 секунд, потому что блокируется до готовности токена. Остальные ваши вызовы должны быть намного короче.
- Передавайте прокси, только если задача в нём нуждается. Типы задач с прокси принимают поле
proxyв форматеhttp://user:pass@host:port, но само по себе добавление прокси не ускоряет работу.
Переиспользуйте то, что не истекает
Часть работы можно безопасно разделять между решениями. Уберите это из горячего пути:
- Sitekey. Считайте sitekey один раз на шаблон страницы и сохраняйте. Он не истекает, как токен. Пересчитывайте его, только когда форма начинает падать.
- HTTP-соединения. Сессия с keep-alive избавляет от нового TLS-рукопожатия при каждом вызове. Создавайте одну сессию на поток или воркер, а не на запрос.
- Разобранная конфигурация. Загружайте список целей, список прокси и настройки один раз при старте, а не читайте файлы внутри функции решения.
Эти изменения стоят мало и убирают накладные расходы, которые есть в каждом вызове.
Измеряйте хвост, а не среднее
Среднее время решения скрывает медленные вызовы, которые задерживают пакет. Записывайте время каждого решения, затем смотрите медиану и 95-й перцентиль. Конвейер, который заканчивается вместе с самой медленной задачей, ограничен хвостом, поэтому небольшое изменение в том, как вы повторяете медленные вызовы, может повлиять сильнее, чем любое изменение медианы:
import statistics
def summarise(seconds: list[float]) -> str:
ordered = sorted(seconds)
p95 = ordered[int(0.95 * (len(ordered) - 1))]
return f"median {statistics.median(ordered):.2f} s, p95 {p95:.2f} s, n={len(ordered)}"
print(summarise([r["seconds"] for r in results]))
Сравнивайте сводку до и после каждого изменения. Если p95 почти не сдвинулся, время тратится где-то ещё в вашем коде.
Проверяйте результат, а не только скорость
Быстрый сбой — всё равно сбой. Считайте решения, которые вернули ошибку, и воспринимайте растущую долю ошибок как сигнал замедлиться. Неудачные решения не списываются, но время, потраченное на них, всё равно время, которое потерял ваш конвейер. Полный паттерн повторов — в обработке ошибок решателя капчи.