Обробка помилок солвера капчі: повтори, backoff і що ніколи не повторювати
Розв'язання капчі — це мережевий виклик до системи, якою ви не керуєте, для виклику, який може змінитися без попередження. Те, як ви обробляєте збої, визначає, чи пайплайн деградує плавно, чи потрапляє у шторм повторів.
У Scarnox економіка працює на вас: невдалі розв'язання безкоштовні, тож повтор коштує лише часу і більше нічого. Але повторювання не того збою все одно марнує цей час, а надто наполегливі повтори можуть створити власні проблеми. Цей посібник дає політику повторів, яку можна вставити в завдання на Python.
Scarnox розв'язує PopularCaptcha вже зараз. Та сама обробка помилок стосується будь-якого солвера капчі, тож політика нижче не залежить від типу капчі.
Завжди спочатку перевіряйте errorId
У відповіді на розв'язання є поле errorId. Значення 0 означає успіх, а токен лежить у solution.token. Будь-яке інше значення означає, що розв'язання не вдалося. Невдала відповідь також містить errorCode і errorDescription. Записуйте обидва: код ви шукатимете пізніше, а опис читатимете, коли запуск пішов не так.
{"errorId": 1, "errorCode": "...", "errorDescription": "..."}
Ніколи не вважайте відповідь успішною лише через HTTP-статус 200. Перевіряйте errorId щоразу.
Збої, які варто повторити
Деякі збої тимчасові. Повторюйте їх з експоненційним backoff і невеликою випадковою затримкою:
- Мережеві помилки: скидання з'єднання, збої DNS, таймаути читання з вашого боку.
- Серверні помилки: відповіді HTTP 5xx, що означають проблему на боці сервісу.
- Обмеження частоти: HTTP 429, що означає, що ви надсилаєте запити занадто швидко. Сповільніться.
- Таймаути: розв'язання тривало довше, ніж чекав ваш клієнт. Повторіть із довшим таймаутом, якщо ви його ще не встановили.
Ці збої варто повторити, бо наступна спроба може пройти успішно без жодних змін з вашого боку.
Збої, які спершу треба виправити
Інші збої не зникнуть від очікування. Повтор із тим самим вхідним значенням провалиться так само. Спершу виправте їх:
- Недійсний тип завдання. Перевірте, що
captcha_typeнаписано точно так, як у документації. - Неправильний або відсутній sitekey. Прочитайте sitekey зі сторінки знову. Sitekey зі старого деплою — поширена причина.
- Неправильний URL сторінки.
site_urlмає бути сторінкою, яка показує віджет. - Проблеми з проксі. Мертвий, неправильно введений або заблокований проксі провалить кожну спробу. Спершу перевірте проксі окремо. Дивіться проксі чи без проксі.
- Проблеми з обліковим записом. Недійсний ключ або обліковий запис без потрібного балансу провалюватимуть кожну спробу, доки хтось не виправить обліковий запис. Сповістіть людину; не повторюйте в циклі.
Коли збій не входить до жодного зі списків, вважайте його таким, що спершу потребує виправлення. Перегляньте текст помилки, вирішіть і додайте код до правильного списку у вашому коді. Тримайте списки короткими і переглянутими.
Політика, яку можна вставити
import os
import random
import time
import requests
API_URL = "https://api.scarnox.com/api/tasks/create"
HEADERS = {"Authorization": f"Bearer {os.environ['SCARNOX_KEY']}"}
MAX_ATTEMPTS = 4
class SolveError(Exception):
def __init__(self, code: str, description: str, retryable: bool):
super().__init__(f"{code}: {description}")
self.code = code
self.retryable = retryable
def solve_once(payload: dict) -> str:
try:
r = requests.post(API_URL, headers=HEADERS, json=payload, timeout=185)
except requests.RequestException as exc:
# Мережева помилка або таймаут з нашого боку: повторювати безпечно.
raise SolveError("NETWORK", str(exc), retryable=True)
if r.status_code == 429 or r.status_code >= 500:
raise SolveError(f"HTTP_{r.status_code}", r.text[:200], retryable=True)
try:
data = r.json()
except ValueError:
raise SolveError(f"HTTP_{r.status_code}", r.text[:200], retryable=False)
if data.get("errorId") == 0:
return data["solution"]["token"]
# API повідомив про збій. Повторюємо лише коди, які ви бачили як тимчасові.
code = data.get("errorCode", "UNKNOWN")
retryable = code in TRANSIENT_CODES
raise SolveError(code, data.get("errorDescription", ""), retryable=retryable)
# Додавайте коди помилок API сюди, лише після того, як побачили в журналах, що вони тимчасові.
TRANSIENT_CODES: set[str] = set()
def solve(payload: dict) -> str:
for attempt in range(MAX_ATTEMPTS):
try:
return solve_once(payload)
except SolveError as err:
last_attempt = attempt == MAX_ATTEMPTS - 1
if not err.retryable or last_attempt:
raise
delay = min(30, 2 ** attempt) + random.random() # backoff + jitter
print(f"attempt {attempt + 1} failed ({err.code}), retrying in {delay:.1f}s")
time.sleep(delay)
raise RuntimeError("unreachable")
Політика ніколи не повторює збій, що потребує виправлення, і зупиняється після фіксованої кількості спроб. Набір TRANSIENT_CODES навмисно порожній на старті: додавайте код лише тоді, коли журнали показують, що він тимчасовий.
Чому важливий jitter
Якщо п'ятдесят воркерів провалюються в один момент і всі спимо рівно дві секунди, вони всі повторять спробу разом і знову провалються разом. Випадкова частка секунди їх розводить. Це та сама порада, яку більшість великих провайдерів API дає своїм клієнтам.
Таймаути
Встановіть таймаут HTTP значно вище типового часу розв'язання, близько 185 секунд. Запит блокується, доки токен не буде готовий, тож короткий клієнтський таймаут може перетворити повільний успіх на збій, який ви потім безглуздо повторюєте. Клієнтський таймаут, довший за розв'язання, — безпечне налаштування.
Токени не кешуються
Токен спливає приблизно за дві хвилини. Не кешуйте токени між запусками і не використовуйте один токен для другої відправки. Якщо відправка провалилася вже після використання токена, розв'яжіть капчу знову, а не відправляйте старий токен повторно. Більше про терміни дії дивіться в посібнику про термін дії токена.
Паралельність і backoff між воркерами
Якщо багато воркерів ділять один ключ, backoff в одному воркері не сповільнює інші. Додайте спільний ліміт одночасних розв'язань, наприклад семафор або чергу, щоб сплеск повторів не перетворився на сплеск запитів. Проста обмеженість паралельності розв'язує більшість проблем з лімітами частоти ще до їх появи.
Логування, яке допомагає
Для кожної невдалої спроби записуйте errorCode, номер спроби, sitekey, хост сторінки і те, чи збій повторено. Не записуйте ключ API або пароль проксі. Панель, що рахує коди за годину, швидко показує, чи проблема в вашому коді, проксі або в сервісі.
Наступні кроки
- Як розв'язати PopularCaptcha на Python
- Проксі чи без проксі: який тип завдання вам потрібен?
- Зелений CI, коли ваш застосунок має капчу
- Безкоштовна пробна версія солвера капчі
Перевірте свою обробку на безкоштовній пробній версії: створіть обліковий запис і почніть з безкоштовного пробного кредиту, картка не потрібна.