API Tutorials

Klien Python CaptchaAI dengan Validasi Pydantic

Meneruskan sitekey kosong ke API CaptchaAI membuang waktu — Anda mendapat ERROR_WRONG_CAPTCHA_ID setelah menunggu respons. Pydantic menangkap error ini sebelum HTTP call terjadi: dapatkan ValidationError yang jelas alih-alih kode error API yang samar.

Mengapa Pydantic untuk Klien API CAPTCHA

Tanpa Pydantic Dengan Pydantic
Sitekey kosong → error API setelah 5 detik ValidationError langsung
Parse respons via dict["key"] → KeyError Model bertipe dengan default dan validasi
Tidak ada autocomplete IDE untuk parameter Type hint lengkap di semua field

Model

# models.py
from pydantic import BaseModel, Field, field_validator, HttpUrl
from enum import Enum
from typing import Optional

class CaptchaMethod(str, Enum):
    RECAPTCHA_V2 = "userrecaptcha"
    RECAPTCHA_V3 = "userrecaptcha"  # Differentiated by version field
    TURNSTILE = "turnstile"
    HCAPTCHA = "hcaptcha"
    IMAGE = "base64"
    GEETEST = "geetest"

class RecaptchaV2Request(BaseModel):
    """Parameters for solving reCAPTCHA v2."""
    sitekey: str = Field(min_length=20, max_length=100, description="Site's reCAPTCHA sitekey")
    pageurl: HttpUrl = Field(description="URL where CAPTCHA appears")
    invisible: bool = False
    cookies: Optional[str] = None

    @field_validator("sitekey")
    @classmethod
    def validate_sitekey(cls, v: str) -> str:
        if v.strip() != v:
            raise ValueError("Sitekey must not have leading/trailing whitespace")
        return v

    def to_params(self) -> dict:
        params = {
            "method": "userrecaptcha",
            "googlekey": self.sitekey,
            "pageurl": str(self.pageurl),
        }
        if self.invisible:
            params["invisible"] = "1"
        if self.cookies:
            params["cookies"] = self.cookies
        return params

class RecaptchaV3Request(BaseModel):
    """Parameters for solving reCAPTCHA v3."""
    sitekey: str = Field(min_length=20, max_length=100)
    pageurl: HttpUrl
    action: str = Field(default="verify", min_length=1, max_length=100)

    def to_params(self) -> dict:
        return {
            "method": "userrecaptcha",
            "version": "v3",
            "googlekey": self.sitekey,
            "pageurl": str(self.pageurl),
            "action": self.action,
        }

class TurnstileRequest(BaseModel):
    """Parameters for solving Cloudflare Turnstile."""
    sitekey: str = Field(min_length=10, max_length=100)
    pageurl: HttpUrl
    action: Optional[str] = None
    cdata: Optional[str] = None

    def to_params(self) -> dict:
        params = {
            "method": "turnstile",
            "sitekey": self.sitekey,
            "pageurl": str(self.pageurl),
        }
        if self.action:
            params["action"] = self.action
        if self.cdata:
            params["data"] = self.cdata
        return params

class ImageRequest(BaseModel):
    """Parameters for solving image/text CAPTCHA."""
    base64_image: str = Field(min_length=100, description="Base64-encoded image")
    case_sensitive: bool = False
    min_length: Optional[int] = Field(default=None, ge=1, le=50)
    max_length: Optional[int] = Field(default=None, ge=1, le=50)

    @field_validator("base64_image")
    @classmethod
    def validate_base64(cls, v: str) -> str:
        # Strip data URI prefix if present
        if v.startswith("data:"):
            parts = v.split(",", 1)
            if len(parts) == 2:
                return parts[1]
        return v

    def to_params(self) -> dict:
        params = {
            "method": "base64",
            "body": self.base64_image,
        }
        if self.case_sensitive:
            params["regsense"] = "1"
        if self.min_length is not None:
            params["min_len"] = str(self.min_length)
        if self.max_length is not None:
            params["max_len"] = str(self.max_length)
        return params

class SubmitResponse(BaseModel):
    """Parsed API submit response."""
    status: int
    request: str

    @property
    def success(self) -> bool:
        return self.status == 1

    @property
    def task_id(self) -> str:
        if not self.success:
            raise ValueError(f"No task ID — submission failed: {self.request}")
        return self.request

class PollResponse(BaseModel):
    """Parsed API poll response."""
    status: int
    request: str

    @property
    def ready(self) -> bool:
        return self.request != "CAPCHA_NOT_READY"

    @property
    def success(self) -> bool:
        return self.status == 1

    @property
    def token(self) -> str:
        if not self.success:
            raise ValueError(f"No token — solve failed: {self.request}")
        return self.request

class SolveResult(BaseModel):
    """Result of a successful solve."""
    token: str
    task_id: str
    solve_time: float = Field(description="Solve time in seconds")

Klien

from models import ( RecaptchaV2Request, RecaptchaV3Request, TurnstileRequest, ImageRequest, SubmitResponse, PollResponse, SolveResult, )

SUBMIT_URL = "https://ocr.captchaai.com/in.php" RESULT_URL = "https://ocr.captchaai.com/res.php"

class CaptchaAIError(Exception): def init(self, code: str, message: str = ""): self.code = code super().init(f"{code}: {message}" if message else code)

class CaptchaAI: def init(self, api_key: str, poll_interval: int = 5, timeout: int = 180): if not api_key or len(api_key) < 10: raise ValueError("Invalid API key") self.api_key = api_key self.poll_interval = poll_interval self.timeout = timeout

def _submit(self, params: dict) -> str:
    params["key"] = self.api_key
    params["json"] = 1

    resp = requests.post(SUBMIT_URL, data=params, timeout=30)
    result = SubmitResponse.model_validate(resp.json())

    if not result.success:
        raise CaptchaAIError(result.request, "Submit failed")

    return result.task_id

def _poll(self, task_id: str) -> str:
    start = time.monotonic()

    while time.monotonic() - start < self.timeout:
        time.sleep(self.poll_interval)

        resp = requests.get(RESULT_URL, params={
            "key": self.api_key,
            "action": "get",
            "id": task_id,
            "json": 1,
        }, timeout=15)

        result = PollResponse.model_validate(resp.json())

        if not result.ready:
            continue

        if result.success:
            return result.token

        raise CaptchaAIError(result.request, "Solve failed")

    raise CaptchaAIError("TIMEOUT", f"Task {task_id} timed out after {self.timeout}s")

def _solve(self, params: dict) -> SolveResult:
    start = time.monotonic()
    task_id = self._submit(params)
    token = self._poll(task_id)
    elapsed = time.monotonic() - start

    return SolveResult(
        token=token,
        task_id=task_id,
        solve_time=round(elapsed, 1),
    )

def solve_recaptcha_v2(self, sitekey: str, pageurl: str, **kwargs) -> SolveResult:
    """Solve reCAPTCHA v2 with validated parameters."""
    req = RecaptchaV2Request(sitekey=sitekey, pageurl=pageurl, **kwargs)
    return self._solve(req.to_params())

def solve_recaptcha_v3(self, sitekey: str, pageurl: str, **kwargs) -> SolveResult:
    """Solve reCAPTCHA v3 with validated parameters."""
    req = RecaptchaV3Request(sitekey=sitekey, pageurl=pageurl, **kwargs)
    return self._solve(req.to_params())

def solve_turnstile(self, sitekey: str, pageurl: str, **kwargs) -> SolveResult:
    """Solve Cloudflare Turnstile with validated parameters."""
    req = TurnstileRequest(sitekey=sitekey, pageurl=pageurl, **kwargs)
    return self._solve(req.to_params())

def solve_image(self, base64_image: str, **kwargs) -> SolveResult:
    """Solve image/text CAPTCHA with validated parameters."""
    req = ImageRequest(base64_image=base64_image, **kwargs)
    return self._solve(req.to_params())

def get_balance(self) -> float:
    """Get current account balance."""
    resp = requests.get(RESULT_URL, params={
        "key": self.api_key,
        "action": "getbalance",
        "json": 1,
    }, timeout=10)
    result = SubmitResponse.model_validate(resp.json())
    return float(result.request)

## Penggunaan

Instal dependensi:

```bash
pip install pydantic requests
from pydantic import ValidationError
from client import CaptchaAI, CaptchaAIError

client = CaptchaAI("YOUR_API_KEY", timeout=120)

# Valid request — passes validation, calls API
result = client.solve_recaptcha_v2(
    sitekey="6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
    pageurl="https://staging.example.com/qa-login",
)
print(f"Token: {result.token[:40]}...")
print(f"Solved in {result.solve_time}s")

# Sitekey tidak valid — tertangkap langsung, tanpa API call
try:
    client.solve_recaptcha_v2(sitekey="", pageurl="https://example.com")
except ValidationError as e:
    print(e)
    # sitekey: String should have at least 20 characters

# Skor tidak valid — tertangkap sebelum API call
try:
    client.solve_recaptcha_v3(
        sitekey="6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
        pageurl="https://example.com",
    )
except ValidationError as e:
    print(e)

# API error — tertangkap saat request
try:
    result = client.solve_turnstile(
        sitekey="0x4AAAAAAADnPIDROrmt1Wwj",
        pageurl="https://example.com",
    )
except CaptchaAIError as e:
    print(f"API error: {e.code}")

Pemecahan Masalah

Masalah Penyebab Solusi
ValidationError pada sitekey yang terlihat valid Sitekey terlalu pendek (<20 karakter) Cek panjang sitekey; sesuaikan min_length jika target Anda menggunakan key yang lebih pendek
ValidationError di pageurl Skema URL tidak ada Sertakan awalan https://
Validasi Base64 gambar gagal String terlalu pendek atau mengandung awalan data: Validator menghapus awalan data: secara otomatis; pastikan konten base64 sebenarnya >100 karakter
CaptchaAIError: ERROR_ZERO_BALANCE Dana tidak mencukupi Isi ulang di dashboard CaptchaAI
Error import Pydantic v1 Versi Pydantic salah Gunakan Pydantic v2: pip install 'pydantic>=2.0'

Pertanyaan Umum

Apakah validasi Pydantic menambah overhead?

Dapat diabaikan — mikrodetik per validasi vs. detik untuk round-trip API. Waktu yang dihemat dari menangkap parameter tidak valid sebelum network call jauh melebihi biaya validasi.

Bisakah saya menggunakan ini dengan async (httpx)?

Ya. Ganti requests dengan httpx.AsyncClient dan buat _submit, _poll, serta metode solver menjadi async. Model Pydantic tetap sama — mereka memvalidasi secara sinkron sebelum HTTP call async.

Bagaimana cara memperluas model untuk jenis CAPTCHA baru?

Buat subkelas BaseModel baru dengan field wajib dan metode to_params(). Tambahkan metode solver yang sesuai di kelas klien yang membuat instance model dan memanggil _solve.


Artikel Terkait

  • Panduan Lengkap Python Playwright CaptchaAI
  • Referensi Kode Error CaptchaAI
  • CaptchaAI JSON API vs Form API

Bangun klien CaptchaAI yang tervalidasi — dapatkan API key Anda dan tambahkan model Pydantic.

Komentar dinonaktifkan untuk artikel ini.