Windows Task Scheduler जॉब में कैप्चा कैसे हल करें

Windows Task Scheduler से कैप्चा हल करने के लिए, एक ऐसा टास्क रजिस्टर करें जो आपके virtual environment का python.exe सीधे चलाए, script को उसकी अपनी रीट्राई और HTTP timeouts दें, और बाकी जो कुछ भी गड़बड़ हो, उसे टास्क की समय सीमा पकड़ने दें। यही पूरा Task Scheduler कैप्चा सेटअप है, और CapSkip उसी PC पर चलता है, इसलिए न कोई क्यू होस्ट करनी है और न प्रति-हल कोई लागत। डिफ़ॉल्ट सेटिंग्स चार जाल छिपाए रखती हैं, और यह गाइड हर एक को कवर करती है। वर्किंग फ़ोल्डर System32 होता है, इसलिए relative फ़ाइल नाम फेल हो जाता है। एक अटका हुआ रन चुपचाप पूरे शेड्यूल को खत्म कर देता है। जब शेड्यूलर टास्क को kill करता है, तब cmd wrapper असली Python प्रोसेस को चलता हुआ छोड़ देता है। और restart-on-failure सेटिंग किसी ऐसी script को दोबारा शुरू नहीं करती जो error के साथ exit होती है।
आपको क्या चाहिए
- किसी Windows PC पर चल रहा CapSkip, जिसमें reCAPTCHA सॉल्वर चालू हो। CapSkip एक डेस्कटॉप ऐप है, इसलिए टास्क तभी काम करता है जब वह खुला हो।
- Python 3.10 या उसके बाद का वर्ज़न, जो CapSkip SDK का न्यूनतम वर्ज़न है। नीचे का सब कुछ Windows 11 पर Python 3.14.0 और capskip 1.3.0 के साथ, Windows PowerShell 5.1 में चलाया गया।
- एक सामान्य PowerShell प्रॉम्प्ट, elevated वाला नहीं। टेस्टिंग में टास्क को रजिस्टर करने और चलाने के लिए administrator प्रॉम्प्ट की ज़रूरत नहीं पड़ी।
- सॉल्वर के लिए एक पता। Local mode में CapSkip केवल उसी डिवाइस के लिए 127.0.0.1 पर जवाब देता है। Server mode में वह आपके नेटवर्क पते या पब्लिक IP पर सुनता है, ताकि किसी दूसरी मशीन पर चल रहा टास्क API के ज़रिए उसे कॉल कर सके। दोनों यहाँ सेट किए जाते हैं: कनेक्शन सेटिंग्स.
चरण 1: जॉब फ़ोल्डर और Python environment बनाएँ
जॉब को एक ऐसे फ़ोल्डर में रखें जिसका अपना virtual environment हो। टास्क उस environment के अंदर वाला python.exe उसके पूरे path से चलाएगा, इसलिए उसे आपके PATH से कुछ नहीं चाहिए और किसी activate किए हुए shell की भी ज़रूरत नहीं।
# PowerShell: make a job folder with its own Python environment mkdir C:\CapSkipJobs cd C:\CapSkipJobs python -m venv venv .\venv\Scripts\python.exe -m pip install capskip==1.3.0 requests
SDK का वर्ज़न pin किया गया है क्योंकि चरण 2 वाला timeout patch SDK के अंदरूनी हिस्सों तक पहुँचता है। नए वर्ज़न पर जाने से पहले इसे फिर से टेस्ट करें। Task Manager हर रन के लिए दो python.exe प्रोसेस दिखाता है: environment का launcher और उसका शुरू किया हुआ असली interpreter। यह सामान्य है।
चरण 2: सेटिंग्स पढ़ें, फ़ाइल में log करें और SDK को timeout दें
चरण 2 से 4 का कोड एक ही फ़ाइल, C:\CapSkipJobs\job.py, में दिखाए गए क्रम में सेव करें। पूरी फ़ाइल इस गाइड के अंत में दोबारा दी गई है। जॉब अपनी सेटिंग्स environment variables से पढ़ती है और उन्हें खुद क्लाइंट को पास करती है, क्योंकि SDK उन्हें अपने आप नहीं पढ़ता। उसमें डिफ़ॉल्ट भी हैं, इसलिए फ़ाइल बिना किसी सेटअप के चल जाती है। CAPSKIP_API_KEY की ज़रूरत तभी है जब आप CapSkip में API key validation चालू करें।
import functools
import logging
import os
import re
import sys
import time
import types
import requests
import capskip.api
from capskip import ApiException, CapSkip, NetworkException, TimeoutException
HOST = os.getenv('CAPSKIP_HOST', '127.0.0.1')
PORT = int(os.getenv('CAPSKIP_PORT', '8080'))
API_KEY = os.getenv('CAPSKIP_API_KEY', 'YOUR_API_KEY')
SITE = os.getenv('SITE_URL', 'https://example.com').rstrip('/')
USER = os.getenv('SITE_USER', 'YOUR_USERNAME')
PASSWORD = os.getenv('SITE_PASSWORD', 'YOUR_PASSWORD')
LOG_FILE = os.getenv('JOB_LOG', r'C:\CapSkipJobs\job.log')
ATTEMPTS = int(os.getenv('JOB_ATTEMPTS', '3'))
PAUSE = int(os.getenv('JOB_PAUSE', '30'))Task Scheduler में environment variables के लिए कोई फ़ील्ड नहीं है, और जो variable आपने सिर्फ़ किसी टर्मिनल में सेट किया, वह टास्क को नहीं दिखता। setx से सेट किया गया user variable उस तक पहुँचता है, टास्क रजिस्टर होने के बाद सेट किया गया variable भी: अगला रन उसे उठा लेता है, sign out करने की ज़रूरत नहीं। ये वैल्यू आपके अकाउंट की registry में सादे टेक्स्ट के रूप में रहती हैं, यानी लगभग उतनी ही निजी जितनी एक .env फ़ाइल।
# Task Scheduler has no environment field. User variables work: # the next run of the task sees them, with no sign-out needed. setx SITE_URL "https://example.com" setx SITE_USER "YOUR_USERNAME" setx SITE_PASSWORD "YOUR_PASSWORD"
आगे log फ़ाइल और SDK के लिए timeout आते हैं। Task Scheduler किसी प्रोग्राम का output फेंक देता है, इसलिए print() कहीं नहीं दिखता, और जॉब इसके बजाय एक absolute path पर log फ़ाइल लिखती है। वर्किंग फ़ोल्डर सेट न हो, तो टास्क C:\Windows\System32 में चलता है, जहाँ job.log जैसा relative फ़ाइल नाम PermissionError के साथ फेल हो जाता है।
os.makedirs(os.path.dirname(LOG_FILE), exist_ok=True)
logging.basicConfig(filename=LOG_FILE, level=logging.INFO,
format='%(asctime)s pid=%(process)d %(levelname)s %(message)s')
log = logging.getLogger('job')
# The SDK sets no HTTP timeouts, so give every CapSkip call one: connect 5 s, read 30 s.
# This reaches into the SDK, so the guard fails loudly if a newer version changes it.
if not hasattr(capskip.api, 'requests'):
log.error('capskip.api.requests is gone: check the timeout patch')
sys.exit(1)
capskip.api.requests = types.SimpleNamespace(
RequestException=requests.RequestException,
post=functools.partial(requests.post, timeout=(5, 30)),
get=functools.partial(requests.get, timeout=(5, 30)))आख़िरी लाइनें वह समस्या ठीक करती हैं जो शेड्यूलर नहीं कर सकता। CapSkip Python SDK कोई HTTP timeout सेट नहीं करता, इसलिए अगर CapSkip कनेक्शन स्वीकार कर ले और फिर कभी जवाब न दे, तो कॉल हमेशा इंतज़ार करती रहती है, और recaptchaTimeout सेटिंग सिर्फ़ दो पोल के बीच जाँची जाती है। patch हर SDK कॉल को 5 सेकंड का connect timeout और 30 सेकंड का read timeout देता है। तब जिस submit को कभी जवाब नहीं मिलता, वह 30 सेकंड बाद NetworkException के रूप में फेल होता है। जिस नतीजे के पोल को कभी जवाब नहीं मिलता, वह recaptchaTimeout खत्म होने के कुछ ही देर बाद TimeoutException के रूप में खत्म होता है: इसके डिफ़ॉल्ट 300 के साथ 333 सेकंड। patch के ऊपर वाला guard, अगर नए SDK में वह attribute न रहे, तो जॉब को log में एक लाइन लिखकर रोक देता है, ताकि वह बिना timeouts के चलती न रहे। APScheduler में कैप्चा हल करने की गाइड यही patch ज़्यादा विस्तार से समझाती है।
चरण 3: फॉर्म लाएँ, उसे हल करें और पोस्ट करें
जॉब लॉगिन पेज को requests Session में लोड करती है, उससे CSRF token और sitekey पढ़ती है, और CapSkip से एक token माँगती है। एक Session वह cookie रखती है जो CSRF token के साथ आती है। पोस्ट Referer भी भेजता है, क्योंकि Django जैसे कुछ frameworks HTTPS पर बिना Referer वाली फॉर्म पोस्ट ठुकरा देते हैं। paths और फ़ील्ड नामों की जगह अपनी साइट वाले इस्तेमाल करें।
def fetch_login(session):
"""Load the login page: the session cookie, the CSRF token and the sitekey."""
page = session.get(SITE + '/login', timeout=(5, 30))
if page.status_code != 200:
raise RuntimeError('login page returned HTTP %d' % page.status_code)
csrf = re.search(r'name="csrfmiddlewaretoken" value="([^"]+)"', page.text)
key = re.search(r'data-sitekey="([^"]+)"', page.text)
if not csrf or not key:
raise RuntimeError('login page has no CSRF token or no sitekey')
return csrf.group(1), key.group(1)
def solve(sitekey):
solver = CapSkip(apiKey=API_KEY, host=HOST, port=PORT)
return solver.recaptcha(sitekey=sitekey, url=SITE + '/login')['code']solver.recaptcha एक ऐसा नतीजा लौटाता है जिसका code फ़ील्ड ही token है। नीचे का पोस्ट उसे बाकी फॉर्म फ़ील्ड के बगल में g-recaptcha-response फ़ील्ड में रखता है। फेल हुआ लॉगिन अक्सर status 200 और दोबारा वही लॉगिन फॉर्म लौटाता है, इसलिए जॉब ऐसे पेज को भी अस्वीकृति मानती है जिसमें अब भी password फ़ील्ड हो। उस जाँच को अपनी साइट के हिसाब से बदलें, क्योंकि ठुकराए गए लॉगिन पर सफलता रिपोर्ट करने वाली जॉब, फेल होने वाली जॉब से बुरी है।
def submit(session, csrf, token):
"""Post the login form, the way the browser would, Referer included."""
resp = session.post(SITE + '/login', timeout=(5, 30),
headers={'Referer': SITE + '/login'},
data={'csrfmiddlewaretoken': csrf, 'username': USER,
'password': PASSWORD, 'g-recaptcha-response': token})
# Many sites answer a failed login with 200 and the form again.
if resp.status_code != 200 or 'type="password"' in resp.text:
raise RuntimeError('login rejected: HTTP %d %s' % (resp.status_code, resp.text[:120]))
log.info('login accepted: %s', resp.text[:40])दूसरे कैप्चा टाइप के लिए, मेल खाता SDK method कॉल करें और पोस्ट में फॉर्म फ़ील्ड का नाम बदल दें। Python में reCAPTCHA v2 हल करने की गाइड एक सादी script में वही कॉल कवर करती है।
चरण 4: script में रीट्राई करें और एक code के साथ exit करें
शेड्यूल की गई जॉब को नाकामी की योजना चाहिए, और Task Scheduler की अपनी restart सेटिंग ऐसी योजना नहीं है। RestartCount और RestartInterval ने टास्क को सिर्फ़ तब दोबारा शुरू किया जब वह शुरू होने में ही फेल हुआ: जो प्रोग्राम मौजूद ही नहीं था, वह करीब एक और दो मिनट बाद फिर शुरू किया गया। जो script code 1 या 3 के साथ exit हुई, या uncaught exception से मर गई, वह एक बार चली और कभी दोबारा शुरू नहीं की गई। इसलिए रीट्राई script के अंदर ही रहती है।
def run():
session = requests.Session() # a fresh session means a fresh CSRF token
csrf, sitekey = fetch_login(session)
log.info('solving reCAPTCHA v2, sitekey %s', sitekey)
submit(session, csrf, solve(sitekey))
def is_retryable(exc):
if isinstance(exc, (NetworkException, TimeoutException, requests.RequestException)):
return True
return isinstance(exc, ApiException) and 'UNSOLVABLE' in str(exc)चार नाकामियाँ दोबारा कोशिश के लायक हैं: CapSkip का जवाब न देना, TimeoutException, ERROR_CAPTCHA_UNSOLVABLE (CapSkip यह टास्क हल नहीं कर सका, और नई कोशिश में हल हो सकता है), और साइट का खुद जवाब न देना। बाकी सब कुछ, जैसे ठुकराया गया लॉगिन या CSRF token के बिना पेज, घातक है, क्योंकि रीट्राई उसी को दोहराएगी।
def main():
log.info('start, CapSkip at %s:%s, site %s', HOST, PORT, SITE)
for attempt in range(1, ATTEMPTS + 1):
try:
run()
log.info('done, exit 0')
return 0
except Exception as exc:
retry = is_retryable(exc)
log.warning('attempt %d/%d failed, %s: %s: %s', attempt, ATTEMPTS,
'retryable' if retry else 'fatal', type(exc).__name__, exc,
exc_info=not retry)
if not retry:
return 1
if attempt < ATTEMPTS:
time.sleep(PAUSE)
log.error('giving up after %d attempts, exit 3', ATTEMPTS)
return 3
if __name__ == '__main__':
sys.exit(main())जॉब 30 सेकंड के अंतर पर ज़्यादा से ज़्यादा तीन कोशिशें करती है, और फिर एक ऐसे code के साथ exit होती है जिसे Task Scheduler Last Run Result के रूप में दिखाता है।
| Exit कोड | अर्थ |
|---|---|
| 0 | लॉगिन सफल रहा |
| 1 | ऐसी नाकामी जिसे रीट्राई ठीक नहीं कर सकती; log में traceback है |
| 3 | हर कोशिश ऐसी नाकामी से टकराई जो अपने आप ठीक हो सकती है |
Code 2 जानबूझकर छोड़ा गया है। Python खुद 2 के साथ exit होता है जब वह script फ़ाइल खोल नहीं पाता, इसलिए Last Run Result में 2 अस्पष्ट होता।
वही लूप उस CapSkip को भी संभालता है जो अभी खुला नहीं है। CapSkip बंद हो, तो एक कोशिश करीब 2 सेकंड में फेल हो जाती है। एक टेस्ट में, जहाँ सॉल्वर और साइट दोनों देर से चालू हुए, पहली कोशिश फेल हुई और दूसरी, 30 सेकंड बाद, सफल रही। अगर आप चाहते हैं कि जॉब तय समय पर शुरू होने के बजाय sign in करते ही शुरू हो, तो आपके अपने अकाउंट के लिए log-on trigger administrator अधिकारों के बिना रजिस्टर हो जाता है और देरी (delay) भी स्वीकार करता है; एक मिनट की देरी ठीक से रजिस्टर हुई।
चरण 5: टास्क रजिस्टर करें
यह PowerShell script टास्क को मौजूदा यूज़र के लिए रजिस्टर करती है। इसमें किसी चीज़ को elevated प्रॉम्प्ट की ज़रूरत नहीं है।
$python = 'C:\CapSkipJobs\venv\Scripts\python.exe'
$jobDir = 'C:\CapSkipJobs'
$action = New-ScheduledTaskAction -Execute $python `
-Argument ('"{0}\job.py"' -f $jobDir) -WorkingDirectory $jobDir
$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(1) `
-RepetitionInterval (New-TimeSpan -Hours 1)
$settings = New-ScheduledTaskSettingsSet `
-ExecutionTimeLimit (New-TimeSpan -Minutes 30) `
-MultipleInstances IgnoreNew `
-StartWhenAvailable -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
Register-ScheduledTask -TaskName 'CapSkip login job' -Action $action `
-Trigger $trigger -Settings $settings -Forceहर हिस्से की अपनी एक वजह है।
- action environment के python.exe को सीधे चलाता है, script को quotes में दिए absolute path के रूप में और जॉब फ़ोल्डर को वर्किंग फ़ोल्डर के रूप में देकर। उसके आगे cmd /c या कोई .bat फ़ाइल न लगाएँ। समय सीमा असल में क्या रोकती है, इस पर लिखा सेक्शन बताता है क्यों।
- trigger अभी से एक मिनट बाद चलता है और फिर हर घंटे, बिना किसी समाप्ति तारीख़ के।
- समय सीमा 30 मिनट है। डिफ़ॉल्ट 72 घंटे है, यानी तीन दिन। किसी रन को ज़्यादा से ज़्यादा करीब 18 मिनट लग सकते हैं: तीन कोशिशें, जिनमें से हर एक चुप पड़े CapSkip का 333 सेकंड तक इंतज़ार करती है, और दो 30 सेकंड के विराम। तीस मिनट इसके लिए गुंजाइश छोड़ते हैं और फिर भी अटके हुए रन को उसी सुबह मुक्त कर देते हैं।
- कई instances के लिए IgnoreNew पहले से ही डिफ़ॉल्ट है। इसे इसलिए लिख दिया गया है कि यह चुनाव दिखाई दे।
- डिफ़ॉल्ट रूप से टास्क बैटरी पावर पर शुरू नहीं होता और जब PC बैटरी पर आ जाता है तब रुक जाता है। AllowStartIfOnBatteries और DontStopIfGoingOnBatteries दोनों को बंद कर देते हैं, जो लैपटॉप पर मायने रखता है। StartWhenAvailable शेड्यूलर को वह रन, जो छूट गया हो (जैसे PC के sleep में रहने के दौरान), जितनी जल्दी हो सके शुरू करने देता है। हर ऑप्शन का दस्तावेज़ Microsoft के इस पेज पर है: New-ScheduledTaskSettingsSet रेफ़रेंस.
- टास्क आपके ही यूज़र के रूप में चलता है, सिर्फ़ तब जब आप sign in हों। यह CapSkip के लिए ठीक बैठता है, जो आपके सेशन में चलने वाला एक डेस्कटॉप ऐप है।
एक और चुनाव python.exe या pythonw.exe का है। इन टेस्ट में इस्तेमाल हुए Windows 11 PC पर python.exe ने कोई दिखने वाली विंडो नहीं खोली, सिर्फ़ एक छिपा हुआ console host। pythonw.exe में कोई console होता ही नहीं, इसलिए उसके sys.stdout और sys.stderr None हैं: print() कुछ नहीं करता, sys.stderr.write AttributeError उठाता है, और uncaught exception कहीं कोई निशान नहीं छोड़ता। जॉब फ़ाइल में log करती है, इसलिए वह दोनों के साथ काम करती है। pythonw.exe पर तभी जाएँ जब आपके PC पर कोई console विंडो दिखाई दे।
चरण 6: इसे चलाएँ और नतीजा पढ़ें
नीचे का block टास्क को हाथ से शुरू करता है, तब तक इंतज़ार करता है जब तक वह चलना बंद न कर दे, फिर दिखाता है कि वह कैसे खत्म हुआ और जॉब ने क्या log किया।
# Run the task now instead of waiting for the trigger
Start-ScheduledTask -TaskName 'CapSkip login job'
Start-Sleep -Seconds 2
# Wait until it is no longer running
while ((Get-ScheduledTask -TaskName 'CapSkip login job').State -eq 'Running') {
Start-Sleep -Seconds 2
}
# How did it end, and what did the job write?
Get-ScheduledTaskInfo -TaskName 'CapSkip login job' |
Select-Object LastRunTime, LastTaskResult, NextRunTime
Get-Content C:\CapSkipJobs\job.log -Tail 6Last Run Result एक संख्या होती है। आपको जो संख्याएँ मिलेंगी, वे ये हैं।
| LastTaskResult | हेक्स | अर्थ |
|---|---|---|
| 0 | 0x0 | जॉब 0 के साथ exit हुई |
| 1 | 0x1 | जॉब 1 के साथ exit हुई, या uncaught exception से मर गई |
| 3 | 0x3 | जॉब अपनी कोशिशों के बाद 3 के साथ exit हुई |
| 267009 | 0x41301 | टास्क अभी चल रहा है |
| 267011 | 0x41303 | टास्क अभी तक चला ही नहीं |
| 267014 | 0x41306 | शेड्यूलर ने उसे रोक दिया, समय सीमा पर या Stop-ScheduledTask से |
| 2147946720 | 0x800710E0 | एक रन अब भी चल रहा था, इसलिए trigger नज़रअंदाज़ कर दिया गया |
| 2147942402 | 0x80070002 | प्रोग्राम का path मौजूद नहीं है |
एक अटका हुआ रन पूरे शेड्यूल को क्यों रोक देता है
डिफ़ॉल्ट सेटिंग्स इसी नाकामी को सबसे बुरा बना देती हैं। कई instances की डिफ़ॉल्ट नीति IgnoreNew है, इसलिए पिछला रन चलते रहने के दौरान fire होने वाला trigger छोड़ दिया जाता है। डिफ़ॉल्ट समय सीमा 72 घंटे है। दोनों मिलकर इसका मतलब यह है कि कभी खत्म न होने वाला एक रन तीन दिन तक शेड्यूल को रोके रखता है।
एक टेस्ट में, जॉब को timeout patch के बिना ऐसे CapSkip के सामने चलाया गया जो कनेक्शन स्वीकार करता था और कभी जवाब नहीं देता था, हर मिनट एक trigger के साथ। देखे गए पाँच मिनट तक टास्क Running state में रहा। जॉब के log में एक शुरुआती लाइन थी और उसके बाद कुछ नहीं। इस बीच LastRunTime हर मिनट आगे बढ़ता रहा और LastTaskResult बदलकर 0x800710E0 हो गया, यानी उस trigger का code जो इसलिए नज़रअंदाज़ हुआ कि एक instance अभी चल रहा था। टास्क ज़िंदा दिखता था, और संख्या अटके हुए रन की ओर नहीं, बल्कि छोड़े गए trigger की ओर इशारा करती थी। Task Scheduler का अपना history log डिफ़ॉल्ट रूप से बंद रहता है, इसलिए आपकी जॉब का log ही आपके पास इकलौता रिकॉर्ड होगा।
तीन परतें इसे रोकती हैं, और हर परत वह पकड़ती है जो बाकी चूक जाती हैं। SDK timeout अटकी हुई CapSkip कॉल को 30 से 333 सेकंड में खत्म कर देता है। रीट्राई लूप उसे एक और कोशिश या साफ़-सुथरे exit 3 में बदल देता है। समय सीमा उस अटकन के लिए आख़िरी सुरक्षा है जिसके बारे में आपने सोचा ही नहीं। तीनों के होने पर, जब CapSkip ने submit का कभी जवाब नहीं दिया, तब जॉब करीब 152 सेकंड बाद exit 3 के साथ खत्म हुई: तीन 30 सेकंड के timeouts और दो विराम। जब उसने नतीजे के पोल का कभी जवाब नहीं दिया, तब हर कोशिश में 333 सेकंड तक लगते हैं, जिसे 30 मिनट की सीमा अब भी कवर करती है।
समय सीमा असल में क्या रोकती है
समय सीमा सटीक है। एक मिनट की सीमा ने रन को 60.2 सेकंड पर kill कर दिया, और Task Scheduler ने 30 सेकंड की सीमा स्वीकार की और 30.5 पर kill किया। वह क्या kill करता है, यह action की बनावट पर निर्भर करता है।
| ऐक्शन | सीमा पर kill हुआ | बाद में भी चलता रहा |
|---|---|---|
| environment का python.exe | launcher और interpreter | कुछ नहीं |
| cmd /c python job.py | cmd.exe | launcher और interpreter, जो अब भी CapSkip से अपना कनेक्शन थामे हैं |
| python चलाने वाली .bat फ़ाइल | cmd.exe | launcher और interpreter, जो अब भी CapSkip से अपना कनेक्शन थामे हैं |
यही बँटवारा Stop-ScheduledTask के साथ भी दिखा। कैप्चा जॉब के लिए यह मायने रखता है, क्योंकि पीछे छूटा orphan प्रोसेस CapSkip से अपना कनेक्शन खुला रखता है। wrappers के छोटे जाल भी हैं। redirect वाली cmd /c लाइन को बाहर की ओर quotes की एक अतिरिक्त जोड़ी चाहिए, वरना जॉब 1 के साथ exit होती है और कुछ log नहीं होता, और redirect का target absolute path होना चाहिए। जिस .bat फ़ाइल की आख़िरी लाइन echo है, वह Python ने जो भी लौटाया हो, 0 रिपोर्ट करती है, इसलिए उसे exit /b %ERRORLEVEL% से खत्म करें। और Unregister-ScheduledTask चल रहे रन को नहीं रोकता, इसलिए पहले Stop-ScheduledTask कॉल करें।
पूरी जॉब
यहाँ job.py के छहों blocks एक साथ हैं। इसे C:\CapSkipJobs\job.py के रूप में सेव करें, यही वह path है जिस पर टास्क इशारा करता है।
import functools
import logging
import os
import re
import sys
import time
import types
import requests
import capskip.api
from capskip import ApiException, CapSkip, NetworkException, TimeoutException
HOST = os.getenv('CAPSKIP_HOST', '127.0.0.1')
PORT = int(os.getenv('CAPSKIP_PORT', '8080'))
API_KEY = os.getenv('CAPSKIP_API_KEY', 'YOUR_API_KEY')
SITE = os.getenv('SITE_URL', 'https://example.com').rstrip('/')
USER = os.getenv('SITE_USER', 'YOUR_USERNAME')
PASSWORD = os.getenv('SITE_PASSWORD', 'YOUR_PASSWORD')
LOG_FILE = os.getenv('JOB_LOG', r'C:\CapSkipJobs\job.log')
ATTEMPTS = int(os.getenv('JOB_ATTEMPTS', '3'))
PAUSE = int(os.getenv('JOB_PAUSE', '30'))
os.makedirs(os.path.dirname(LOG_FILE), exist_ok=True)
logging.basicConfig(filename=LOG_FILE, level=logging.INFO,
format='%(asctime)s pid=%(process)d %(levelname)s %(message)s')
log = logging.getLogger('job')
# The SDK sets no HTTP timeouts, so give every CapSkip call one: connect 5 s, read 30 s.
# This reaches into the SDK, so the guard fails loudly if a newer version changes it.
if not hasattr(capskip.api, 'requests'):
log.error('capskip.api.requests is gone: check the timeout patch')
sys.exit(1)
capskip.api.requests = types.SimpleNamespace(
RequestException=requests.RequestException,
post=functools.partial(requests.post, timeout=(5, 30)),
get=functools.partial(requests.get, timeout=(5, 30)))
def fetch_login(session):
"""Load the login page: the session cookie, the CSRF token and the sitekey."""
page = session.get(SITE + '/login', timeout=(5, 30))
if page.status_code != 200:
raise RuntimeError('login page returned HTTP %d' % page.status_code)
csrf = re.search(r'name="csrfmiddlewaretoken" value="([^"]+)"', page.text)
key = re.search(r'data-sitekey="([^"]+)"', page.text)
if not csrf or not key:
raise RuntimeError('login page has no CSRF token or no sitekey')
return csrf.group(1), key.group(1)
def solve(sitekey):
solver = CapSkip(apiKey=API_KEY, host=HOST, port=PORT)
return solver.recaptcha(sitekey=sitekey, url=SITE + '/login')['code']
def submit(session, csrf, token):
"""Post the login form, the way the browser would, Referer included."""
resp = session.post(SITE + '/login', timeout=(5, 30),
headers={'Referer': SITE + '/login'},
data={'csrfmiddlewaretoken': csrf, 'username': USER,
'password': PASSWORD, 'g-recaptcha-response': token})
# Many sites answer a failed login with 200 and the form again.
if resp.status_code != 200 or 'type="password"' in resp.text:
raise RuntimeError('login rejected: HTTP %d %s' % (resp.status_code, resp.text[:120]))
log.info('login accepted: %s', resp.text[:40])
def run():
session = requests.Session() # a fresh session means a fresh CSRF token
csrf, sitekey = fetch_login(session)
log.info('solving reCAPTCHA v2, sitekey %s', sitekey)
submit(session, csrf, solve(sitekey))
def is_retryable(exc):
if isinstance(exc, (NetworkException, TimeoutException, requests.RequestException)):
return True
return isinstance(exc, ApiException) and 'UNSOLVABLE' in str(exc)
def main():
log.info('start, CapSkip at %s:%s, site %s', HOST, PORT, SITE)
for attempt in range(1, ATTEMPTS + 1):
try:
run()
log.info('done, exit 0')
return 0
except Exception as exc:
retry = is_retryable(exc)
log.warning('attempt %d/%d failed, %s: %s: %s', attempt, ATTEMPTS,
'retryable' if retry else 'fatal', type(exc).__name__, exc,
exc_info=not retry)
if not retry:
return 1
if attempt < ATTEMPTS:
time.sleep(PAUSE)
log.error('giving up after %d attempts, exit 3', ATTEMPTS)
return 3
if __name__ == '__main__':
sys.exit(main())CapSkip को किसी दूसरी मशीन पर चलाना
टास्क और सॉल्वर का एक ही PC पर होना ज़रूरी नहीं। कनेक्शन सेटिंग्स में CapSkip को Server mode पर कर दें, तो वह आपके नेटवर्क पते या पब्लिक IP पर सुनता है। फिर जॉब को उसी पते पर पॉइंट करें। नीचे की setx लाइन अगले रन पर असर में आती है।
# Only if CapSkip runs in Server mode on another Windows PC setx CAPSKIP_HOST "192.168.1.50" # Only if API key validation is switched on in CapSkip setx CAPSKIP_API_KEY "YOUR_API_KEY"
अगर रास्ता इंटरनेट से होकर जाता है तो स्टैटिक पब्लिक IP इस्तेमाल करें, और API key validation चालू करें, CAPSKIP_API_KEY में भी वही key रखें। Server mode शुरू होते ही CapSkip अपना पोर्ट हर पते के लिए खोल देता है, इसलिए पहुँच को सिर्फ़ अपेक्षित मशीनों तक सीमित रखने के लिए बाकी पतों के लिए Windows Firewall में एक block नियम जोड़ें। सॉल्वर अब भी आपकी अपनी Windows मशीन है, और हल अब भी बिना मीटर वाले हैं।
Task Scheduler में कैप्चा की आम errors
| आप जो देखते हैं | कारण | फिक्स |
|---|---|---|
| टास्क की state घंटों तक Running रहती है | एक कॉल अटकी है और 72 घंटे की डिफ़ॉल्ट सीमा बहुत दूर है | SDK timeout जोड़ें और मिनटों की समय सीमा रखें |
| हर trigger पर 2147946720 (0x800710E0) | एक रन के अब भी चलते रहने के दौरान triggers नज़रअंदाज़ हो रहे हैं | जॉब के log की आख़िरी लाइन पढ़ें, फिर अटकन ठीक करें |
| 2147942402 (0x80070002) | प्रोग्राम का path मौजूद नहीं है | environment के python.exe का पूरा path इस्तेमाल करें |
| Last Run Result 2 और खाली log | Python job.py खोल नहीं सका, आमतौर पर वर्किंग फ़ोल्डर के बिना relative path की वजह से | action को script का absolute path और एक वर्किंग फ़ोल्डर दें |
| job.log पर PermissionError | relative फ़ाइल नाम C:\Windows\System32 के अंदर resolve हुआ | absolute paths इस्तेमाल करें |
| हर रन पर Exit 3 | CapSkip चल नहीं रहा, host या port गलत है, या हर हल फेल हुआ | log की पहली लाइन और कनेक्शन सेटिंग्स में दिया पता जाँचें |
| 267014 (0x41306) | शेड्यूलर ने रन को समय सीमा पर रोक दिया | देखें कि log कहाँ खत्म होता है, और उस कॉल को ठीक करें |
| जॉब के अंदर सेटिंग्स गायब हैं | variables सिर्फ़ किसी टर्मिनल में सेट किए गए थे | उन्हें setx से सेट करें, या फ़ाइल में डिफ़ॉल्ट रखें |
| रोकने के बाद दो python.exe प्रोसेस बचे रहते हैं | action cmd /c या कोई .bat फ़ाइल है | action के रूप में python.exe चलाएँ |
FAQ
क्या मेरे sign out होने पर भी टास्क चल सकता है?
ऐसा टास्क रजिस्टर करना जो sign in हों या न हों, दोनों हालत में चले और जिसके लिए कोई password सहेजा न गया हो, टेस्ट में elevated न किए गए PowerShell प्रॉम्प्ट से Access is denied (0x80070005) के साथ फेल हुआ। सहेजे हुए password के साथ रजिस्टर करना टेस्ट नहीं किया गया, और न ही standard यूज़र अकाउंट। दोनों सूरतों में जवाब देने के लिए CapSkip का खुला होना ज़रूरी है, और वह डेस्कटॉप ऐप है, इसलिए आम सेटअप ऐसा PC है जिस पर आप sign in रहते हैं। अगर आपको सॉल्वर किसी ऐसी मशीन पर चाहिए जिसे कोई इस्तेमाल नहीं करता, तो वहाँ CapSkip को Server mode में चलाएँ और जॉब को उसी पर पॉइंट करें।
क्या अगला trigger नज़रअंदाज़ होने के बजाय अटके हुए रन की जगह ले सकता है?
हाँ, StopExisting नीति के साथ। New-ScheduledTaskSettingsSet सिर्फ़ Parallel, Queue और IgnoreNew देता है, इसलिए वैल्यू को settings object पर हाथ से सेट करें:
# New-ScheduledTaskSettingsSet cannot set this policy, so set it on the # $settings object from Step 5 by hand, then update the task. $settings.CimInstanceProperties['MultipleInstances'].Value = 3 Set-ScheduledTask -TaskName 'CapSkip login job' -Settings $settings
एक्सपोर्ट किए गए टास्क में तब StopExisting दिखता है। action के रूप में python.exe होने पर, हर trigger ने पुराना रन kill किया और नया शुरू कर दिया। action के रूप में cmd /c होने पर, हर kill के बाद एक orphan पीछे बच गया। Parallel ने हर मिनट एक नया रन शुरू किया जबकि अटका हुआ रन बना रहा, और Queue उसके पीछे इंतज़ार करता रहा।
क्या यह किसी compiled C# प्रोग्राम के लिए काम करता है?
शेड्यूलर की तरफ़ सब वैसा ही है। action को अपनी exe पर पॉइंट करें, वर्किंग फ़ोल्डर सेट करें, समय सीमा रखें और log का absolute path इस्तेमाल करें। रीट्राई और exit codes फिर आपके अपने कोड में आते हैं। अगर प्रोग्राम को तय समय पर शुरू होने के बजाय पूरे दिन चलना है, तो एक .NET Worker Service में कैप्चा हल करने की गाइड इसी ढाँचे को कवर करती है।
Task Scheduler के बजाय क्यू कब बेहतर विकल्प है?
जब काम कोई दूसरा कोड बनाता है, या जब काम का हर हिस्सा crash के बाद भी बचा रहना चाहिए और अलग से रीट्राई होना चाहिए। Task Scheduler तय समय पर एक नया प्रोसेस शुरू करता है और अलग-अलग जॉब के बारे में कुछ नहीं जानता। Huey जैसी क्यू हर टास्क को एक फ़ाइल में रखती है और उसे रीट्राई करती है, और Huey टास्क में कैप्चा हल करने की गाइड Windows पर एक ही SQLite फ़ाइल के साथ चलने वाला सेटअप दिखाती है।
संक्षेप में
Task Scheduler कैप्चा जॉब को सिर्फ़ हल से ज़्यादा चाहिए। environment का python.exe सीधे चलाएँ, वर्किंग फ़ोल्डर, log के absolute path और script के absolute path के साथ। SDK की HTTP कॉल को timeout दें और script के अंदर रीट्राई करें, क्योंकि restart सेटिंग exit codes को नज़रअंदाज़ करती है। execution की समय सीमा मिनटों में रखें, क्योंकि 72 घंटे का डिफ़ॉल्ट एक अटके हुए रन को बाद के हर trigger को रोकने देता है।
- कैप्चा के वे सभी टाइप जिन्हें Python पैकेज संभालता है: Python कैप्चा सॉल्वर पेज.
- checkbox वाला reCAPTCHA कैसे काम करता है और सॉल्वर क्या-क्या कवर करता है: reCAPTCHA v2 सॉल्वर पेज.
हर घंटे चलने वाली जॉब महीने में करीब 720 हल करती है। अपनी खुद की Windows मशीन पर चल रहे लोकल captcha सॉल्वर से इन्हें चलाने का मतलब है कि यह संख्या कभी बिल में नहीं बदलती।
