Windows पर FastAPI कैप्चा सॉल्वर सर्विस कैसे चलाएँ

FastAPI कैप्चा सॉल्वर सर्विस चलाने के लिए, ऐप शुरू होते समय एक AsyncCapSkip क्लाइंट बनाएँ। एक POST endpoint दें जो sitekey और पेज URL लेता है। हर हल को asyncio.timeout और एक Semaphore में लपेटें, और SDK के exceptions को HTTP status codes से जोड़ें। फिर किसी भी भाषा के स्क्रैपर, वर्कर और script एक ही आंतरिक पते को कॉल करते हैं और CapSkip का पोर्ट या key कभी नहीं देखते। CapSkip आपकी अपनी Windows मशीन पर चलता है, इसलिए सर्विस में प्रति-हल कोई बिल नहीं जुड़ता। यह गाइड इसे करीब 100 लाइनों में बनाती है और दिखाती है कि CapSkip के धीमे, बंद या चुप होने पर हर हिस्सा क्या करता है।
आपको क्या चाहिए
- किसी Windows PC पर चल रहा CapSkip, जिसमें reCAPTCHA सॉल्वर चालू हो। पहली बार शुरू करने के चरण सेटअप गाइड में समझाए गए हैं।
- Python 3.11 या उसके बाद का वर्ज़न, क्योंकि कोड asyncio.timeout इस्तेमाल करता है। नीचे सब कुछ Windows 11 पर Python 3.14.0, capskip 1.3.0, fastapi 0.150.0 और uvicorn 0.54.0 के साथ चलाया गया।
- एक PowerShell prompt। कमांड PowerShell syntax में लिखी गई हैं।
- reCAPTCHA v2 checkbox वाला एक पेज। सर्विस को सिर्फ़ उसका sitekey और उसका पता चाहिए।
- CapSkip के लिए एक पता। Local mode में वह केवल उसी डिवाइस के लिए 127.0.0.1 पर जवाब देता है। Server mode में वह आपके नेटवर्क पते या पब्लिक IP पर सुनता है, ताकि किसी दूसरी मशीन पर चल रही सर्विस API के ज़रिए उसे कॉल कर सके। दोनों यहाँ सेट किए जाते हैं: कनेक्शन सेटिंग्स.
इस गाइड की संख्याएँ उसी PC पर चल रहे एक mock CapSkip से ली गई हैं (यानी टेस्ट के लिए बनाया गया नकली CapSkip)। FastAPI, uvicorn और SDK असली हैं। mock हर हल को एक तयशुदा समय तक रोके रखता है, आमतौर पर दो सेकंड, इसलिए आपके असली हल अलग होंगे। यहाँ कुछ भी असली CapSkip या असली साइट के सामने नहीं चलाया गया।
CapSkip के आगे सर्विस क्यों रखें
अगर कोई एक Python script कैप्चा हल करती है, तो SDK को वहीं से कॉल करें। इसकी जानकारी Python कैप्चा सॉल्वर पेज में है, और पैरेलल सॉल्विंग गाइड में एक ही प्रोसेस के भीतर कई हल करने का तरीका बताया गया है। सर्विस तब काम आती है जब कई कॉलर एक ही CapSkip साझा करते हों। तब चार काम एक ही जगह होने चाहिए।
- एक समय-सीमा। Python SDK डिफ़ॉल्ट रूप से 300 सेकंड तक इंतज़ार करता है। उसका सादा क्लाइंट कोई HTTP timeout सेट ही नहीं करता। हर कॉलर को अपना अलग timer नहीं रखना चाहिए।
- एक सीमा। CapSkip एक साथ तय संख्या में हल चलाता है, और बाकी जॉब उसके अंदर इंतज़ार करती हैं। आपके नियंत्रण वाली क़तार को समझना आसान है।
- एक key। कॉलर को सर्विस की key मिलती है। CapSkip का पता और API key सर्विस की सेटिंग्स में ही रहते हैं।
- कोई भी भाषा। C# वर्कर, PHP साइट और PowerShell टास्क, सब HTTP और JSON बोलते हैं।
चरण 1: पैकेज इंस्टॉल करें
अगर आप हर प्रोजेक्ट के लिए अलग virtual environment रखते हैं, तो पहले एक फ़ोल्डर और virtual environment बनाएँ। capskip पैकेज httpx को साथ खींच लाता है, जिसे async क्लाइंट इस्तेमाल करता है।
pip install capskip fastapi uvicorn
चरण 2: सेटिंग्स को एनवायरनमेंट वेरिएबल के रूप में सेट करें
SDK खुद कोई एनवायरनमेंट वेरिएबल नहीं पढ़ता, इसलिए सर्विस उन्हें पढ़ती है और हर वैल्यू आगे पास करती है। उन्हें उसी PowerShell विंडो में सेट करें जो सर्विस शुरू करेगी। दोनों placeholder keys बदल दें। YOUR_CAPSKIP_KEY CapSkip की सेटिंग्स की API key है, और CapSkip में key validation बंद हो तो कोई भी टेक्स्ट चलता है। YOUR_API_KEY वह key है जो आप अपने कॉलर के लिए खुद गढ़ते हैं।
# 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"
होस्ट के लिए 127.0.0.1 इस्तेमाल करें। इस क्लाइंट के साथ, ठुकराए गए कनेक्शन में 127.0.0.1 पर करीब 2.4 सेकंड और localhost पर करीब 2.7 सेकंड लगे। सादे requests क्लाइंट को एक पहले के टेस्ट में localhost पर करीब 4 सेकंड लगे थे।
चरण 3: सेटिंग्स पढ़ें
main.py बनाएँ। चरण 3 से 10 के ब्लॉक उसी एक फ़ाइल के क्रमवार हिस्से हैं। आख़िर की पूरी फ़ाइल उनके imports को ऊपर मिला देती है।
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"))चरण 4: ऐप शुरू होते समय एक क्लाइंट और एक गेट बनाएँ
lifespan function स्टार्टअप पर एक बार चलता है। वह क्लाइंट और एक Semaphore बनाता है, और दोनों को app state पर रखता है, ताकि हर रिक्वेस्ट उन्हें साझा करे। Semaphore को CapSkip के हर thread के लिए एक स्लॉट दें, जो CapSkip की Max. Threads सेटिंग है (उसका डिफ़ॉल्ट 10 है)।
AsyncCapSkip, Python में एक असली async क्लाइंट है। वह httpx पर बना है और asyncio.sleep से poll करता है, इसलिए poll के बीच का इंतज़ार event loop को ब्लॉक नहीं करता। कॉल खुद कुछ CPU ज़रूर खर्च करती हैं, जैसा ध्यान रखने लायक एक कीमत नाम के सेक्शन में समझाया गया है। सादा CapSkip क्लाइंट requests को बिना timeout के कॉल करता है, इसलिए चुप पड़ा CapSkip एक thread को हमेशा के लिए रोके रखता। recaptchaTimeout को समय-सीमा पर सेट करने से SDK की अपनी घड़ी हमारी घड़ी के बराबर रहती है। दोनों के बराबर होने पर asyncio.timeout पहले चलता है।
@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)चरण 5: सर्विस की key जाँचें
नेटवर्क पते पर खुला, बिना सुरक्षा वाला solve endpoint उन सबके लिए मुफ़्त सॉल्वर है जो उस पोर्ट तक पहुँच सकते हैं। यह dependency X-API-Key header को secrets.compare_digest से मिलाती है, जो bytes जहाँ भी अलग हों, बराबर समय लेता है। mock पर गायब, खाली, गलत, ज़्यादा लंबी और non-ASCII key, सबको 401 मिला। सही key को 200 मिला।
ऊपर की जाँच सर्विस को खाली key के साथ शुरू होने से रोकती है। उसके बिना, header-रहित रिक्वेस्ट डिफ़ॉल्ट रूप से खाली string बन जाती और मेल खा जाती। dependency, body जाँचे जाने से पहले चलती है, इसलिए बिना key वाली और validation में फेल होने वाली body की रिक्वेस्ट को 422 नहीं, 401 मिलता है। जो body वैध JSON नहीं है उसे फिर भी 422 मिलता है, क्योंकि FastAPI पहले JSON decode करता है। जिन कॉलर के पास key नहीं है, उन्हें आपके schema के बारे में बहुत कम पता चलता है।
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"})चरण 6: रिक्वेस्ट जाँचें और समय-सीमा तय करें
request model खाली sitekey या ऐसे URL को ठुकरा देता है जो http से शुरू नहीं होता। FastAPI गलत body का जवाब अपने 422 से देता है और हर समस्या की सूची देता है।
समय-सीमा सबसे अहम हिस्सा है। asyncio.timeout स्लॉट के इंतज़ार और हल, दोनों को एक साथ लपेटता है। हल्के भार वाली सर्विस कुल मिलाकर DEADLINE सेकंड के भीतर जवाब देती है। अगर timeout सिर्फ़ हल को लपेटता, तो एक रिक्वेस्ट की घड़ी शुरू होने से पहले ही वह मिनटों तक क़तार में रह सकती थी। भारी भार में जवाब देर से आ सकता है, जैसा ध्यान रखने लायक एक कीमत नाम के सेक्शन में समझाया गया है।
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"]चरण 7: solve endpoint जोड़ें
endpoint तीन लाइनों का है। पहले key dependency चलती है, फिर हल, फिर टोकन 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}चरण 8: SDK के exceptions को status codes से जोड़ें
SDK चार exception classes उठाता है, सब CapSkipError के अंतर्गत, और सब capskip से import की जा सकती हैं। base class पर एक handler हर एक को status code और एक छोटे error नाम में बदल देता है। ब्योरा log में जाता है और कॉलर तक कभी नहीं पहुँचता, इसलिए CapSkip के संदेश निजी रहते हैं।
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)नीचे की हर पंक्ति को mock पर जानबूझकर पैदा किया गया था।
| स्थिति | क्या हुआ | कितना समय लगा |
|---|---|---|
| 503 | CapSkip बंद है, इसलिए कनेक्शन ठुकरा दिया गया | 2.2 s |
| 503 | CapSkip ने कनेक्शन स्वीकार किया और submit का जवाब कभी नहीं दिया | 5.2 s |
| 422 | CapSkip ने ERROR_CAPTCHA_UNSOLVABLE बताया | समय नहीं मापा गया |
| 502 | CapSkip ने सर्विस की अपनी API key ठुकरा दी | समय नहीं मापा गया |
| 504 | चुप पड़े CapSkip को poll करते समय समय-सीमा बीत गई (एक रिक्वेस्ट, 10 s की समय-सीमा) | 10.0 s |
502 जानबूझकर है। CapSkip की ठुकराई गई key सर्विस की गलती है, कॉलर की नहीं। 5.2 सेकंड वाली पंक्ति httpx का 5 सेकंड का डिफ़ॉल्ट timeout है, जो async क्लाइंट को विरासत में मिलता है। poll का चुप जवाब अलग बात है। SDK फेल हुए poll को अभी तैयार नहीं मानकर दोबारा कोशिश करता है, इसलिए उसे सिर्फ़ समय-सीमा खत्म करती है। सिर्फ़ sitekey और URL के साथ ValidationException नहीं आ सकता। उसके लिए अतिरिक्त options चाहिए, जैसे v2 हल पर action, जो यह सर्विस कभी नहीं भेजती।
चरण 9: समय-सीमा को अलग से पकड़ें
asyncio.timeout, Python का built-in TimeoutError उठाता है। SDK का TimeoutException एक अलग class है, और built-in को पकड़ने से वह नहीं पकड़ा जाता। चरण 8 का handler SDK के TimeoutException को पहले ही capskip_timeout नाम वाले 504 से जोड़ देता है। यह दूसरा handler समय-सीमा को संभालता है, और इसका error नाम कॉलर को बताता है कि कौन-सी घड़ी खत्म हुई।
@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)चरण 10: health check जोड़ें
CapSkip में कोई ping कॉल नहीं है, इसलिए यह जाँच उसके पोर्ट पर एक TCP कनेक्शन खोलती है और तीन सेकंड बाद हार मान लेती है। CapSkip बंद होने पर उसने करीब 2 सेकंड बाद 503 दिया, और mock चलने पर उसने तुरंत 200 दिया। यह monitoring script को बताती है कि सर्विस चालू है और CapSkip नहीं। यह साबित नहीं करती कि हल काम करता है।
@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}Windows पर चलाएँ
uvicorn को उस फ़ोल्डर से शुरू करें जिसमें main.py है। 127.0.0.1 पर bind होने पर सर्विस सिर्फ़ उसी PC पर जवाब देती है।
python -m uvicorn main:app --host 127.0.0.1 --port 8000
दूसरी मशीनों के कॉलर के लिए host को 0.0.0.0 कर दें, जिसका मतलब है हर नेटवर्क adapter। टेस्ट PC पर वह PC के LAN पते पर जवाब देता था, और 127.0.0.1 पर bind उसे ठुकरा देता था। पहले असली सर्विस key सेट करें, और जाँचें कि Windows पोर्ट के लिए कौन-सा firewall नियम बनाता है। सर्विस की key सादे HTTP पर clear text में जाती है, इसलिए इसे सिर्फ़ ऐसे नेटवर्क पर इस्तेमाल करें जिस पर आपको भरोसा हो। firewall prompt यहाँ देखा नहीं गया, और दूसरी मशीन से पहुँच को टेस्ट नहीं किया गया।
एक worker चलाएँ। workers option को 2 पर और हर एक की सीमा 3 पर रखने पर, mock ने एक साथ 6 तक हल देखे। हर worker एक अलग प्रोसेस है जिसका अपना Semaphore है, इसलिए सीमा गुणा हो जाती है। reload option को आप फ़ाइल संपादित करते समय सुरक्षित रूप से इस्तेमाल कर सकते हैं। हल के बीच फ़ाइल को छूने से चल रही रिक्वेस्ट नहीं गिरी, और 1.5 सेकंड बाद की नई रिक्वेस्ट भी सफल रही।
दस्तावेज़ वाले पेज डिफ़ॉल्ट रूप से खुले होते हैं। जब सर्विस किसी नेटवर्क पते पर सुनती हो, तो ऐप को docs_url और openapi_url को None पर सेट करके बनाएँ, ताकि schema सार्वजनिक न रहे।
सर्विस को CapSkip से दूर चलाएँ
सर्विस का उसी PC पर होना ज़रूरी नहीं। CapSkip को अपने नियंत्रण वाली किसी Windows मशीन पर Server mode में रखें, बेहतर होगा कि स्टैटिक पब्लिक IP के साथ। uvicorn शुरू करने से पहले CAPSKIP_HOST और CAPSKIP_PORT को उसी पते पर सेट करें। तीन सावधानियाँ लागू होती हैं। CapSkip में API key validation चालू करें और CAPSKIP_API_KEY में वही key इस्तेमाल करें। SDK सादे HTTP पर बात करता है, इसलिए वह key clear text में नेटवर्क पार करती है। CapSkip अपने पोर्ट के लिए एक allow-any नियम भी जोड़ देता है, इसलिए हर उस पते के लिए एक block नियम जोड़ें जिसे वहाँ तक नहीं पहुँचना चाहिए। सेटअप गाइड के कनेक्शन सेटिंग्स सेक्शन में दोनों mode की सूची दी गई है।
PowerShell से कॉल करें
कॉलर sitekey, पेज URL और सर्विस की key भेजता है। जवाब एक JSON object है जिसमें एक ही फ़ील्ड है, token। टोकन का इस्तेमाल करीब दो मिनट के भीतर करें, और उसे एक ही बार इस्तेमाल करें। यह घड़ी कैसे चलती है, यह टोकन एक्सपायरी गाइड में समझाया गया है। नीचे दिया गया पता आपकी सर्विस के लिए एक placeholder है।
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"}'/solve/ पर trailing slash के साथ किया गया POST 307 redirect पाता है। Windows PowerShell 5.1 पर Invoke-RestMethod ने उसे follow किया और body व header बनाए रखे। अगर आप curl.exe पसंद करते हैं, तो JSON को stdin से दें। आम तौर पर single quotes में लिखा JSON argument PowerShell 5.1 पर फेल होता है, क्योंकि अंदर के double quotes हटा दिए जाते हैं, और सर्विस ने JSON error से जवाब दिया।
# 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 "@-"गेट क्या करता है और क्या नहीं
mock की thread सीमा 3 थी, और दस रिक्वेस्ट एक साथ आईं, हर एक को 2 सेकंड का हल चाहिए था। गेट ने mock को एक समय में तीन हल पर रखा, और दसों 200 के साथ लौटीं। काम करते गेट के बिना, दसों जॉब CapSkip तक पहुँचीं और वहीं क़तार में लग गईं। mock में, उनमें से चार उसकी तीन सेकंड की प्रतीक्षा-सीमा से ज़्यादा इंतज़ार करके हार मान गईं और unsolvable के रूप में लौटीं। वह जवाब mock का अपना मॉडल है, और असली CapSkip पर उसकी पुष्टि नहीं हुई।
स्लॉट का इंतज़ार एक वजह से समय-सीमा के भीतर रखा गया है। 5 सेकंड की समय-सीमा, 1 सेकंड के हल और एक साथ दस रिक्वेस्ट के साथ, छह 200 के साथ लौटीं और चार 504 के साथ। एक रिक्वेस्ट कभी CapSkip तक पहुँची ही नहीं, और पहुँचने वाली नौ में से तीन हल के बीच में छोड़ दी गईं।
स्लॉट हल से थोड़ी ज़्यादा देर तक रुका रहता है। SDK पहले तुरंत poll करता है, फिर 0.25, 0.5, 1, 2 और 4 सेकंड इंतज़ार करता है, और उसके बाद हर poll के बीच 5 सेकंड। इससे poll 0, 0.25, 0.75, 1.75, 3.75, 7.75 और 12.75 सेकंड पर पड़ते हैं, और स्लॉट तब तक लिया हुआ रहता है जब तक कोई poll जवाब न पढ़ ले। जो नतीजा poll के ठीक बाद आता है, वह अगले poll का इंतज़ार करता है। 7.75 s वाला poll 8 सेकंड के हल से एक चौथाई सेकंड पहले ही रह जाता है। टेस्ट PC पर अगले सेक्शन में बताई गई धीमी कॉलों ने उस poll को 8 s के पार धकेल दिया, इसलिए जवाब करीब 8.4 से 8.9 s पर पढ़ा गया। वह कीमत हटाने पर वही हल 12.75 s वाले poll का इंतज़ार करता रहा और pollingInterval के डिफ़ॉल्ट 5 के साथ 12.9 s बाद लौटा, और pollingInterval 1 के साथ 8.9 s बाद। अगर हल लंबे हैं और क़तार लंबी है, तो कम pollingInterval स्लॉट जल्दी खाली करता है।
दो सीमाएँ बाकी हैं। जो कॉलर कनेक्शन बंद कर देता है, वह अपने handler को cancel नहीं करता, इसलिए अनाथ हो चुका हल खत्म होने तक अपना स्लॉट रखे रहता है। एक स्लॉट और 4 सेकंड के हल के साथ, अगली रिक्वेस्ट में कुल 8.3 सेकंड लगे, जबकि खाली स्लॉट के साथ 4.7 सेकंड। mock समय-सीमा बीत जाने के बाद भी छोड़ी गई जॉब पर काम करता रहता है, और SDK में कोई cancel कॉल नहीं है। इसलिए स्लॉट की गिनती कुछ सेकंड के लिए CapSkip के असली threads से आगे निकल सकती है। यह mock में मॉडल किया गया है। असली ऐप को टेस्ट नहीं किया गया।
ध्यान रखने लायक एक कीमत
AsyncCapSkip अपनी भेजी हर रिक्वेस्ट के लिए, हर poll समेत, एक नया httpx क्लाइंट बनाता है। टेस्ट PC पर इसकी कीमत हर बार करीब 141 ms की ब्लॉक करने वाली CPU थी, ज़्यादातर certificate bundle लोड करने में, जिसकी सादे HTTP पते को कभी ज़रूरत नहीं होती। दस एक साथ चलने वाले 2 सेकंड के हल करीब 6 s में खत्म हुए, और उस बौछार के दौरान भेजी गई /openapi.json की रिक्वेस्ट को करीब 1.9 s तक इंतज़ार करना पड़ा। synchronous endpoint से वही दस 3.8 s में हुए और सबसे बुरी स्थिति कुछ मिलीसेकंड रही, लेकिन वह रास्ता अटकी हुई कॉल को खत्म नहीं कर सकता। thread के चारों ओर लपेटी गई समय-सीमा thread को अटका हुआ छोड़ देती है। 41 अटकी रिक्वेस्ट के बाद सर्विस में 41 अटके threads थे, और वे प्रोसेस के रीस्टार्ट होने तक बने रहते हैं।
ब्लॉक हुआ loop भार में घड़ियों को भी खिसका देता है। 2 सेकंड की समय-सीमा वाले चुप पड़े CapSkip के सामने, 12 एक साथ आई रिक्वेस्ट ने 2.6 से 2.9 s पर 8 जवाब दिए और 8.5 s पर capskip_timeout नाम वाले 4 जवाब। 41 रिक्वेस्ट के साथ सबसे धीमे जवाब में 13.9 s लगे। अकेली रिक्वेस्ट ने अपनी समय-सीमा निभाई। 8.5 s वाले जवाबों का कारण अलग से नहीं पकड़ा गया, पर ब्लॉक हुआ loop उसका हिस्सा है, और अकेले SDK में भी यही खिसकाव दिखा। मुट्ठी भर कॉलर के लिए यह ठहराव हानिरहित है। यह चलते हुए हलों की संख्या के साथ बढ़ता है, इसलिए स्लॉट की गिनती उतनी ही रखें जितना CapSkip सच में चला सकता है।
पूरी main.py
यह चरण 3 से 10 को जोड़कर बनी फ़ाइल है, imports को ऊपर इकट्ठा करके। इसे 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}FastAPI कैप्चा सॉल्वर की आम errors
| आप जो देखते हैं | कारण | फिक्स |
|---|---|---|
| सर्विस स्टार्टअप पर ही बंद हो जाती है | SOLVER_API_KEY सेट नहीं है या खाली है। तब Python PowerShell विंडो में उसके लिए KeyError प्रिंट करता है। | उसे उसी विंडो में सेट करें |
| हर कॉल 401 लौटाती है | X-API-Key header, SOLVER_API_KEY के बराबर नहीं है | सर्विस की key भेजें, CapSkip वाली नहीं |
| deadline_exceeded के साथ 504 | स्लॉट का इंतज़ार और हल मिलकर समय-सीमा से आगे निकल गए | SOLVE_DEADLINE बढ़ाएँ, या CapSkip में Max. Threads और CAPSKIP_MAX_THREADS को साथ-साथ बढ़ाएँ |
| capskip_timeout के साथ 504 | SDK की polling सीमा पहले खत्म हो गई, आमतौर पर भार में | एक साथ चल रही रिक्वेस्ट की संख्या घटाएँ, या SOLVE_DEADLINE बढ़ाएँ |
| capskip_unreachable के साथ 503 | CapSkip के पोर्ट पर कुछ भी कनेक्शन स्वीकार नहीं कर रहा | CapSkip शुरू करें और Local या Server mode जाँचें |
| सीमा उम्मीद से ज़्यादा है | एक से ज़्यादा uvicorn worker | एक worker चलाएँ |
| curl.exe से json_invalid के साथ 422 | PowerShell 5.1 ने quotes हटा दिए | JSON को stdin से pipe करें |
FastAPI के अपने lifespan दस्तावेज़ startup hook को कवर करता है, और CapSkip के हर error code की सूची यहाँ मिलती है: API दस्तावेज़.
FAQ
अगर सिर्फ़ एक script कैप्चा हल करती है, तो क्या मुझे सर्विस चाहिए?
नहीं। SDK को उसी script से कॉल करें। सर्विस तब काम आती है जब कई कॉलर, या दूसरी भाषाओं के कॉलर, एक ही CapSkip साझा करते हों और आप एक समय-सीमा, एक सीमा और एक key चाहते हों। सीधी कॉल reCAPTCHA v2 Python गाइड में दिखाई गई है।
क्या मैं सर्विस को किसी क्लाउड प्लेटफ़ॉर्म पर होस्ट कर सकता हूँ?
हाँ, पर होस्टेड सर्विस किसी और की मशीन पर चलती है, इसलिए वह आपके PC के 127.0.0.1 तक नहीं पहुँच सकती। CapSkip को अपने नियंत्रण वाली किसी Windows मशीन पर Server mode में चलाएँ, बेहतर होगा कि स्टैटिक पब्लिक IP के साथ, और CAPSKIP_HOST को उसी पते पर सेट करें। API key validation चालू करें, हर दूसरे पते के लिए block नियम जोड़ें, और याद रखें कि सादे HTTP पर key clear text में जाती है। इस गाइड के लिए किसी होस्टिंग प्लेटफ़ॉर्म को टेस्ट नहीं किया गया।
क्या endpoint को इसके बजाय एक सादा def function होना चाहिए?
इस काम के लिए नहीं। सादा def endpoint एक thread में चलता है, और सादे CapSkip क्लाइंट के पास अटकी हुई कॉल को खत्म करने का कोई तरीका नहीं है। अटकी हुई कॉल कभी नहीं लौटती, इसलिए कॉलर अपने timeout तक इंतज़ार करता है। उसे समय-सीमा में लपेटने पर जवाब समय पर आता है, पर thread अटका रहता है। async क्लाइंट साफ़ तरीके से खत्म होता है।
क्या यही endpoint reCAPTCHA v3 हल कर सकता है?
जैसा लिखा है वैसा नहीं। सर्विस v2 मेथड को कॉल करती है। v3 के लिए कॉल में एक version और एक action जोड़ें, जैसा SDK का reCAPTCHA मेथड स्वीकार करता है। कोई score वैल्यू न भेजें, क्योंकि CapSkip में कोई score सेटिंग नहीं है। इस गाइड में इस्तेमाल हुए checkbox टाइप की जानकारी reCAPTCHA v2 सॉल्वर पेज पर दी गई है।
संक्षेप में
FastAPI कैप्चा सॉल्वर सर्विस चलाने के लिए, एक AsyncCapSkip, एक Semaphore और इंतज़ार व हल के चारों ओर एक asyncio.timeout रखें। सबसे पहले सर्विस की key जाँचें। SDK के TimeoutException को built-in TimeoutError से अलग पकड़ें। सिर्फ़ एक worker चलाएँ। अगर CapSkip Server mode में चलता है, तो उसके आगे block नियम लगाएँ।
- कैप्चा के वे सभी टाइप जिन्हें Python पैकेज संभालता है: Python कैप्चा सॉल्वर पेज.
- checkbox वाला सॉल्वर कैसे काम करता है: reCAPTCHA v2 सॉल्वर पेज.
- एक ही script में कई हल: Python में समानांतर रूप से कैप्चा हल करना.
एक साझा सर्विस पूरी टीम के काम आ सकती है, और किसी को हल गिनने की ज़रूरत नहीं पड़ती। CapSkip आपकी अपनी Windows मशीन पर कैप्चा बायपास संभालता है, इसलिए सर्विस के पीछे का हर हल प्रति-कॉल बिल से मुक्त रहता है।
