Scarnox · Blog

Penanganan error solver captcha: retry, backoff, dan apa yang tidak boleh di-retry

Solve captcha adalah panggilan jaringan ke sistem yang tidak Anda kendalikan, untuk tantangan yang bisa berubah tanpa peringatan. Menangani kegagalan dengan baik adalah perbedaan antara pipeline yang menurun dengan anggun dan pipeline yang membuang waktu dalam badai retry.

Dengan Scarnox, ekonominya membantu Anda: solve yang gagal tidak dikenai biaya, jadi retry hanya memakan waktu dan tidak ada yang lain. Namun, me-retry kegagalan yang salah tetap membuang waktu itu, dan retry yang terlalu agresif bisa menimbulkan masalah tersendiri. Panduan ini memberi Anda kebijakan retry yang bisa langsung ditempel ke job Python.

Scarnox men-solve PopularCaptcha saat ini. Penanganan error yang sama berlaku untuk solver captcha apa pun, jadi kebijakan di bawah ini tidak bergantung pada jenis captcha.

Selalu periksa errorId terlebih dahulu

Respons solve memiliki field errorId. 0 berarti berhasil dan token berada di solution.token. Nilai lain berarti solve gagal. Respons gagal juga membawa errorCode dan errorDescription. Catat keduanya: kodenya yang akan Anda cari nanti, dan deskripsinya yang akan Anda baca saat run bermasalah.

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

Jangan pernah berasumsi bahwa respons adalah sukses hanya karena status HTTP-nya 200. Periksa errorId setiap kali.

Kegagalan yang perlu di-retry

Beberapa kegagalan bersifat sementara. Retry kegagalan ini dengan exponential backoff dan jeda acak kecil:

Kegagalan ini layak di-retry karena percobaan berikutnya mungkin berhasil tanpa perubahan apa pun di pihak Anda.

Kegagalan yang harus diperbaiki sebelum retry

Kegagalan lain tidak akan hilang hanya dengan menunggu. Retry dengan input yang sama akan gagal dengan cara yang sama. Perbaiki dulu hal-hal berikut:

Jika sebuah kegagalan tidak masuk ke salah satu daftar di atas, perlakukan sebagai kegagalan yang perlu diperbaiki dulu. Baca teks error-nya, putuskan, lalu tambahkan kodenya ke daftar yang tepat di kode Anda. Jaga agar daftarnya tetap pendek dan ditinjau.

Kebijakan yang bisa ditempel

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:
        # Error jaringan atau timeout di sisi kami: aman untuk di-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 melaporkan kegagalan. Retry hanya kode yang sudah terbukti sementara.
    code = data.get("errorCode", "UNKNOWN")
    retryable = code in TRANSIENT_CODES
    raise SolveError(code, data.get("errorDescription", ""), retryable=retryable)

# Tambahkan kode error API di sini hanya setelah log Anda menunjukkan bahwa kode itu sementara.
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")

Kebijakan ini tidak pernah me-retry kegagalan yang perlu diperbaiki dulu, dan berhenti setelah jumlah percobaan tertentu. Set TRANSIENT_CODES sengaja dibiarkan kosong: tambahkan kode hanya setelah log Anda menunjukkan bahwa kode itu sementara.

Mengapa jitter penting

Jika lima puluh worker gagal pada saat yang sama dan semuanya tidur tepat dua detik, mereka akan retry bersama dan gagal bersama lagi. Jeda acak beberapa per detik menyebarkan mereka. Ini adalah saran yang sama yang biasanya diberikan penyedia API besar untuk klien mereka sendiri.

Timeout

Atur timeout HTTP Anda jauh di atas waktu solve yang umum, sekitar 185 detik. Request memblokir sampai token siap, jadi timeout client yang pendek bisa mengubah keberhasilan yang lambat menjadi kegagalan yang lalu Anda retry tanpa hasil. Timeout client yang lebih panjang dari waktu solve adalah pengaturan yang aman.

Token tidak bisa di-cache

Token kedaluwarsa dalam sekitar dua menit. Jangan menyimpan token di antara run atau memakai ulang satu token untuk pengiriman kedua. Jika pengiriman gagal setelah token dipakai, solve lagi alih-alih mengirim ulang token lama. Panduan masa berlaku token membahas waktunya lebih detail.

Konkurensi dan backoff antar worker

Jika banyak worker memakai satu key yang sama, backoff di satu worker tidak memperlambat yang lain. Tambahkan batas bersama untuk solve yang berjalan bersamaan, seperti semaphore atau antrean, agar lonjakan retry tidak berubah menjadi lonjakan request. Batas konkurensi sederhana sudah mencegah sebagian besar kegagalan rate limit sebelum mulai.

Logging yang membantu

Untuk setiap percobaan yang gagal, catat errorCode, nomor percobaan, sitekey, host halaman, dan apakah kegagalan itu di-retry. Jangan mencatat API key atau password proxy. Dashboard yang menghitung kode per jam memberi tahu Anda dengan cepat apakah masalahnya ada di kode Anda, di proxy, atau di layanan.

Langkah selanjutnya

Uji penanganan Anda dengan uji coba gratis: buat akun dan mulai dengan kredit uji coba gratis, tanpa kartu.