Scarnox · Blog

Captcha solver error handling - retries, backoff और कभी retry न करने वाली errors

Captcha solve एक network call है, एक ऐसी system के लिए जिसे आप नियंत्रित नहीं करते, और एक ऐसे challenge के खिलाफ़ जो बिना चेतावनी के बदल सकता है। Failures को अच्छी तरह संभालना ही उस pipeline और उस pipeline के बीच का फ़र्क है जो सुचारू रूप से कम होती रहती है, और उस pipeline के बीच जो retry storm में समय बर्बाद करती है।

Scarnox में economics आपकी मदद करता है: विफल solves मुफ़्त हैं, इसलिए retry में केवल समय लगता है, और कुछ नहीं। पर गलत failure को retry करने से भी वह समय बर्बाद होता है, और ज़रूरत से ज़्यादा retry करने से अपनी अलग समस्याएँ हो सकती हैं। यह guide एक retry policy देती है जिसे आप Python job में paste कर सकते हैं।

Scarnox आज PopularCaptcha solve करता है। यही error handling किसी भी captcha solver पर लागू होता है, इसलिए नीचे की policy captcha type पर निर्भर नहीं करती।

हमेशा पहले errorId जाँचें

Solve response में एक errorId field होता है। 0 का मतलब सफलता है और token solution.token के नीचे होता है। कोई और मान बताता है कि solve विफल हुआ। विफल response में errorCode और errorDescription भी होते हैं। दोनों लॉग करें: code वह है जिसे आप बाद में खोजेंगे, और description वह है जो तब पढ़ेंगे जब run गड़बड़ हो।

{"errorId": 1, "errorCode": "...", "errorDescription": "..."}

यह कभी न मानें कि response सफल है क्योंकि HTTP status 200 है। हर बार errorId जाँचें।

जिन failures को retry करना है

कुछ failures अस्थायी होती हैं। इन्हें exponential backoff और एक छोटे random delay के साथ retry करें:

इन failures की retry सार्थक है, क्योंकि अगली कोशिश बिना आपकी तरफ़ से किसी बदलाव के सफल हो सकती है।

जिन failures को retry से पहले ठीक करना है

कुछ failures इंतज़ार से नहीं जाएँगी। वही input के साथ retry उसी तरह फ़ेल होगा। इन्हें पहले ठीक करें:

जब कोई failure दोनों सूचियों में न हो, तो उसे पहले ठीक करने वाली failure मानें। Error text देखें, फ़ैसला करें, और code को सही सूची में जोड़ें। ये सूचियाँ छोटी और समीक्षित रखें।

एक policy जिसे आप paste कर सकें

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:
        # हमारी तरफ़ network error या timeout: retry सुरक्षित है।
        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 ने failure बताया। केवल वही codes retry करें जो आपने अस्थायी पाए हों।
    code = data.get("errorCode", "UNKNOWN")
    retryable = code in TRANSIENT_CODES
    raise SolveError(code, data.get("errorDescription", ""), retryable=retryable)

# API error codes यहाँ तभी जोड़ें जब आपके logs में वे अस्थायी दिखें।
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 (थोड़ा random delay)
            print(f"attempt {attempt + 1} failed ({err.code}), retrying in {delay:.1f}s")
            time.sleep(delay)
    raise RuntimeError("unreachable")

Policy कभी ऐसी failure को retry नहीं करती जिसे पहले ठीक करना है, और वह एक तय संख्या के प्रयासों के बाद रुक जाती है। TRANSIENT_CODES set जानबूझकर खाली शुरू होता है: कोई code तभी जोड़ें जब आपके logs दिखाएँ कि वह अस्थायी है।

Jitter क्यों मायने रखता है

अगर पचास workers एक ही पल में फ़ेल हों और सब ठीक दो सेकंड सोएँ, तो वे सब एक साथ retry करेंगे और फिर एक साथ फ़ेल होंगे। आधा सेकंड का random हिस्सा उन्हें फैला देता है। बड़े API providers अपने clients को भी यही सलाह देते हैं।

Timeouts

HTTP timeout को आम solve समय से काफ़ी ऊपर रखें, लगभग 185 सेकंड। Request तब तक रुकती है जब तक token तैयार न हो, इसलिए छोटा client timeout एक धीमी सफलता को ऐसी विफलता में बदल सकता है जिसे आप बेकार में retry करते हैं। Solve से लंबा client timeout ही सुरक्षित सेटिंग है।

Tokens cache नहीं होते

Token लगभग दो मिनट में expire होता है। Tokens को runs के बीच cache न करें, और एक ही token को दूसरे submission के लिए दोबारा इस्तेमाल न करें। अगर token इस्तेमाल होने के बाद submit विफल हो, तो पुराना token दोबारा submit करने के बजाय फिर से solve करें। token expiry guide समय का विवरण देती है।

कई workers के बीच concurrency और backoff

अगर कई workers एक key साझा करते हैं, तो एक worker का backoff बाकियों को धीमा नहीं करता। समवर्ती solves पर एक साझा सीमा जोड़ें, जैसे semaphore या queue, ताकि retries का burst requests के burst में न बदले। एक सरल concurrency cap ही ज़्यादातर rate-limit failures को शुरू होने से पहले रोक देता है।

ऐसा logging जो मदद करे

हर विफल प्रयास के लिए errorCode, प्रयास संख्या, sitekey, page host और यह लॉग करें कि failure retry हुई या नहीं। API key या proxy password कभी लॉग न करें। एक dashboard जो हर घंटे codes गिनता है, आपको जल्दी बताता है कि समस्या आपके code में है, proxy में है या service में।

अगले कदम

अपनी handling को मुफ़्त trial पर टेस्ट करें: खाता बनाएँ और मुफ़्त ट्रायल क्रेडिट से शुरू करें, कार्ड की ज़रूरत नहीं।