Cara Menjalankan Service Pemecah CAPTCHA FastAPI di Windows

fastapi captcha solver - How to Run a FastAPI CAPTCHA Solver Service on Windows

Untuk menjalankan service pemecah CAPTCHA FastAPI, buat satu klien AsyncCapSkip saat aplikasi dimulai. Sediakan endpoint POST yang menerima sitekey dan URL halaman. Bungkus setiap pemecahan dengan asyncio.timeout dan Semaphore, lalu petakan exception SDK ke kode status HTTP. Scraper, worker, dan skrip dalam bahasa apa pun kemudian memanggil satu alamat internal dan tidak pernah melihat port atau key CapSkip. CapSkip berjalan di mesin Windows Anda sendiri, jadi service ini tidak menambah biaya per pemecahan. Panduan ini membangunnya dalam sekitar 100 baris dan menunjukkan apa yang dilakukan setiap bagian saat CapSkip lambat, ditutup, atau diam.

Apa yang Anda butuhkan

  • CapSkip yang berjalan di PC Windows, dengan pemecah reCAPTCHA diaktifkan. Lihat panduan penyiapan yang menjelaskan cara menjalankan CapSkip untuk pertama kali.
  • Python 3.11 atau lebih baru, karena kodenya memakai asyncio.timeout. Semua yang ada di bawah dijalankan di Windows 11 dengan Python 3.14.0, capskip 1.3.0, fastapi 0.150.0, dan uvicorn 0.54.0.
  • Prompt PowerShell. Perintahnya memakai sintaks PowerShell.
  • Halaman dengan kotak centang reCAPTCHA v2. Service hanya membutuhkan sitekey dan alamatnya.
  • Sebuah alamat untuk CapSkip. Dalam mode Local, CapSkip menjawab di 127.0.0.1 hanya untuk perangkat itu. Dalam mode Server, CapSkip mendengarkan di alamat jaringan atau IP publik Anda, sehingga service di mesin lain dapat memanggilnya melalui API. Keduanya diatur di pengaturan koneksi.

Angka-angka dalam panduan ini berasal dari mock CapSkip (pengganti CapSkip yang dibuat khusus untuk pengujian) di PC yang sama. FastAPI, uvicorn, dan SDK yang dipakai adalah yang asli. Mock menahan setiap pemecahan selama waktu yang ditetapkan, biasanya dua detik, jadi pemecahan Anda yang sesungguhnya akan berbeda. Tidak ada bagian di sini yang dijalankan terhadap CapSkip sungguhan atau situs sungguhan.

Mengapa menaruh service di depan CapSkip

Jika satu skrip Python memecahkan CAPTCHA, panggil SDK dari skrip itu. Halaman pemecah CAPTCHA Python membahas hal itu, dan panduan pemecahan paralel membahas banyak pemecahan dalam satu proses. Service baru berguna jika beberapa pemanggil berbagi satu CapSkip. Saat itu, ada empat tugas yang sebaiknya berada di satu tempat.

  • Satu tenggat. SDK Python menunggu hingga 300 detik secara default. Klien biasa SDK ini sama sekali tidak menyetel timeout HTTP. Pemanggil seharusnya tidak membawa timer sendiri-sendiri.
  • Satu batas. CapSkip menjalankan sejumlah pemecahan tertentu sekaligus, dan job tambahan menunggu di dalamnya. Antrean yang Anda kendalikan lebih mudah dipahami.
  • Satu key. Pemanggil mendapat service key. Alamat dan API key CapSkip tetap berada di pengaturan service.
  • Bahasa apa pun. Worker C#, situs PHP, dan tugas PowerShell semuanya berbicara HTTP dan JSON.

Langkah 1: instal paket

Buat folder dan virtual environment lebih dulu jika Anda memakai satu per proyek. Paket capskip membawa httpx, yang dipakai klien async.

pip install capskip fastapi uvicorn

Langkah 2: setel pengaturan sebagai variabel lingkungan

SDK tidak membaca variabel lingkungan sendiri, jadi service membacanya dan meneruskan setiap nilai. Setel di jendela PowerShell yang akan memulai service. Ganti kedua key placeholder. YOUR_CAPSKIP_KEY adalah API key dari pengaturan CapSkip, dan teks apa pun berfungsi selama validasi key dimatikan di CapSkip. YOUR_API_KEY adalah key yang Anda buat sendiri untuk pemanggil Anda.

# Where CapSkip listens and which key it expects.
$env:CAPSKIP_HOST = "127.0.0.1"
$env:CAPSKIP_PORT = "8080"
$env:CAPSKIP_API_KEY = "YOUR_CAPSKIP_KEY"
# Match Max. Threads in the CapSkip settings.
$env:CAPSKIP_MAX_THREADS = "10"
# The key your callers must send to this service.
$env:SOLVER_API_KEY = "YOUR_API_KEY"
# Seconds a caller may wait, queue time included.
$env:SOLVE_DEADLINE = "120"

Pakai 127.0.0.1 untuk host. Dengan klien ini, koneksi yang ditolak memakan sekitar 2.4 detik pada 127.0.0.1 dan sekitar 2.7 detik pada localhost. Klien requests biasa memakan sekitar 4 detik pada localhost dalam pengujian sebelumnya.

Langkah 3: baca pengaturan

Buat main.py. Blok pada Langkah 3 sampai 10 adalah bagian-bagian berurutan dari satu file itu. File lengkap di bagian akhir menggabungkan import-nya di bagian atas.

import asyncio
import os
from contextlib import asynccontextmanager

from capskip import AsyncCapSkip
from fastapi import FastAPI

# The SDK reads no env vars, so pass each setting in by hand.
HOST = os.getenv("CAPSKIP_HOST", "127.0.0.1")
PORT = int(os.getenv("CAPSKIP_PORT", "8080"))
KEY = os.getenv("CAPSKIP_API_KEY", "capskip")
MAX_THREADS = int(os.getenv("CAPSKIP_MAX_THREADS", "10"))
DEADLINE = float(os.getenv("SOLVE_DEADLINE", "120"))

Langkah 4: bangun satu klien dan satu gerbang saat aplikasi dimulai

Fungsi lifespan berjalan sekali saat startup. Fungsi ini membuat klien dan sebuah Semaphore, lalu menaruh keduanya di app state, sehingga setiap permintaan berbagi keduanya. Beri Semaphore satu slot per thread CapSkip, yaitu pengaturan Max. Threads di CapSkip (default-nya 10).

AsyncCapSkip adalah klien async sungguhan di Python. Klien ini dibangun di atas httpx dan melakukan polling dengan asyncio.sleep, jadi jeda di antara poll tidak memblokir event loop. Panggilan itu sendiri memang memakan sedikit CPU, seperti dijelaskan bagian berjudul Satu biaya yang perlu diketahui. Klien CapSkip biasa memanggil requests tanpa timeout, jadi CapSkip yang diam akan menahan sebuah thread selamanya. Menyetel recaptchaTimeout ke tenggat membuat jam milik SDK sendiri sama dengan jam kita. Jika keduanya seri, asyncio.timeout terpicu lebih dulu.

@asynccontextmanager
async def lifespan(app: FastAPI):
    # One client and one gate, shared by every request.
    app.state.cap = AsyncCapSkip(
        apiKey=KEY, host=HOST, port=PORT,
        recaptchaTimeout=DEADLINE,
    )
    app.state.slots = asyncio.Semaphore(MAX_THREADS)
    yield


app = FastAPI(lifespan=lifespan)

Langkah 5: periksa service key

Endpoint pemecahan tanpa perlindungan di alamat jaringan adalah pemecah gratis bagi siapa pun yang bisa menjangkau port-nya. Dependency ini membandingkan header X-API-Key dengan secrets.compare_digest, yang memakan waktu sama di mana pun byte-nya berbeda. Key yang hilang, kosong, salah, lebih panjang, dan non-ASCII semuanya mendapat 401 pada mock. Key yang benar mendapat 200.

Pemeriksaan di bagian atas mencegah service dimulai dengan key kosong. Tanpanya, permintaan tanpa header akan memakai string kosong sebagai default dan cocok. Dependency berjalan sebelum body diperiksa, jadi permintaan tanpa key dan dengan body yang gagal validasi mendapat 401, bukan 422. Body yang bukan JSON valid tetap mendapat 422, karena FastAPI mendekode JSON lebih dulu. Pemanggil yang tidak punya key hanya sedikit mengetahui skema Anda.

import secrets

from fastapi import Depends, Header, HTTPException

SERVICE_KEY = os.environ["SOLVER_API_KEY"]
if not SERVICE_KEY:
    raise SystemExit("SOLVER_API_KEY must not be empty")


def require_key(x_api_key: str = Header(default="")):
    good = secrets.compare_digest(
        x_api_key.encode(), SERVICE_KEY.encode())
    if not good:
        raise HTTPException(
            401, "missing or wrong X-API-Key",
            headers={"WWW-Authenticate": "APIKey"})

Langkah 6: validasi permintaan dan setel tenggat

Model permintaan menolak sitekey kosong atau URL yang tidak diawali http. FastAPI menjawab body yang buruk dengan 422-nya sendiri dan mencantumkan setiap masalah.

Tenggat adalah bagian yang penting. asyncio.timeout membungkus penantian slot dan pemecahan sekaligus. Service dengan beban ringan menjawab dalam DEADLINE detik secara total. Jika timeout hanya membungkus pemecahan, sebuah permintaan bisa mengantre bermenit-menit sebelum jamnya bahkan mulai berjalan. Di bawah beban berat jawaban bisa datang lebih lambat, seperti dijelaskan bagian berjudul Satu biaya yang perlu diketahui.

from fastapi import Request
from pydantic import BaseModel, Field


class SolveRequest(BaseModel):
    sitekey: str = Field(min_length=1)
    url: str = Field(pattern=r"^https?://")


async def solve_v2(state, sitekey: str, url: str) -> str:
    # The deadline also covers the wait for a free slot.
    async with asyncio.timeout(DEADLINE):
        async with state.slots:
            result = await state.cap.recaptcha(sitekey, url)
    return result["code"]

Langkah 7: tambahkan endpoint pemecahan

Endpoint-nya hanya tiga baris. Dependency key berjalan lebih dulu, lalu pemecahan, lalu token dikembalikan sebagai JSON.

@app.post("/solve", dependencies=[Depends(require_key)])
async def solve(body: SolveRequest, request: Request):
    token = await solve_v2(request.app.state, body.sitekey, body.url)
    return {"token": token}

Langkah 8: petakan exception SDK ke kode status

SDK melempar empat kelas exception, semuanya di bawah CapSkipError, dan semuanya bisa diimpor dari capskip. Satu handler pada kelas dasar mengubah masing-masing menjadi kode status dan nama error yang singkat. Detailnya masuk ke log dan tidak pernah ke pemanggil, sehingga pesan CapSkip tetap privat.

import logging

from capskip import ApiException, CapSkipError, NetworkException
from capskip import TimeoutException, ValidationException
from fastapi.responses import JSONResponse

STATUS = {
    NetworkException: (503, "capskip_unreachable"),
    TimeoutException: (504, "capskip_timeout"),
    ValidationException: (422, "bad_parameters"),
    ApiException: (502, "capskip_error"),
}
@app.exception_handler(CapSkipError)
async def capskip_error(request: Request, exc: CapSkipError):
    # Log the detail here, keep it away from the caller.
    logging.getLogger("uvicorn.error").warning("%r", exc)
    status, code = STATUS.get(type(exc), (502, "capskip_error"))
    if "UNSOLVABLE" in str(exc):
        status, code = 422, "unsolvable"
    return JSONResponse({"error": code}, status)

Setiap baris di bawah ini dipicu dengan sengaja terhadap mock.

StatusApa yang terjadiBerapa lama waktu yang dibutuhkan
503CapSkip ditutup, jadi koneksi ditolak2.2 s
503CapSkip menerima koneksi dan tidak pernah menjawab pengiriman5.2 s
422CapSkip melaporkan ERROR_CAPTCHA_UNSOLVABLEtidak diukur
502CapSkip menolak API key milik service sendiritidak diukur
504Tenggat lewat saat polling CapSkip yang diam (satu permintaan, tenggat 10 detik)10.0 s

Status 502 itu disengaja. API key CapSkip yang ditolak adalah kesalahan service, bukan kesalahan pemanggil. Baris 5.2 detik adalah timeout default httpx sebesar 5 detik, yang diwarisi klien async. Jawaban yang diam pada sebuah poll berbeda. SDK menganggap poll yang gagal sebagai belum siap lalu mencoba lagi, jadi hanya tenggat yang mengakhirinya. ValidationException tidak mungkin terjadi hanya dengan sitekey dan URL. Exception itu membutuhkan opsi tambahan seperti action pada pemecahan v2, yang tidak pernah dikirim service ini.

Langkah 9: tangkap tenggat secara terpisah

asyncio.timeout melempar TimeoutError bawaan Python. TimeoutException milik SDK adalah kelas yang berbeda, dan menangkap yang bawaan tidak menangkapnya. Handler di Langkah 8 sudah memetakan TimeoutException milik SDK ke 504 bernama capskip_timeout. Handler kedua ini menangani tenggat, dan nama error-nya memberi tahu pemanggil jam mana yang habis.

@app.exception_handler(TimeoutError)
async def deadline_hit(request: Request, exc: TimeoutError):
    # asyncio.timeout raises the built-in TimeoutError. The SDK's
    # TimeoutException is a different class, so it needs its own entry.
    return JSONResponse({"error": "deadline_exceeded"}, 504)

Langkah 10: tambahkan health check

CapSkip tidak memiliki panggilan ping, jadi pemeriksaan membuka koneksi TCP ke port-nya dan menyerah setelah tiga detik. Dengan CapSkip ditutup, pemeriksaan menjawab 503 setelah sekitar 2 detik, dan dengan mock berjalan pemeriksaan menjawab 200 seketika. Pemeriksaan ini memberi tahu skrip pemantau bahwa service berjalan sedangkan CapSkip tidak. Pemeriksaan ini tidak membuktikan bahwa pemecahan berfungsi.

@app.get("/health")
async def health():
    # CapSkip has no ping call, so check that its port accepts a
    # connection.
    try:
        _, writer = await asyncio.wait_for(
            asyncio.open_connection(HOST, PORT), timeout=3)
    except (OSError, TimeoutError):
        return JSONResponse({"ok": False}, 503)
    writer.close()
    return {"ok": True}

Jalankan di Windows

Jalankan uvicorn dari folder yang berisi main.py. Dengan bind ke 127.0.0.1, service hanya menjawab di PC itu.

python -m uvicorn main:app --host 127.0.0.1 --port 8000

Untuk pemanggil di mesin lain, ubah host menjadi 0.0.0.0, yang berarti setiap adapter jaringan. Di PC uji, alamat itu menjawab di alamat LAN PC tersebut, sedangkan bind ke 127.0.0.1 menolaknya. Setel service key yang sungguhan lebih dulu, dan periksa aturan firewall apa yang dibuat Windows untuk port itu. Service key dikirim sebagai teks polos lewat HTTP biasa, jadi pakai ini hanya di jaringan yang Anda percaya. Prompt firewall tidak teramati di sini, dan akses dari mesin lain tidak diuji.

Jalankan satu worker. Dengan opsi workers disetel ke 2 dan batas 3 untuk masing-masing, mock melihat hingga 6 pemecahan sekaligus. Setiap worker adalah proses terpisah dengan Semaphore sendiri, jadi batasnya berlipat. Opsi reload aman dipakai saat Anda mengedit. Menyentuh file di tengah pemecahan tidak menjatuhkan permintaan yang sedang berjalan, dan permintaan baru 1.5 detik kemudian juga berhasil.

Halaman dokumentasi terbuka secara default. Saat service mendengarkan di alamat jaringan, buat aplikasi dengan docs_url dan openapi_url disetel ke None, agar skemanya tidak publik.

Jalankan service terpisah dari CapSkip

Service tidak harus berada di PC yang sama. Taruh CapSkip dalam mode Server di mesin Windows yang Anda kendalikan, sebaiknya dengan IP publik statis. Setel CAPSKIP_HOST dan CAPSKIP_PORT ke alamat itu sebelum menjalankan uvicorn. Ada tiga peringatan. Aktifkan validasi API key di CapSkip dan pakai key yang sama di CAPSKIP_API_KEY. SDK berbicara lewat HTTP biasa, jadi key itu melintasi jaringan sebagai teks polos. CapSkip juga menambahkan aturan allow-any untuk port-nya, jadi tambahkan aturan blokir untuk setiap alamat yang tidak boleh menjangkaunya. Bagian pengaturan koneksi di panduan penyiapan mencantumkan kedua mode.

Panggil dari PowerShell

Pemanggil mengirim sitekey, URL halaman, dan service key. Jawabannya adalah objek JSON dengan satu field, token. Pakai token dalam sekitar dua menit, dan pakai sekali saja. Lihat panduan masa berlaku token yang menjelaskan hitungan waktunya. Alamat di bawah ini hanyalah placeholder untuk service Anda.

Invoke-RestMethod -Method Post -Uri http://192.168.1.50:8000/solve `
  -Headers @{ "X-API-Key" = "YOUR_API_KEY" } `
  -ContentType "application/json" `
  -Body '{"sitekey":"YOUR_SITEKEY","url":"https://example.com/page-with-recaptcha"}'

POST ke /solve/ dengan trailing slash mendapat redirect 307. Invoke-RestMethod di Windows PowerShell 5.1 mengikutinya dan mempertahankan body serta header. Jika Anda lebih suka curl.exe, berikan JSON lewat stdin. Argumen JSON dengan tanda kutip tunggal yang lazim gagal di PowerShell 5.1, karena tanda kutip ganda di dalamnya dihapus, dan service menjawab dengan error JSON.

# curl.exe reads the JSON from stdin, so no quote escaping is needed.
$body = '{"sitekey":"YOUR_SITEKEY","url":"https://example.com/page-with-recaptcha"}'
$body | curl.exe -s http://192.168.1.50:8000/solve `
  -H "X-API-Key: YOUR_API_KEY" `
  -H "Content-Type: application/json" `
  --data-binary "@-"

Apa yang dilakukan dan tidak dilakukan gerbang

Batas thread mock adalah 3, dan sepuluh permintaan datang sekaligus, masing-masing membutuhkan pemecahan 2 detik. Gerbang menjaga mock pada tiga pemecahan sekaligus, dan kesepuluhnya kembali dengan 200. Tanpa gerbang yang berfungsi, kesepuluh job sampai ke CapSkip dan mengantre di sana. Pada mock, empat di antaranya menyerah setelah menunggu melewati batas tunggu tiga detiknya dan kembali sebagai tak terpecahkan. Jawaban itu adalah model milik mock sendiri, dan tidak dikonfirmasi pada CapSkip sungguhan.

Penantian slot berada di dalam tenggat karena suatu alasan. Dengan tenggat 5 detik, pemecahan 1 detik, dan sepuluh permintaan sekaligus, enam kembali dengan 200 dan empat kembali dengan 504. Satu permintaan tidak pernah sampai ke CapSkip, dan tiga dari sembilan yang sampai ditinggalkan di tengah pemecahan.

Sebuah slot dipegang sedikit lebih lama daripada pemecahan itu sendiri. SDK melakukan polling seketika, lalu menunggu 0.25, 0.5, 1, 2, dan 4 detik, kemudian 5 detik di antara poll. Itu menempatkan poll pada 0, 0.25, 0.75, 1.75, 3.75, 7.75, dan 12.75 detik, dan slot tetap terpakai sampai sebuah poll membaca jawabannya. Hasil yang muncul tepat setelah sebuah poll menunggu poll berikutnya. Poll pada 7.75 detik meleset seperempat detik dari pemecahan 8 detik. Di PC uji, panggilan lambat yang dijelaskan di bagian berikutnya mendorong poll itu melewati 8 detik, sehingga jawaban dibaca pada sekitar 8.4 sampai 8.9 detik. Setelah biaya itu dihilangkan, pemecahan yang sama menunggu poll pada 12.75 detik dan kembali setelah 12.9 detik dengan pollingInterval default 5, dan setelah 8.9 detik dengan pollingInterval 1. Jika pemecahan panjang dan antrean panjang, pollingInterval yang lebih rendah membebaskan slot lebih cepat.

Masih ada dua batas. Pemanggil yang memutus koneksi tidak membatalkan handler-nya, jadi pemecahan yatim (orphaned solve) tetap memegang slotnya sampai selesai. Dengan satu slot dan pemecahan 4 detik, permintaan berikutnya memakan 8.3 detik secara total, dibandingkan 4.7 detik dengan slot yang bebas. Mock juga terus mengerjakan job yang ditinggalkan setelah tenggat lewat, dan SDK tidak memiliki panggilan cancel. Jumlah slot karena itu bisa mendahului thread CapSkip yang sebenarnya selama beberapa detik. Hal itu dimodelkan di mock. Aplikasi yang asli tidak diuji.

Satu biaya yang perlu diketahui

AsyncCapSkip membangun klien httpx baru untuk setiap permintaan yang dikirimnya, termasuk setiap poll. Di PC uji, itu memakan sekitar 141 ms CPU yang memblokir setiap kali, terutama untuk memuat bundel sertifikat, yang tidak pernah dibutuhkan alamat HTTP biasa. Sepuluh pemecahan 2 detik serentak selesai dalam sekitar 6 detik, dan permintaan ke /openapi.json yang dikirim selama lonjakan itu menunggu hingga sekitar 1.9 detik. Kesepuluh yang sama melalui endpoint sinkron memakan 3.8 detik dengan kasus terburuk beberapa milidetik, tetapi jalur itu tidak bisa mengakhiri panggilan yang menggantung. Tenggat yang dibungkus di sekitar sebuah thread meninggalkan thread itu macet. Setelah 41 permintaan menggantung, service memegang 41 thread macet, dan thread itu tetap ada sampai proses di-restart.

Loop yang terblokir juga membuat jam meleset saat beban tinggi. Terhadap CapSkip yang diam dengan tenggat 2 detik, 12 permintaan serentak memberi 8 jawaban pada 2.6 sampai 2.9 detik dan 4 jawaban bernama capskip_timeout pada 8.5 detik. Dengan 41 permintaan, yang paling lambat memakan 13.9 detik. Satu permintaan tunggal memenuhi tenggatnya. Penyebab jawaban 8.5 detik itu tidak diisolasi, tetapi loop yang terblokir adalah bagian darinya, dan SDK saja menunjukkan keterlambatan yang sama. Untuk segelintir pemanggil, hentian itu tidak berbahaya. Hentian itu bertambah seiring jumlah pemecahan yang sedang berjalan, jadi jaga jumlah slot sebesar yang benar-benar bisa dijalankan CapSkip.

main.py lengkap

Ini adalah Langkah 3 sampai 10 yang digabung, dengan import dikumpulkan di bagian atas. Simpan sebagai main.py.

import asyncio
import logging
import os
import secrets
from contextlib import asynccontextmanager

from capskip import ApiException, AsyncCapSkip, CapSkipError
from capskip import NetworkException, TimeoutException, ValidationException
from fastapi import Depends, FastAPI, Header, HTTPException, Request
from fastapi.responses import JSONResponse
from pydantic import BaseModel, Field

# The SDK reads no env vars, so pass each setting in by hand.
HOST = os.getenv("CAPSKIP_HOST", "127.0.0.1")
PORT = int(os.getenv("CAPSKIP_PORT", "8080"))
KEY = os.getenv("CAPSKIP_API_KEY", "capskip")
MAX_THREADS = int(os.getenv("CAPSKIP_MAX_THREADS", "10"))
DEADLINE = float(os.getenv("SOLVE_DEADLINE", "120"))


@asynccontextmanager
async def lifespan(app: FastAPI):
    # One client and one gate, shared by every request.
    app.state.cap = AsyncCapSkip(
        apiKey=KEY, host=HOST, port=PORT,
        recaptchaTimeout=DEADLINE,
    )
    app.state.slots = asyncio.Semaphore(MAX_THREADS)
    yield


app = FastAPI(lifespan=lifespan)

SERVICE_KEY = os.environ["SOLVER_API_KEY"]
if not SERVICE_KEY:
    raise SystemExit("SOLVER_API_KEY must not be empty")


def require_key(x_api_key: str = Header(default="")):
    good = secrets.compare_digest(
        x_api_key.encode(), SERVICE_KEY.encode())
    if not good:
        raise HTTPException(
            401, "missing or wrong X-API-Key",
            headers={"WWW-Authenticate": "APIKey"})


class SolveRequest(BaseModel):
    sitekey: str = Field(min_length=1)
    url: str = Field(pattern=r"^https?://")


async def solve_v2(state, sitekey: str, url: str) -> str:
    # The deadline also covers the wait for a free slot.
    async with asyncio.timeout(DEADLINE):
        async with state.slots:
            result = await state.cap.recaptcha(sitekey, url)
    return result["code"]


@app.post("/solve", dependencies=[Depends(require_key)])
async def solve(body: SolveRequest, request: Request):
    token = await solve_v2(request.app.state, body.sitekey, body.url)
    return {"token": token}


STATUS = {
    NetworkException: (503, "capskip_unreachable"),
    TimeoutException: (504, "capskip_timeout"),
    ValidationException: (422, "bad_parameters"),
    ApiException: (502, "capskip_error"),
}


@app.exception_handler(CapSkipError)
async def capskip_error(request: Request, exc: CapSkipError):
    # Log the detail here, keep it away from the caller.
    logging.getLogger("uvicorn.error").warning("%r", exc)
    status, code = STATUS.get(type(exc), (502, "capskip_error"))
    if "UNSOLVABLE" in str(exc):
        status, code = 422, "unsolvable"
    return JSONResponse({"error": code}, status)


@app.exception_handler(TimeoutError)
async def deadline_hit(request: Request, exc: TimeoutError):
    # asyncio.timeout raises the built-in TimeoutError. The SDK's
    # TimeoutException is a different class, so it needs its own entry.
    return JSONResponse({"error": "deadline_exceeded"}, 504)


@app.get("/health")
async def health():
    # CapSkip has no ping call, so check that its port accepts a
    # connection.
    try:
        _, writer = await asyncio.wait_for(
            asyncio.open_connection(HOST, PORT), timeout=3)
    except (OSError, TimeoutError):
        return JSONResponse({"ok": False}, 503)
    writer.close()
    return {"ok": True}

Kesalahan umum pada pemecah CAPTCHA FastAPI

Apa yang Anda lihatPenyebabPerbaiki
Service keluar saat startupSOLVER_API_KEY tidak disetel atau kosong. Python lalu mencetak KeyError untuknya di jendela PowerShell.Setel di jendela yang sama
Setiap panggilan mengembalikan 401Header X-API-Key tidak sama dengan SOLVER_API_KEYKirim service key, bukan key milik CapSkip
504 dengan deadline_exceededPenantian slot ditambah pemecahan melewati tenggatNaikkan SOLVE_DEADLINE, atau naikkan Max. Threads di CapSkip dan CAPSKIP_MAX_THREADS bersamaan
504 dengan capskip_timeoutBatas polling SDK habis lebih dulu, biasanya saat beban tinggiKurangi jumlah permintaan yang sedang berjalan, atau naikkan SOLVE_DEADLINE
503 dengan capskip_unreachableTidak ada yang menerima koneksi di port CapSkipJalankan CapSkip dan periksa mode Local atau Server
Batas lebih tinggi dari yang diharapkanLebih dari satu worker uvicornJalankan satu worker
422 dengan json_invalid dari curl.exePowerShell 5.1 menghapus tanda kutipAlirkan JSON lewat stdin

Dari FastAPI sendiri, baca dokumentasi lifespan yang membahas hook startup. Setiap kode error CapSkip tercantum di Dokumentasi API.

FAQ

Apakah saya membutuhkan service jika hanya satu skrip yang memecahkan CAPTCHA?

Tidak. Panggil SDK dari skrip itu. Service membantu jika beberapa pemanggil, atau pemanggil dalam bahasa lain, berbagi satu CapSkip dan Anda menginginkan satu tenggat, satu batas, dan satu key. Lihat panduan reCAPTCHA v2 Python yang menunjukkan pemanggilan langsungnya.

Bisakah saya meng-host service di platform cloud?

Bisa, tetapi service yang di-host berjalan di mesin milik pihak lain, jadi tidak bisa menjangkau 127.0.0.1 di PC Anda. Jalankan CapSkip dalam mode Server di mesin Windows yang Anda kendalikan, sebaiknya dengan IP publik statis, dan setel CAPSKIP_HOST ke alamat itu. Aktifkan validasi API key, tambahkan aturan blokir untuk setiap alamat lain, dan ingat bahwa key dikirim sebagai teks polos lewat HTTP biasa. Tidak ada platform hosting yang diuji untuk panduan ini.

Haruskah endpoint-nya berupa fungsi def biasa?

Tidak untuk pekerjaan ini. Endpoint def biasa berjalan di sebuah thread, dan klien CapSkip biasa tidak punya cara untuk mengakhiri panggilan yang menggantung. Panggilan yang menggantung tidak pernah kembali, jadi pemanggil menunggu sampai timeout-nya sendiri. Membungkusnya dengan tenggat membuat jawaban tepat waktu, tetapi thread-nya tetap macet. Klien async mengakhirinya dengan bersih.

Bisakah endpoint yang sama memecahkan reCAPTCHA v3?

Tidak seperti yang ditulis. Service memanggil metode v2. Untuk v3, tambahkan version dan action ke panggilan, seperti yang diterima metode reCAPTCHA SDK. Jangan kirim nilai score, karena CapSkip tidak memiliki pengaturan score. Halaman pemecah reCAPTCHA v2 membahas tipe kotak centang yang dipakai panduan ini.

Versi singkatnya

Untuk menjalankan service pemecah CAPTCHA FastAPI, pertahankan satu AsyncCapSkip, satu Semaphore, dan satu asyncio.timeout di sekitar penantian dan pemecahan. Periksa service key lebih dulu. Tangkap TimeoutException milik SDK secara terpisah dari TimeoutError bawaan. Jalankan satu worker. Pasang aturan blokir di depan CapSkip jika berjalan dalam mode Server.

Service bersama bisa melayani seluruh tim tanpa ada yang menghitung jumlah pemecahan. CapSkip menangani bypass captcha di mesin Windows Anda sendiri, sehingga setiap pemecahan di balik service tetap bebas dari tagihan per panggilan.