Celery टास्क में कैप्चा कैसे हल करें (Python क्यू)

celery captcha - How to Solve CAPTCHAs in a Celery Task (Python Queue)

Celery कैप्चा टास्क का एक नियम बाकी सब कुछ तय कर देता है: उसे हर कोशिश में शुरू से हल करना होगा। Celery कम से कम एक बार डिलीवरी देता है, यानी कोई टास्क दो बार चल सकता है, और किसी CapSkip नतीजे को एक ही बार पढ़ा जा सकता है। अगर आपने कोई कैप्चा id सहेजकर रीट्राई के बाद वहीं से आगे बढ़ने की कोशिश की, तो आपको कुछ नहीं मिलेगा। हल कीजिए, टोकन इस्तेमाल कीजिए और काम खत्म कीजिए, सब कुछ एक ही टास्क बॉडी के भीतर।

आपको क्या चाहिए

  • किसी ब्रोकर के साथ Celery 5, यानी Redis या RabbitMQ। ब्रोकर का चुनाव आगे चलकर एक सेटिंग बदल देता है।
  • किसी Windows मशीन पर चल रहा CapSkip, और वर्कर इमेज में इंस्टॉल किया गया Python क्लाइंट।
  • लगभग हर असली डिप्लॉयमेंट में Server mode। वर्कर आम तौर पर Linux कंटेनर में चलते हैं, और सॉल्वर नहीं चलता।
  • sitekey और पेज URL, जो टास्क में पहले से जड़े होने के बजाय टास्क आर्गुमेंट के रूप में दिए जाएँ।
# pip install capskip
pip install -U celery[redis] capskip

चरण 1: टास्क

पूरा हल एक ही कॉल है। SDK सबमिट करता है, पोल करता है और टोकन लौटाता है, इसलिए चरणों के बीच ढोने के लिए कोई id नहीं बचती और सहेजने के लिए कुछ होता ही नहीं।

# pip install capskip
import os
from celery import Celery
from capskip import CapSkip, NetworkException

app = Celery("solves", broker="redis://redis:6379/0")

# CAPSKIP_HOST is the solver machine. Loopback only works if the
# worker runs on the same Windows box as CapSkip.
solver = CapSkip(
    host=os.environ.get("CAPSKIP_HOST", "127.0.0.1"),
    port=int(os.environ.get("CAPSKIP_PORT", "8080")),
    apiKey=os.environ.get("CAPSKIP_API_KEY", "capskip"),
)

@app.task(
    bind=True,
    autoretry_for=(NetworkException,),
    retry_backoff=True,
    max_retries=3,
    soft_time_limit=330,
    time_limit=360,
)
def solve_and_submit(self, sitekey, page_url):
    result = solver.recaptcha(sitekey=sitekey, url=page_url)
    return submit_form(page_url, result["code"])   # token, used here

ध्यान दीजिए कि इसमें क्या नहीं है। रिटर्न वैल्यू में कोई कैप्चा id नहीं, टोकन सबमिट करने के लिए कोई दूसरा टास्क नहीं, बाद के लिए सहेजा गया कोई नतीजा नहीं। टोकन उसी टास्क में इस्तेमाल होता है जिसने उसे बनाया। इसकी वजह सफ़ाई नहीं बल्कि समय है, और इसे नीचे रीट्राई वाले हिस्से में समझाया गया है।

वह कॉल reCAPTCHA v2 के लिए है। CapSkip जिन दूसरे प्रकारों को सपोर्ट करता है, उनका ढाँचा भी वही है: invisible या enterprise को 1 पर भेजिए, या version को किसी action के साथ v3 पर, या इसके बजाय turnstile या geetest कॉल कीजिए। पूरा विवरण यहाँ है: Python कैप्चा सॉल्वर पेज.

चरण 2: दो समय सीमाएँ, और उन्हें कहाँ रखना है

Celery में डिफ़ॉल्ट रूप से कोई समय सीमा नहीं होती, किसी भी सेटिंग पर नहीं। ऐसे टास्क के लिए यह गलत डिफ़ॉल्ट है जो किसी नेटवर्क सर्विस का इंतज़ार करता है, क्योंकि अटका हुआ हल एक वर्कर स्लॉट को हमेशा के लिए घेर लेता है।

दोनों सेट कीजिए, और उन्हें SDK की अपनी अनुमत सीमा से ऊपर रखिए। CapSkip किसी reCAPTCHA, Turnstile या GeeTest हल को तीन सौ सेकंड देता है और किसी इमेज कैप्चा को एक सौ बीस, और दोनों को क्लाइंट पर कॉन्फ़िगर किया जा सकता है। अगर सॉफ़्ट सीमा पहले लग गई, तो Celery आपके टास्क के भीतर SoftTimeLimitExceeded उठाता है और आप SDK का अपना TimeoutException खो देते हैं, जो ज़्यादा उपयोगी संकेत है क्योंकि वह बताता है कि सॉल्वर तक पहुँच तो बनी थी, बस काम पूरा नहीं हुआ।

कौन-सी सेटिंगकिसी हल के लिए सुझाया गया मानक्यों
soft_time_limit330 सेकंडसॉल्वर की अपनी अधिकतम सीमा से तीस सेकंड ऊपर, ताकि SDK पहले रिपोर्ट करे
time_limit360 सेकंडआखिरी सुरक्षा। इस बिंदु पर वर्कर प्रोसेस को खत्म कर देता है
क्लाइंट पर recaptchaTimeout300 सेकंड, यानी डिफ़ॉल्टअगर आप इंतज़ार करने के बजाय जल्दी विफल होना पसंद करें तो इसे घटा दीजिए
क्लाइंट पर defaultTimeout120 सेकंड, यानी डिफ़ॉल्टसिर्फ़ इमेज कैप्चा के लिए। इनमें शायद ही कभी कई सेकंड लगते हैं, मिनट तो दूर की बात है

अगर आप इमेज कैप्चा और reCAPTCHA दोनों एक ही वर्कर से चलाते हैं, तो एक ही टास्क में ऊँची जोड़ी लगाने के बजाय उन्हें अलग-अलग सीमाओं वाले अलग टास्क दीजिए। जो काम आम तौर पर एक सेकंड में निपट जाता है, उस पर तीन सौ सेकंड की सीमा असली गड़बड़ियों को पाँच मिनट तक छिपा देती है।

चरण 3: वह रीट्राई नियम जो खास तौर पर हल करने से जुड़ा है

Celery की स्वचालित रीट्राई उस सॉल्वर के लिए बिल्कुल सही हैं जो दोबारा शुरू हो रहा है, और उस टोकन के लिए बिल्कुल गलत जो आपके पास पहले से है। यह फ़र्क साफ़-साफ़ समझ लेना ज़रूरी है।

retry_backoff को True पर सेट करने पर पहली रीट्राई एक सेकंड रुकती है, फिर दो, फिर चार, फिर आठ, और jitter डिफ़ॉल्ट रूप से चालू रहता है इसलिए असली देरी उस अधिकतम तक कोई भी बेतरतीब मान होती है। ऊपरी सीमा retry_backoff_max है, जिसका डिफ़ॉल्ट छह सौ सेकंड है। अब इसकी तुलना reCAPTCHA टोकन से कीजिए, जो लगभग दो मिनट तक ही चलता है।

यानी जिस टास्क ने सफलतापूर्वक हल किया, टोकन सहेजा, सबमिट पर विफल हुआ और फिर रीट्राई हुआ, वह टोकन की उम्र से कई गुना लंबी देरी के बाद दोबारा चल सकता है। वह ऐसे टोकन पर विफल होगा जो बनते समय पूरी तरह वैध था, और लॉग इसका दोष लक्षित साइट पर डालेगा। यह विफलता इस गाइड में समझाई गई है: reCAPTCHA token की समय समाप्ति.

इसका फिक्स ऊपर दिया गया टास्क का रूप है: रीट्राई से पहले नहीं, उसके भीतर हल कीजिए।

autoretry_for को सिर्फ़ उन अपवादों तक सीमित रखिए जो ट्रांसपोर्ट की समस्या बताते हैं। NetworkException का मतलब है कि CapSkip तक पहुँच नहीं बनी, जिसे दोबारा आज़माना सही है। ApiException और ValidationException का मतलब है कि रिक्वेस्ट ही गलत थी और दोबारा भी गलत ही रहेगी। TimeoutException पर फ़ैसला हालात देखकर लेना होता है, और आम तौर पर तीन के बजाय एक ही रीट्राई ठीक रहती है।

चरण 4: acks_late, और क्यों किसी नतीजे को सिर्फ़ एक बार पढ़ा जा सकता है

डिफ़ॉल्ट रूप से Celery किसी संदेश को चलाने से ठीक पहले स्वीकार कर लेता है, इसलिए टास्क के बीच में मरने वाला वर्कर वह काम गँवा देता है। task_acks_late चालू करने पर स्वीकृति टास्क पूरा होने के बाद होती है, इसलिए क्रैश हुए वर्कर का काम दोबारा भेजा जाता है और फिर से चलता है। जिस काम पर कहीं और पैसा खर्च होता है, उसके लिए आम तौर पर आप यही चाहते हैं, और यही एक बार ही पढ़े जाने वाले नियम को अहम बना देता है।

किसी CapSkip नतीजे को एक ही बार पढ़ा जा सकता है। अगर पहली कोशिश ने चैलेंज सबमिट किया, टोकन पढ़ा और फिर स्वीकृति देने से पहले क्रैश हो गई, तो दोबारा भेजी गई कोशिश उस id को फिर से नहीं पढ़ सकती। उसे दोबारा सबमिट करना ही होगा। ऊपर दिया गया टास्क ठीक यही करता है, क्योंकि वह कोशिशों के बीच कोई स्थिति नहीं रखता, और दोबारा हल करने की कीमत आपके अपने हार्डवेयर के कुछ सेकंड हैं, कोई दूसरा शुल्क नहीं।

# Redelivery is safe here because the task resolves rather
# than resuming. Pair it with reject_on_worker_lost so a
# killed worker requeues instead of dropping the job.
app.conf.task_acks_late = True
app.conf.task_reject_on_worker_lost = True

# Long tasks and a prefetch of 4 means idle workers sit on
# queued jobs. Drop it to 1 for solve queues.
app.conf.worker_prefetch_multiplier = 1

यही आखिरी वाली बात ज़्यादातर हल करने वाली क्यू में छिपा हुआ परफ़ॉर्मेंस बग है। डिफ़ॉल्ट prefetch मल्टीप्लायर चार है, इसलिए हर वर्कर प्रोसेस पहले ही चार संदेश आरक्षित कर लेता है। मिलीसेकंड में निपटने वाले टास्क के साथ यह फ़ायदे का सौदा है। लेकिन जो टास्क किसी हल का इंतज़ार करते हैं, उनके साथ उन चार में से तीन ऐसे काम के पीछे आरक्षित पड़े रहते हैं जो पोलिंग के अलावा कुछ नहीं कर रहा, जबकि दूसरे वर्कर के पास करने को कुछ नहीं होता।

चरण 5: वह ब्रोकर सेटिंग जो हल को दोहरा देती है

अगर आपका ब्रोकर Redis है, तो एक और संख्या है। Redis में मूल रूप से स्वीकृति की व्यवस्था नहीं है, इसलिए Celery उसकी नकल एक visibility timeout से करता है: यानी वह इतने सेकंड इंतज़ार करता है कि कोई वर्कर टास्क स्वीकार कर ले, वरना संदेश किसी दूसरे वर्कर को भेज देता है। इसका डिफ़ॉल्ट एक घंटा है, और यह अपनी अलग सेटिंग होने के बजाय broker_transport_options के भीतर रहता है।

एक घंटा तीन सौ सेकंड के हल से आराम से ऊपर है, इसलिए डिफ़ॉल्ट सुरक्षित है। दिक्कत तब शुरू होती है जब कोई इसे इसलिए घटा देता है ताकि विफल काम जल्दी उबर जाएँ, क्योंकि Celery के अपने दस्तावेज़ चेतावनी देते हैं कि जिस टास्क के चलने का समय visibility timeout से ज़्यादा हो, वह बार-बार, एक लूप में चलता रहता है। तब हर धीमा हल कम से कम दो बार चलता है।

# Keep this above your hard time limit, not near it.
# 3600 is the default and it is fine. If you must lower it,
# stay well clear of the 360 second time_limit above.
app.conf.broker_transport_options = {"visibility_timeout": 3600}

RabbitMQ मूल रूप से ही स्वीकृति देता है और उसमें ऐसी कोई सेटिंग नहीं है, यही एक वजह है कि धीमे टास्क से भरी क्यू के लिए उसे प्राथमिकता दी जाए।

चरण 6: सॉल्वर को वहाँ चलाना जहाँ वर्कर पहुँच सकें

Celery वर्कर आम तौर पर किसी क्लस्टर पर Linux कंटेनर में चलते हैं। CapSkip Windows पर चलता है। इसलिए व्यवहार में वर्कर और सॉल्वर अलग-अलग मशीनों पर होते हैं, और loopback इसका जवाब नहीं है।

CapSkip में इसके लिए दो कनेक्शन मोड हैं। Local, 127.0.0.1 पर बाइंड होता है और सिर्फ़ उसी डिवाइस को सेवा देता है। Server, आपके नेटवर्क पते या पब्लिक IP पर बाइंड होता है, ताकि कोई दूसरी मशीन, कंटेनर होस्ट या होस्टेड प्लेटफ़ॉर्म उसी Windows मशीन तक API के ज़रिए पहुँच सके। ये दोनों यहाँ मिलते हैं: कनेक्शन सेटिंग्स, और Server mode सिर्फ़ यह बदलता है कि सॉल्वर किस address पर सुनता है। hardware अब भी आपका ही है और हल अब भी बिना मीटर वाला है।

यही आखिरी बात लगातार चलती रहने वाली क्यू को चलाने लायक बनाती है।

वर्कर कहाँ चलते हैंकौन-सा कनेक्शन मोड
CapSkip वाली उसी Windows मशीन परLocal mode, होस्ट 127.0.0.1 ही रहता है
Docker में या आपके नेटवर्क की किसी दूसरी मशीन परसॉल्वर के LAN पते के साथ Server mode
किसी मैनेज्ड प्लेटफ़ॉर्म या क्लाउड क्लस्टर परस्टैटिक पब्लिक IP और एक फ़ायरवॉल नियम के साथ Server mode

होस्ट और key को कोड के बजाय एनवायरनमेंट से पढ़िए। Python क्लाइंट CAPSKIP_HOST, CAPSKIP_PORT या CAPSKIP_API_KEY को अपने आप नहीं पढ़ता, इसीलिए चरण 1 वाला टास्क इन्हें पढ़कर क्लाइंट को पास करता है। फिर वर्कर कंटेनर को इन वेरिएबल के अलावा और कुछ नहीं चाहिए।

एक साथ कई हल करना

इसके दो तरीके हैं, और वे अलग-अलग तरह के काम पर फ़बते हैं। हर कैप्चा के लिए एक टास्क, जिसमें समानांतरता वर्कर कंकरेंसी से आती है, सामान्य जवाब है और ऊपर दी गई prefetch वाली बात इसी के बारे में है। जो बैच एक साथ आता है, उसके लिए Python के क्लाइंट में असली async इम्प्लीमेंटेशन है, इसलिए एक ही टास्क अपने भीतर पूरा बैच इकट्ठा कर सकता है।

import asyncio
import os
from capskip import AsyncCapSkip

# AsyncCapSkip in Python is a real async client, not an alias.
async def solve_batch(pairs):
    solver = AsyncCapSkip(
        host=os.environ.get("CAPSKIP_HOST", "127.0.0.1"), port=8080)
    return await asyncio.gather(*[
        solver.recaptcha(sitekey=k, url=u) for k, u in pairs
    ])

@app.task(soft_time_limit=330, time_limit=360)
def solve_many(pairs):
    return [r["code"] for r in asyncio.run(solve_batch(pairs))]

इसे किसी दूसरी भाषा में कॉपी करने से पहले यह जान लेना ज़रूरी है: Node और .NET क्लाइंट भी एक क्लास का नाम AsyncCapSkip रखते हैं, लेकिन वहाँ वह दूसरा इम्प्लीमेंटेशन नहीं, सिर्फ़ एक उपनाम है। Python ही वह जगह है जहाँ इसका कोई मतलब है। पूरी तुलना इस गाइड में है: Python में समानांतर रूप से कैप्चा हल करना.

आम errors और उनका मतलब

आप जो देखते हैंकारणफिक्स
हर task पर NetworkExceptionCapSkip loopback से bound है और worker कहीं और हैServer mode पर स्विच कीजिए और CAPSKIP_HOST को सॉल्वर के पते पर सेट कीजिए
TimeoutException के बजाय SoftTimeLimitExceeded मिलता हैसॉफ़्ट सीमा क्लाइंट की अपनी अधिकतम सीमा से नीचे हैsoft_time_limit को 300 से ऊपर बढ़ाइए, या recaptchaTimeout घटाइए
किसी धीमे हल पर वही काम दो बार चलता हैRedis का visibility timeout टास्क से छोटा हैइसे हार्ड समय सीमा से काफ़ी ऊपर बढ़ा दीजिए
कोई रीट्राई एक्सपायर हो चुके टोकन पर विफल हो जाती हैटोकन रीट्राई के भीतर नहीं, उससे पहले हल हुआ थाहल को टास्क बॉडी के भीतर ले जाइए, जैसा ऊपर बताया गया है
उसी कैप्चा id को पढ़ने पर कुछ नहीं मिलताकिसी CapSkip नतीजे को एक ही बार पढ़ा जा सकता हैकिसी id को कोशिशों के बीच कभी मत सहेजिए। इसके बजाय दोबारा हल कीजिए
किसी ApiException में ERROR_WRONG_USER_KEYवर्कर एनवायरनमेंट में CAPSKIP_API_KEY सेट नहीं हैइसे वर्कर एनवायरनमेंट में सेट कीजिए और वर्कर को दोबारा शुरू कीजिए
क्यू भरती जा रही है और वर्कर खाली बैठे हैंprefetch लंबे टास्क को लंबे टास्क के पीछे आरक्षित कर रहा हैहल वाली क्यू पर worker_prefetch_multiplier को 1 कर दीजिए
वर्कर खत्म होने पर काम गायब हो जाते हैंदेर से स्वीकृति बंद हैtask_acks_late और task_reject_on_worker_lost चालू कीजिए

key से जुड़ी त्रुटियों के बारे में अलग से पढ़ना फ़ायदेमंद है, क्योंकि एक ही रिस्पॉन्स गायब key और गलत key, दोनों को कवर करता है: ERROR_WRONG_USER_KEY कैसे ठीक करें.

FAQ

क्या हल और सबमिट, दो अलग टास्क होने चाहिए?

नहीं। यह बँटवारा लुभावना लगता है, क्योंकि दोनों हिस्से अलग-अलग वजहों से विफल होते हैं और मॉनिटर में एक chain ज़्यादा सुथरी दिखती है। लेकिन reCAPTCHA टोकन लगभग दो मिनट चलता है और क्यू में पड़ा टास्क इससे ज़्यादा देर तक रुक सकता है, इसलिए दूसरा हिस्सा अक्सर ऐसे टोकन पर चलता है जो पहले ही एक्सपायर हो चुका होता है। दोनों को साथ रखिए और पूरी चीज़ को एक इकाई की तरह रीट्राई होने दीजिए। दोबारा हल करने पर आपकी अपनी मशीन के कुछ सेकंड ही खर्च होते हैं।

क्या कैप्चा हल के साथ acks_late सुरक्षित है?

हाँ, बशर्ते टास्क वहीं से आगे बढ़ने के बजाय दोबारा हल करे। देर से स्वीकृति का मतलब है कि क्रैश हुए वर्कर का काम दोबारा भेजा जाता है और दूसरी बार चलता है, इसलिए टास्क का दोहराया जाना सुरक्षित होना चाहिए। जो टास्क हर बार नया चैलेंज सबमिट करता है, वह सुरक्षित है। जो टास्क कोई id सहेजकर उसे दोबारा पढ़ने की कोशिश करता है, वह नहीं, क्योंकि किसी नतीजे को एक ही बार पढ़ा जा सकता है। इस गाइड वाला रूप ही सुरक्षित रूप है।

अगर सॉल्वर Windows पर चलता है तो क्या वर्कर Linux पर चल सकते हैं?

हाँ, और यही सामान्य व्यवस्था है। वर्कर को सिर्फ़ एक HTTP एंडपॉइंट तक पहुँचना होता है, इसलिए वह नेटवर्क पर कहीं भी Linux कंटेनर हो सकता है जबकि सॉल्वर किसी Windows मशीन पर Server mode में चलता रहे। CAPSKIP_HOST को उसी मशीन की ओर इंगित कीजिए। जिस मायने में फ़र्क पड़ता है, उसमें हल का कुछ भी मीटर्ड या रिमोट नहीं होता: हार्डवेयर अब भी आपका ही है।

Airflow में हल चलाने से यह कैसे अलग है?

Airflow चरणों का एक ग्राफ़ शेड्यूल करता है और उनके बीच डेटा भेजता है, इसलिए वहाँ दिलचस्प सवाल यह है कि टोकन कौन-सी सीमा पार करता है। Celery एक क्यू है, इसलिए दिलचस्प सवाल यह है कि जब वही संदेश दो बार डिलीवर हो तो क्या होता है। हल करने वाली कॉल एक जैसी ही है। सीमा वाला सवाल पूरे विस्तार से यहाँ समझाया गया है: Airflow DAG गाइड.

संक्षेप में

हल और टोकन इस्तेमाल करने वाली चीज़, दोनों को एक ही टास्क में रखिए। 330 की सॉफ़्ट सीमा और 360 की हार्ड सीमा तय कीजिए ताकि क्लाइंट का अपना टाइमआउट पहले रिपोर्ट करे। रीट्राई सिर्फ़ NetworkException पर कीजिए, और रीट्राई को वहीं से आगे बढ़ने के बजाय दोबारा हल करने दीजिए, क्योंकि कोई बैकऑफ़ किसी टोकन से कहीं ज़्यादा लंबा खिंच सकता है और किसी नतीजे को एक ही बार पढ़ा जा सकता है। देर से स्वीकृति चालू कीजिए, prefetch मल्टीप्लायर घटाकर 1 कीजिए, और Redis का visibility timeout हार्ड सीमा से काफ़ी ऊपर रखिए। जब भी वर्कर सॉल्वर की अपनी मशीन पर न हों, CapSkip को Server mode में चलाइए।

क्यू का आकार तय करने से पहले एक आखिरी बात तौलने लायक है: CapSkip एक कैप्चा सॉल्वर है जो आपके पास पहले से मौजूद हार्डवेयर पर चलता है, इसलिए उस पर टूट पड़ने वाले सौ वर्कर और उन्हीं कामों को धीरे-धीरे निपटाने वाला एक वर्कर, दोनों पर एक जैसा कुछ भी खर्च नहीं होता।