Windmill Script (Python) में कैप्चा कैसे हल करें

Windmill में कैप्चा हल करना इस इंटीग्रेशन का लगभग सबसे छोटा रूप है। Windmill आपके script के ऊपर लिखे imports पढ़ता है, उन्हें PyPI से मिलाता है और एक lockfile में पिन कर देता है, इसलिए CapSkip SDK बिना किसी install स्टेप और बिना किसी requirements फ़ाइल के आ जाता है। बचता है सिर्फ़ एक main function जो sitekey लेता है और आपको token लौटाता है। सोचने लायक़ हिस्सा कोड नहीं है, बल्कि यह है कि worker किस मशीन पर चलता है, क्योंकि यही तय करता है कि सॉल्वर loopback पते पर ही रहेगा या उसे आपके network पर सुनना पड़ेगा।
आपको क्या चाहिए
- एक Windmill instance, चाहे सेल्फ-होस्टेड हो या उनके cloud पर, और एक ऐसा workspace जिसमें आप कोई script deploy कर सकें।
- किसी Windows मशीन पर चलता CapSkip। अगर worker उसी मशीन पर चलता है तो Local mode ठीक है। अगर workers containers में या किसी दूसरे host पर रहते हैं, तो Server mode पर चले जाइए।
- जिस साइट को आप ऑटोमेट कर रहे हैं उसका sitekey और पेज URL।
- एक Windmill वेरिएबल जिसमें सॉल्वर की key रखी हो, और एक worker एनवायरनमेंट वेरिएबल जिसमें उसका पता हो।
import की लाइन ही पूरा install क्यों है
जब आप कोई script सेव करते हैं, Windmill उसके top-level imports पढ़ता है, पता लगाता है कि वे किन PyPI packages से मेल खाते हैं, और एक dependency job चलाता है जो lockfile लिखता है। वह lockfile script के उसी वर्शन से जुड़ जाता है, इसलिए जो deployment आपने टेस्ट किया था वही छह महीने बाद भी चलता है। न कोई requirements फ़ाइल संभालनी है और न worker पर हाथ से कुछ install करना है।
आप interpreter को भी उसी जगह पिन कर सकते हैं, script के header में एक comment के ज़रिए। जो deployed script किसी वर्शन की माँग नहीं करता, वह Python 3.11 पर चलता है।
# py312 # pip install capskip - Windmill resolves this import itself # and locks the version when the script is deployed. from capskip import CapSkip
स्टेप 1: solve वाला script
Windmill का script एक main function होता है। उसके arguments ही input schema बनते हैं और वही form Windmill रेंडर करता है, इसलिए उन्हें type कीजिए। आप जो कुछ return करते हैं वही script का नतीजा होता है, और आगे का कोई flow step उसे वहीं से पढ़ लेता है।
# py312
# pip install capskip - resolved from this import on save.
import wmill
from capskip import CapSkip
def main(sitekey: str, page_url: str) -> str:
# Host and port come from the worker environment. The key is
# a Windmill variable, so it is stored encrypted and never
# appears in the script body or in the run logs.
solver = CapSkip(
host=wmill.get_variable("u/admin/capskip_host"),
port=8080,
apiKey=wmill.get_variable("u/admin/capskip_key"),
)
result = solver.recaptcha(sitekey=sitekey, url=page_url)
return result["code"] # the token, for the next stepreCAPTCHA v2 के लिए पूरा इंटीग्रेशन बस इतना ही है। बाक़ी हर variant वही method है, बस एक अतिरिक्त keyword के साथ: invisible को 1, enterprise को 1, या version को v3 पर सेट कीजिए और साथ में एक action का नाम दीजिए। Turnstile और GeeTest के अपने methods हैं, बनावट वही है, और parameters की पूरी सूची यहाँ है: CapSkip API डॉक्यूमेंटेशन.
हाथ से पोलिंग loop लिखने की तरफ़ बढ़ने से पहले SDK के बारे में दो बातें जान लेना ठीक रहेगा। पोलिंग वह ख़ुद करता है, 250 मिलीसेकंड से शुरू करके धीरे-धीरे अंतराल बढ़ाते हुए, किसी एक समान अंतराल पर सोए बिना, और यह आमतौर पर raw API के बताए गए इंतज़ार के समय से बेहतर निकलता है। और reCAPTCHA, Turnstile तथा GeeTest के लिए उसकी ऊपरी सीमा 300 सेकंड है, जो recaptchaTimeout से तय होती है। यही संख्या तब मायने रखती है जब आप स्टेप 4 में script का timeout सेट करते हैं।
स्टेप 2: key को Windmill वेरिएबल में रखें
Windmill में वेरिएबल और secrets पहले दर्जे की चीज़ें हैं, और ऊपर वाला script उनमें से एक को सीधे पढ़ता है। इसे करने का एक दूसरा तरीक़ा भी है जो flows के लिए ज़्यादा जँचता है: वेरिएबल को reference syntax से step के argument के रूप में भेजिए, और Windmill उसे चलने के समय कॉल करने वाले की अनुमतियों के साथ हल कर देता है।
| मान कहाँ रहता है | script उसे कैसे पाता है |
|---|---|
| एक Windmill secret वेरिएबल | उसे script के body में wmill client से पढ़ें, जैसा ऊपर है |
| एक Windmill वेरिएबल, जो step के argument के रूप में भेजा गया हो | argument को dollar-var वाला मान दें और उसके बाद वेरिएबल का path |
| एक Windmill resource जिसमें एक साथ कई fields हों | argument को dollar-res वाला मान दें और उसके बाद resource का path |
| worker host पर एक एनवायरनमेंट वेरिएबल | उसे process के environment से पढ़ें, बशर्ते worker को उसे आगे भेजने की अनुमति हो |
ये references पुनरावर्ती ढंग से हल होते हैं, lists और नेस्टेड objects के अंदर भी, इसलिए keys की सूची लेने वाला कोई step हर element में एक reference रख सकता है। इस workspace को उसकी अपनी सॉल्वर key दीजिए, बजाय इसके कि आप एक ही key हर चलने वाली चीज़ में साझा करें।
स्टेप 3: worker कहाँ चलता है, यही कनेक्शन मोड तय करता है
असल में सेटअप की शक्ल यही सवाल तय करता है, और इसमें ग़लती करना आसान है क्योंकि script दोनों हालात में एक जैसा ही दिखता है। Windmill का worker एक स्वायत्त process है जो एक समय पर एक ही script चलाता है। वह डेटाबेस के बग़ल में कोई container हो सकता है, किसी VM पर चलती कोई process, या आपके अपने डेस्कटॉप पर चलती कोई process। जो भी हो, SDK की कॉल एक socket worker से खोलती है, इसलिए सॉल्वर तक वहीं से पहुँचा जाना चाहिए, कहीं और से नहीं।
ठीक इसी के लिए CapSkip में दो कनेक्शन मोड हैं। Local, 127.0.0.1 से बंधता है और सिर्फ़ उसी डिवाइस को सेवा देता है। Server, आपके network पते या public IP से बंधता है, इसलिए कोई दूसरा box, कोई container host या कोई होस्टेड प्लेटफ़ॉर्म उसी Windows मशीन तक API के ज़रिए पहुँच सकता है। दोनों यहाँ सेट किए जाते हैं: कनेक्शन सेटिंग्स, और Server mode सिर्फ़ यह बदलता है कि सॉल्वर कहाँ चलता है। यह अब भी आपका हार्डवेयर है और अब भी बिना मीटर के है।
| आपका worker कहाँ चलता है | कौन सा mode, और host का मान |
|---|---|
| CapSkip वाली उसी Windows मशीन पर | Local mode। host का मान 127.0.0.1 ही रहता है |
| किसी container में या अपने network के किसी दूसरे box पर | Server mode। host का मान सॉल्वर मशीन का LAN पता होता है |
| Windmill के cloud पर, या आपके network के बाहर किसी VM पर | स्टैटिक public IP के साथ Server mode, और साथ में एक firewall नियम |
Windmill के workers Windows पर चलते ज़रूर हैं, और यही वह स्थिति है जो आपको loopback पते पर बनाए रखती है। वहाँ एक सेटिंग मायने रखती है। PID namespace की isolation Linux पर डिफ़ॉल्ट रूप से true रहती है, और Windmill का अपना दस्तावेज़ कहता है कि Windows workers के लिए इसे false कर दीजिए। किसी Windows worker पर ENABLE_UNSHARE_PID नाम के वेरिएबल को false कर दीजिए और वह सामान्य रूप से शुरू हो जाता है।
बाक़ी दो पंक्तियों के लिए, पता script में नहीं बल्कि worker के environment में रहना चाहिए। Windmill हर host वेरिएबल डिफ़ॉल्ट रूप से किसी job को नहीं सौंपता, इसलिए जो चाहिए उनके नाम worker पर WHITELIST_ENVS नाम के वेरिएबल में, कॉमा से अलग करके लिख दीजिए। कोई worker group अपने ख़ुद के स्टैटिक और डायनैमिक एनवायरनमेंट वेरिएबल भी रख सकता है, जो UI में सेट होते हैं, और जब आपके सिर्फ़ कुछ ही workers सॉल्वर के पास बैठे हों तो यही ज़्यादा साफ़-सुथरा विकल्प है।
स्टेप 4: timeout और retry
Windmill किसी script की runtime settings में एक Timeout फ़ील्ड रखता है, Cache और Concurrency की सीमाओं के बग़ल में। इसे अपने सबसे धीमे solve से ऊपर सेट कीजिए, नीचे नहीं। reCAPTCHA v2 का checkbox आमतौर पर एक मिनट से भी काफ़ी कम में हो जाता है, पर Turnstile के challenge पेज और GeeTest ज़्यादा वक़्त लेते हैं, और SDK हार मानकर TimeoutException देने से पहले 300 सेकंड तक पोल करता रहेगा। उस मान से कम script timeout किसी धीमे solve को ऐसे मारे गए job में बदल देता है जिसके log में कुछ काम का नहीं होता।
जैसे ही यह script किसी flow का step बनता है, आपको एक दूसरी परत मिल जाती है। Windmill के flow steps दो शक्लों में retry करते हैं, और exponential वाली शक्ल ऐसे सॉल्वर पर जँचती है जो थोड़ी देर के लिए व्यस्त हो।
| retry की शक्ल | आप क्या कॉन्फ़िगर करते हैं | कब इस्तेमाल करें |
|---|---|---|
| एक स्थिर देरी | अधिकतम कितनी कोशिशें और एक तय देरी | ऐसा सॉल्वर जो कभी-कभार रीस्टार्ट होता है, जहाँ एक समान इंतज़ार ठीक रहता है |
| Exponential backoff | अधिकतम कितनी कोशिशें, सेकंड में एक base और एक multiplier | ऐसी कोई भी चीज़ जो सचमुच व्यस्त हो सकती है, ताकि आप उस पर हथौड़े चलाने के बजाय पीछे हटें |
exponential शक्ल में देरी multiplier को base की कोशिश-संख्या वाली घात से गुणा करके निकलती है, इसलिए 3 का base और 2 का multiplier पाँच कोशिशों में इंतज़ार को 6 सेकंड से 486 सेकंड तक फैला देता है। एक Continue on error सेटिंग भी है जो retries ख़त्म हो जाने के बाद flow को आगे बढ़ने देती है और error को step के नतीजे के रूप में आगे भेज देती है, और इसी तरह आप ऐसी branch बनाते हैं जो रन को नाकाम करने के बजाय किसी विकल्प पर लौट आती है।
पूरा चलने वाला उदाहरण
एक ही script जो हल भी करता है और submit भी, ताकि token अगले step का इंतज़ार करता हुआ पड़ा न रहे। आख़िरी बात शैली की नहीं है। reCAPTCHA token लगभग दो मिनट तक चलता है, और ऐसा flow जो एक step में हल करता है, फिर किसी मंज़ूरी का इंतज़ार करता है, फिर दूसरे step में submit करता है, उसे गँवाने का पक्का तरीक़ा है। इस पर और बातें इस गाइड में हैं: reCAPTCHA token की समय समाप्ति.
# py312
# pip install capskip requests - both resolved from these imports.
import os
import requests
import wmill
from capskip import CapSkip
from capskip.exceptions import TimeoutException, NetworkException
SITE = "https://example.com/page-with-recaptcha"
def main(sitekey: str, username: str) -> dict:
# CAPSKIP_HOST is set on the worker and allowed through by
# WHITELIST_ENVS. It falls back to the loopback address so the
# same script still runs on a worker that sits next to CapSkip.
solver = CapSkip(
host=os.environ.get("CAPSKIP_HOST", "127.0.0.1"),
port=8080,
apiKey=wmill.get_variable("u/admin/capskip_key"),
)
try:
result = solver.recaptcha(sitekey=sitekey, url=SITE)
except TimeoutException:
# Let the flow's retry policy decide what happens next.
raise
except NetworkException:
raise RuntimeError("CapSkip is unreachable from this worker")
# Submit immediately. The token is short lived, and the field
# name below is the one the page's own form posts.
posted = requests.post(
SITE,
data={
"username": username,
"g-recaptcha-response": result["code"],
},
timeout=30,
)
return {"status": posted.status_code, "captcha_id": result["captchaId"]}वह आख़िरी script token के बजाय captcha id लौटाता है, और यह जान-बूझकर है। id तब काम आती है जब आप बाद में Windmill का run history पढ़ रहे होते हैं, और token काम नहीं आता: तब तक वह expire हो चुका होता है, और उसे सहेजे गए job नतीजे में डालने का मतलब है उसे अपने logs में डाल देना।
एक साथ कई हल करना
Windmill का worker एक समय पर एक ही script चलाता है और उसे जो मशीन मिली है उसका पूरा इस्तेमाल करता है। इसलिए यहाँ concurrency इस बात पर टिकी है कि आप कितने workers चलाते हैं, इस पर नहीं कि आपका script क्या करता है। इसे पाने के दो तरीक़े हैं, और दोनों साथ भी चल सकते हैं।
- और workers चलाइए। worker group को अलग से स्केल किया जा सकता है, और jobs वही worker उठा लेता है जो ख़ाली हो।
- एक ही script के अंदर batch में हल कीजिए। Python SDK एक असली asynchronous client देता है, इसलिए एक ही job में कई solves एक साथ चल सकते हैं। यह तब करने लायक़ है जब किसी रन को एक नहीं बल्कि दस tokens चाहिए, और यह तरीक़ा यहाँ लिखा गया है: Python के साथ समानांतर में कैप्चा हल करने की गाइड.
अगर नाज़ुक हिस्सा वह साइट है जिसे आप निशाना बना रहे हैं, तो script पर concurrency की सीमा लगा दीजिए। CapSkip ख़ुद मीटर पर नहीं चलता, इसलिए इनमें से ज़्यादा चलाने पर कोई अतिरिक्त ख़र्च नहीं होता, पर जिस साइट को आप ऑटोमेट कर रहे हैं वह ज़रूर ध्यान दे सकती है।
आम errors और उनका मतलब
| आप जो देखते हैं | कारण | फिक्स |
|---|---|---|
| NetworkException, port 8080 पर connection refused | worker उस मशीन पर नहीं है जिससे CapSkip बंधा हुआ है | Server mode पर स्विच करें और host वेरिएबल को सॉल्वर के पते पर सेट करें |
| job के अंदर host एनवायरनमेंट वेरिएबल ख़ाली पढ़ा जाता है | वह worker पर मौजूद तो है, पर उसे कभी आगे जाने की अनुमति नहीं मिली | उसका नाम WHITELIST_ENVS में जोड़ें, या उसे worker group पर सेट करें |
| capskip के import पर ModuleNotFoundError | इस वर्शन के लिए dependency job अभी चला ही नहीं है | script को सेव करके deploy करें, फिर देखें कि dependency job पूरा हुआ या नहीं |
| solve के बीच में ही job मार दिया जाता है | script का timeout उस समय से कम है जितना solve ने लिया | script की runtime settings में Timeout बढ़ाकर 300 सेकंड से ऊपर करें |
| SDK से TimeoutException | solve सचमुच recaptchaTimeout से ज़्यादा लंबा खिंच गया | flow को इसे retry करने दें, और जाँचें कि sitekey और page URL सही हैं |
| रिस्पॉन्स में ERROR_WRONG_USER_KEY | Windmill वेरिएबल ख़ाली है, इसलिए ख़ाली key भेजी गई | वेरिएबल का path जाँचें, workspace वाले prefix समेत |
| Windows पर कोई worker शुरू ही नहीं होता | इस worker के लिए PID namespace की isolation बंद नहीं की गई है | उस worker पर ENABLE_UNSHARE_PID को false कर दें |
| एक वैध token को टारगेट साइट अस्वीकार कर देती है | वह solve वाले step और submit वाले step के बीच expire हो गया | एक ही script में हल करें और submit करें, या बिना किसी इंतज़ार के लगातार आने वाले steps में |
FAQ
क्या मैं Windmill के साथ CapSkip को loopback पते पर ही रख सकता हूँ?
हाँ, अगर worker उसी Windows मशीन पर चलता है जिस पर सॉल्वर है। यही एक ऐसा deployment है जहाँ Local mode बचा रहता है, और इसे जान-बूझकर सेट करना फ़ायदे का है: सॉल्वर वाले box पर एक अलग worker चलाइए, उसे उसका अपना worker tag दीजिए, और कैप्चा वाले scripts को उसी tag पर भेजिए। बाक़ी हर शक्ल में, Windmill के cloud और किसी भी container समेत, Server mode चाहिए, क्योंकि worker कहीं और होता है।
क्या SDK के लिए मुझे requirements फ़ाइल चाहिए?
नहीं। script सेव होते ही Windmill उसके top-level imports पढ़ लेता है, उन्हें PyPI packages से मिलाता है और script के उस वर्शन के लिए एक lockfile बना देता है। import की लाइन ही dependency की घोषणा है। अगर चलने के समय import नाकाम होता है, तो देखने वाली बात यह है कि dependency job पूरा हुआ या नहीं, न कि कोई फ़ाइल ग़ायब है या नहीं।
क्या flow को नतीजे के लिए ख़ुद पोल करना चाहिए?
ऐसे solve के लिए नहीं जो एक ही job में समा जाता है। SDK पहले ही पोल करता है, और वह किसी तय अंतराल पर सोने के बजाय 250 मिलीसेकंड से शुरू करके अंतराल बढ़ाता है, इसलिए sleep वाले steps का हाथ से बनाया loop धीमा भी है और कोड भी ज़्यादा। flow से पोल तभी कीजिए जब आपने जान-बूझकर submit और collect को दो steps में बाँटा हो, और उस हालत में pending रिस्पॉन्स को उसके नाम से जाँचिए। उसकी स्पेलिंग CAPCHA_NOT_READY है, जिसमें एक T ग़ायब है, और उसका मतलब है इंतज़ार जारी रखिए, न कि कुछ ग़लत हो गया। यहाँ मौजूद है CAPCHA_NOT_READY रिस्पॉन्स पर एक पूरा लेख.
Airflow, Dagster या n8n में यही काम करने के मुक़ाबले यह कैसा है?
चारों में सबसे कम कोड Windmill माँगता है, क्योंकि script एक सादा function है और उसकी dependencies import की लाइन से आ जाती हैं। Airflow किसी DAG के अंदर एक task चाहता है और उसमें scheduler का अंतराल भी सोचना पड़ता है, जिसकी बात यहाँ है: Airflow कैप्चा DAG गाइड। Dagster उसी काम को एक asset के रूप में देखता है, जिसका वर्णन यहाँ है: Dagster वॉकथ्रू। n8n किसी code runtime के बजाय एक node graph है, इसलिए n8n गाइड उसके HTTP Request node के इर्द-गिर्द बना है। कनेक्शन का सवाल चारों में एक जैसा ही है।
संक्षेप में
SDK को Windmill script के ऊपर import कीजिए और dependency job को उसे पिन करने दीजिए। सॉल्वर की key किसी Windmill वेरिएबल में रखिए और उसका पता ऐसे worker एनवायरनमेंट वेरिएबल में जिसे WHITELIST_ENVS आगे जाने देता है। कनेक्शन मोड यह पूछकर तय कीजिए कि worker कहाँ चलता है, यह नहीं कि आप कहाँ बैठे हैं: सॉल्वर वाले Windows box पर चलने वाला worker Local mode रख लेता है, और बाक़ी सबको Server mode चाहिए। script का timeout 300 सेकंड से ऊपर सेट कीजिए, flow step पर exponential backoff जोड़िए, और token को उसी job में submit कीजिए जिसने उसे बनाया है।
- ख़ुद Python SDK के बारे में यहाँ बताया गया है: Python कैप्चा सॉल्वर पेज.
- checkbox वाले challenge के बारे में यहाँ बताया गया है: reCAPTCHA v2 सॉल्वर पेज.
- Node.js, PHP और C# में इसके समकक्ष calls यहाँ सूचीबद्ध हैं: CAPTCHA solving SDK पेज.
इसे हर पाँच मिनट पर शेड्यूल करने से पहले एक बात तौल लीजिए: CapSkip एक लोकल captcha सॉल्वर है, जो उसी हार्डवेयर पर चलता है जो पहले से आपका है, इसलिए लगातार चलने वाले flow और कभी-कभार चलने वाले flow, दोनों की लागत बिल्कुल एक जैसी है।
