Foutafhandeling van de captcha-solver: opnieuw proberen, backoff en wat je nooit opnieuw moet proberen
Een captchaoplossing is een netwerkaanroep naar een systeem dat je niet beheert, tegen een uitdaging die zonder waarschuwing kan veranderen. Goed omgaan met fouten maakt het verschil tussen een pipeline die gracieus achteruitgaat en een pipeline die tijd verspilt in een herhaalstorm.
Bij Scarnox helpt de economie je: mislukte oplossingen zijn gratis, dus een nieuwe poging kost alleen tijd en verder niets. Maar een verkeerde fout opnieuw proberen verspilt die tijd nog steeds, en te hard opnieuw proberen kan eigen problemen veroorzaken. Deze gids geeft je een beleid voor opnieuw proberen dat je in een Python-job kunt plakken.
Scarnox lost vandaag PopularCaptcha op. Dezelfde foutafhandeling geldt voor elke captcha-solver, dus het beleid hieronder hangt niet af van het captchatype.
Controleer altijd eerst errorId
Een oplossingsrespons heeft een veld errorId. 0 betekent succes en het token staat onder solution.token. Elke andere waarde betekent dat de oplossing is mislukt. Een mislukte respons bevat ook een errorCode en een errorDescription. Log beide: de code is wat je later zoekt, en de beschrijving is wat je leest wanneer een run misgaat.
{"errorId": 1, "errorCode": "...", "errorDescription": "..."}
Ga nooit uit van succes omdat de HTTP-status 200 is. Controleer errorId elke keer.
Fouten om opnieuw te proberen
Sommige fouten zijn tijdelijk. Probeer deze opnieuw met exponentiële backoff en een kleine willekeurige vertraging:
- Netwerkfouten: verbindingsresets, DNS-haperingen, leestime-outs aan jouw kant.
- Serverfouten: HTTP 5xx-antwoorden, die een probleem aan de servicekant betekenen.
- Snelheidslimiet: HTTP 429, wat betekent dat je te snel verzoeken stuurt. Vertraag.
- Time-outs: de oplossing duurde langer dan je client wachtte. Probeer opnieuw met een langere time-out als je die nog niet hebt ingesteld.
Deze fouten zijn het opnieuw proberen waard omdat de volgende poging kan slagen zonder dat je iets verandert.
Fouten om eerst te repareren
Andere fouten verdwijnen niet door te wachten. Een nieuwe poging met dezelfde invoer faalt op dezelfde manier. Los deze eerst op:
- Ongeldig tasktype. Controleer of
captcha_typeprecies is gespeld zoals in de documentatie. - Verkeerde of ontbrekende sitekey. Lees de sitekey opnieuw van de pagina. Een sitekey van een oude deploy is een veelvoorkomende oorzaak.
- Verkeerde paginaURL.
site_urlmoet de pagina zijn die de widget toont. - Proxyproblemen. Een dode, verkeerd getypte of geblokkeerde proxy laat elke poging mislukken. Test de proxy eerst apart. Zie proxy versus zonder proxy.
- Accountproblemen. Een ongeldige sleutel of een account zonder het benodigde saldo laat elke poging mislukken totdat iemand het account repareert. Waarschuw een persoon; probeer niet in een lus opnieuw.
Wanneer een fout op geen van beide lijsten staat, behandel je hem als een fout die eerst gerepareerd moet worden. Bekijk de foutmelding, beslis, en voeg de code toe aan de juiste lijst in je code. Houd de lijsten kort en laat ze reviewen.
Een beleid om te plakken
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:
# Netwerkfout of time-out aan onze kant: veilig om opnieuw te proberen.
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"]
# De API meldde een fout. Probeer alleen codes opnieuw die je in je logs als tijdelijk hebt gezien.
code = data.get("errorCode", "UNKNOWN")
retryable = code in TRANSIENT_CODES
raise SolveError(code, data.get("errorDescription", ""), retryable=retryable)
# Voeg API-foutcodes hier pas toe nadat je in je logs hebt gezien dat ze tijdelijk zijn.
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")
Het beleid probeert nooit opnieuw een fout die eerst gerepareerd moet worden, en stopt na een vast aantal pogingen. De set TRANSIENT_CODES begint bewust leeg: voeg een code pas toe nadat je logs laten zien dat hij tijdelijk is.
Waarom jitter belangrijk is
Als vijftig workers tegelijk falen en allemaal exact twee seconden slapen, proberen ze gezamenlijk opnieuw en falen ze gezamenlijk opnieuw. Een willekeurig deel van een seconde spreidt ze uit. Dit is hetzelfde advies dat de meeste grote API-aanbieders geven aan hun eigen clients.
Time-outs
Stel je HTTP-time-out ruim in boven de typische oplostijd, ongeveer 185 seconden. Het verzoek blokkeert totdat het token klaar is, dus een korte client-time-out kan een trage maar geslaagde oplossing veranderen in een fout die je vervolgens voor niets opnieuw probeert. Een client-time-out die langer is dan de oplossing is de veilige instelling.
Tokens kunnen niet worden gecachet
Een token verloopt binnen ongeveer twee minuten. Cache tokens niet tussen runs en hergebruik er geen voor een tweede verzending. Als een verzending mislukt nadat het token is gebruikt, los dan opnieuw op in plaats van het oude token opnieuw in te dienen. De gids over tokenverval behandelt de timing in meer detail.
Gelijktijdigheid en backoff over workers heen
Als veel workers één sleutel delen, vertraagt de backoff van één worker de anderen niet. Voeg een gedeelde limiet toe op gelijktijdige oplossingen, zoals een semafoor of een wachtrij, zodat een uitbarsting van nieuwe pogingen niet verandert in een uitbarsting van verzoeken. Een eenvoudige gelijktijdigheidslimiet voorkomt de meeste snelheidslimietfouten voordat ze ontstaan.
Logging die helpt
Log voor elke mislukte poging de errorCode, het pogingnummer, de sitekey, de host van de pagina en of de fout opnieuw is geprobeerd. Log de API-sleutel of een proxywachtwoord niet. Een dashboard dat codes per uur telt, laat snel zien of een probleem in je code, de proxy of de dienst zit.
Volgende stappen
- Een PopularCaptcha oplossen in Python
- Proxy of zonder proxy: welk tasktype heb je nodig?
- Captcha in CI groen houden
- Gratis proefversie van de captcha-solver
Test je afhandeling tegen een gratis proefversie: maak een account aan en begin met gratis proefcredit, zonder creditcard.