Obsługa błędów solvera captcha: ponawianie, backoff i czego nigdy nie ponawiać
Rozwiązanie captcha to wywołanie sieciowe do systemu, którego nie kontrolujesz, dla wyzwania, które może się zmienić bez ostrzeżenia. Dobra obsługa błędów decyduje o tym, czy potok działa łagodnie, czy marnuje czas w lawinie ponowień.
Przy Scarnox ekonomia Ci pomaga: nieudane rozwiązania są bezpłatne, więc ponowienie kosztuje tylko czas i nic więcej. Ale ponawianie niewłaściwego błędu nadal marnuje ten czas, a zbyt agresywne ponawianie może samo spowodować problemy. Ten przewodnik daje politykę ponawiania, którą możesz wkleić do zadania w Pythonie.
Scarnox rozwiązuje dziś PopularCaptcha. Ta sama obsługa błędów dotyczy każdego solvera captcha, więc poniższa polityka nie zależy od typu captcha.
Zawsze najpierw sprawdź errorId
Odpowiedź na rozwiązanie zawiera pole errorId. 0 oznacza sukces, a token znajduje się w solution.token. Każda inna wartość oznacza nieudane rozwiązanie. Nieudana odpowiedź zawiera też errorCode i errorDescription. Zapisuj oba: kod będziesz później wyszukiwać, a opis przeczytasz, gdy uruchomienie pójdzie źle.
{"errorId": 1, "errorCode": "...", "errorDescription": "..."}
Nigdy nie zakładaj, że odpowiedź to sukces, bo status HTTP to 200. Sprawdzaj errorId za każdym razem.
Błędy do ponowienia
Niektóre błędy są tymczasowe. Ponawiaj je z wykładniczym backoffem i małym losowym opóźnieniem:
- Błędy sieci: resetowanie połączeń, problemy z DNS, timeouty odczytu po Twojej stronie.
- Błędy serwera: odpowiedzi HTTP 5xx, które oznaczają problem po stronie usługi.
- Limity tempa: HTTP 429, co oznacza, że wysyłasz żądania zbyt szybko. Zwolnij.
- Timeouty: rozwiązanie trwało dłużej, niż Twój klient czekał. Ponów z dłuższym timeoutem, jeśli jeszcze go nie ustawiłeś.
Te błędy warto ponowić, bo kolejna próba może się udać bez żadnej zmiany po Twojej stronie.
Błędy do naprawienia przed ponowieniem
Inne błędy nie znikną z czasem. Ponowienie z tym samym wejściem zakończy się tym samym błędem. Najpierw je napraw:
- Nieprawidłowy typ zadania. Sprawdź, czy
captcha_typejest zapisany dokładnie tak, jak w dokumentacji. - Błędny lub brakujący sitekey. Odczytaj sitekey ze strony jeszcze raz. Sitekey ze starego wdrożenia to częsta przyczyna.
- Błędny adres strony.
site_urlpowinien być stroną, która pokazuje widżet. - Problemy z proxy. Martwe, źle wpisane lub zablokowane proxy zawiedzie przy każdej próbie. Najpierw przetestuj proxy osobno. Zobacz proxy kontra bez proxy.
- Problemy z kontem. Nieprawidłowy klucz lub konto bez wymaganego salda będzie zawodzić przy każdej próbie, dopóki ktoś nie naprawi konta. Powiadom osobę; nie ponawiaj w pętli.
Gdy błąd nie znajduje się na żadnej z tych list, traktuj go jako błąd do naprawienia najpierw. Przeczytaj tekst błędu, zdecyduj i dodaj kod do właściwej listy w swoim kodzie. Trzymaj listy krótkie i sprawdzone przez kogoś.
Polityka, którą możesz wkleić
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:
# Błąd sieci lub timeout po naszej stronie: bezpieczne do ponowienia.
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 zgłosiło błąd. Ponawiaj tylko kody, które zaobserwowano jako przejściowe.
code = data.get("errorCode", "UNKNOWN")
retryable = code in TRANSIENT_CODES
raise SolveError(code, data.get("errorDescription", ""), retryable=retryable)
# Dodawaj tu kody błędów API dopiero po tym, jak zobaczysz w logach, że są przejściowe.
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 + losowe opóźnienie
print(f"attempt {attempt + 1} failed ({err.code}), retrying in {delay:.1f}s")
time.sleep(delay)
raise RuntimeError("unreachable")
Polityka nigdy nie ponawia błędu do naprawienia i zatrzymuje się po stałej liczbie prób. Zbiór TRANSIENT_CODES na początku jest celowo pusty: dodaj kod dopiero wtedy, gdy logi pokażą, że jest tymczasowy.
Dlaczego losowe opóźnienie ma znaczenie
Jeśli pięćdziesiąt workerów zawiedzie w tym samym momencie i wszystkie zaśpią dokładnie na dwie sekundy, ponowią próbę razem i razem znowu zawiodą. Losowa ułamek sekundy je rozprasza. To ta sama rada, którą większość dużych dostawców API daje swoim klientom.
Timeouty
Ustaw timeout HTTP wyraźnie powyżej typowego czasu rozwiązania, czyli około 185 sekund. Żądanie blokuje się, aż token będzie gotowy, więc krótki timeout klienta może zamienić powolny sukces w błąd, który potem ponawiasz na darmo. Bezpieczne ustawienie to timeout dłuższy niż samo rozwiązanie.
Tokeny nie nadają się do cache'owania
Token wygasa w ciągu około dwóch minut. Nie buforuj tokenów między uruchomieniami i nie używaj jednego ponownie do drugiego wysłania. Jeśli wysyłanie nie powiedzie się po tym, jak token został użyty, rozwiąż captcha ponownie zamiast wysyłać stary token. Przewodnik o wygasaniu tokenu omawia czas szczegółowo.
Równoległość i backoff między workerami
Jeśli wielu workerów współdzieli jeden klucz, backoff w jednym workerze nie spowalnia pozostałych. Dodaj wspólny limit równoczesnych rozwiązań, na przykład semafor lub kolejkę, aby seria ponowień nie zamieniła się w serię żądań. Prosty limit równoległości zapobiega większości błędów limitu tempa, zanim w ogóle się pojawią.
Logowanie, które pomaga
Dla każdej nieudanej próby zapisuj errorCode, numer próby, sitekey, host strony i to, czy błąd został ponowiony. Nie loguj klucza API ani hasła do proxy. Panel liczący kody na godzinę szybko pokaże, czy problem leży w Twoim kodzie, w proxy, czy w usłudze.
Następne kroki
- Jak rozwiązać PopularCaptcha w Pythonie
- Proxy czy bez proxy: który typ zadania jest Ci potrzebny?
- Utrzymanie zielonego CI, gdy aplikacja ma captcha
- Darmowy okres próbny solvera captcha
Przetestuj swoją obsługę błędów na darmowym okresie próbnym: załóż konto i zacznij od darmowego kredytu próbnego, bez karty.