asyncio.run errors के बिना Jupyter में कैप्चा कैसे हल करें

jupyter notebook captcha - How to Solve CAPTCHAs in Jupyter Without asyncio.run Errors

Jupyter नोटबुक में कैप्चा हल करने के लिए, capskip पैकेज को %pip से इंस्टॉल करें, ताकि वह कर्नेल के अपने Python में पहुँचे। फिर किसी सेल से CapSkip को वैसे ही कॉल करें जैसे script में करते, या उसके AsyncCapSkip क्लाइंट को सीधे सेल के टॉप लेवल पर await करें। जो आपको नहीं करना है, वह है async कॉल को asyncio.run() में लपेटना। कर्नेल पहले से एक event loop चला रहा होता है, इसलिए asyncio.run एक RuntimeError उठाता है, और इसी वजह से Playwright का sync API शुरू होने से मना कर देता है। Jupyter नोटबुक में कैप्चा के काम के दो और जाल हैं, जो सिर्फ़ यहीं मिलते हैं: स्टॉप बटन हमेशा किसी solve को रोक नहीं पाता, और Windows पर पुराना ipykernel, Playwright को ब्राउज़र लॉन्च नहीं करने देता। यह गाइड इंस्टॉल, दोनों क्लाइंट, ब्राउज़र और रिमोट कर्नेल कवर करती है, और इसे ipykernel 7.4 और 7.3 पर टेस्ट किया गया है।

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

  • किसी Windows मशीन पर चल रहा CapSkip। उदाहरण reCAPTCHA v2 हल करते हैं, और SDK जो भी दूसरे टाइप संभालता है, वे सब सेल में इसी तरह काम करते हैं।
  • Python 3.10 या उसके बाद का वर्ज़न और capskip पैकेज का मौजूदा वर्ज़न (यह लेख लिखते समय 1.3.0), साथ में पेज के लिए requests और Beautiful Soup। Playwright सिर्फ़ तब, जब आपको ब्राउज़र चाहिए।
  • किसी भी रूप में Jupyter: Notebook 7, JupyterLab या VS Code का नोटबुक एडिटर। तीनों आपके सेल ipykernel पर चलाते हैं, और नीचे बताया गया हर व्यवहार वहीं से आता है।
  • सॉल्वर के लिए एक पता। Local मोड केवल उसी डिवाइस के लिए 127.0.0.1 पर जवाब देता है; Server मोड आपके नेटवर्क पते या पब्लिक IP पर सुनता है ताकि किसी दूसरी मशीन पर चल रहा कर्नेल API के ज़रिए उसे कॉल कर सके। दोनों कनेक्शन सेटिंग्स में मिलते हैं, और Server मोड की ज़रूरत कब पड़ती है, यह आख़िरी सेक्शन में बताया गया है।

स्टेप 1: %pip से कर्नेल के Python में इंस्टॉल करें

इंस्टॉल किसी नोटबुक सेल से चलाएँ, प्रतिशत चिह्न के साथ:

# pip install capskip requests beautifulsoup4, run inside the notebook:
%pip install capskip requests beautifulsoup4

प्रतिशत चिह्न मायने रखता है। IPython का %pip मैजिक pip को कर्नेल के अपने interpreter, यानी sys.executable, से चलाता है, इसलिए पैकेज वहीं पहुँचता है जहाँ से आपके सेल import करते हैं। विस्मयादिबोधक चिह्न (!) इसके बजाय लाइन को shell के हवाले कर देता है, और shell वह pip चलाता है जो PATH पर सबसे पहले मिले। एक से ज़्यादा Python वाली मशीन पर ये दो अलग-अलग इंस्टॉलेशन होते हैं। एक टेस्ट में, कर्नेल में %pip ने capskip 1.3.0 बताया, जबकि shell के pip ने, जो एक global Python 3.14 से था, पुराना 1.0.1 बताया, जिसमें नए कैप्चा टाइप नहीं हैं। आगे बढ़ने से पहले जाँच लें कि आपको कौन सा Python और कौन सा वर्ज़न मिला:

import sys
import capskip

print(sys.executable)        # the Python this kernel runs on
print(capskip.__version__)   # 1.3.0 or later

अगर अपग्रेड करते समय कोई पैकेज पहले से import हो चुका था, तो कर्नेल रीस्टार्ट करें ताकि नया वर्ज़न लोड हो। IPython भी %pip के बाद यही याद दिलाता है।

स्टेप 2: sync क्लाइंट से सेल में हल करें

नोटबुक में sync क्लाइंट को कुछ खास नहीं चाहिए। requests.Session से पेज fetch करें, widget ढूँढें, और हल करें:

import requests
from bs4 import BeautifulSoup
from capskip import CapSkip

PAGE_URL = "https://example.com/login"

# pollingInterval caps the wait between polls. At 1 instead of the
# default 5, the stop button takes effect within about a second.
solver = CapSkip(host="127.0.0.1", port=8080, pollingInterval=1)

session = requests.Session()
page = session.get(PAGE_URL, timeout=30)
soup = BeautifulSoup(page.text, "html.parser")
widget = soup.select_one(".g-recaptcha[data-sitekey]")
form = widget.find_parent("form")

token = solver.recaptcha(sitekey=widget["data-sitekey"], url=PAGE_URL)["code"]
len(token)

फिर अगले सेल में उसे पोस्ट करें, फॉर्म के अपने फ़ील्ड और उसी session के साथ:

from urllib.parse import urljoin

def form_fields(form):
    """The fields a browser would send: hidden, text and checked boxes."""
    fields = {}
    for tag in form.select("input[name]"):
        kind = (tag.get("type") or "text").lower()
        if kind in ("submit", "button", "image", "file", "reset"):
            continue
        if kind in ("checkbox", "radio") and not tag.has_attr("checked"):
            continue
        fields[tag["name"]] = tag.get("value", "on" if kind in ("checkbox", "radio") else "")
    return fields

data = form_fields(form)
data["username"] = "YOUR_USERNAME"
data["g-recaptcha-response"] = token
res = session.post(urljoin(PAGE_URL, form.get("action") or PAGE_URL), data=data,
                   headers={"Referer": PAGE_URL}, timeout=30)
res.status_code

session पेज की cookies को पोस्ट तक ले जाता है, और यही hidden CSRF फ़ील्ड को आपकी विज़िट से जोड़ता है। Referer header उस header की जगह लेता है जो ब्राउज़र भेजता है: requests कोई Referer नहीं भेजता, और कुछ frameworks, जिनमें Django भी है, बिना Referer या Origin वाली HTTPS फॉर्म पोस्ट को मना कर देते हैं। reCAPTCHA token एक ही सबमिट और करीब दो मिनट के लिए वैध रहता है, इसलिए पोस्ट वाला सेल solve वाले सेल के तुरंत बाद चलाएँ, बाद में लौटकर नहीं।

स्टॉप बटन ही वह जगह है जहाँ नोटबुक script से अलग है। इंटरप्ट करने पर आपके सेल में KeyboardInterrupt उठता है, लेकिन Windows कर्नेल पर यह तभी पहुँचता है जब मौजूदा कॉल लौटती है। SDK किसी solve का ज़्यादातर समय दो पोल के बीच sleep में बिताता है, इसलिए सेल तब रुकता है जब वह sleep खत्म होता है: डिफ़ॉल्ट pollingInterval पर पाँच सेकंड तक, और ऊपर वाली सेटिंग पर करीब एक सेकंड। traceback अजीब दिखता है: पहले एक NetworkException, और फिर उसे संभालते समय उठा KeyboardInterrupt। वह NetworkException बस SDK का यह संकेत है कि जवाब अभी तैयार नहीं है।

बुरा मामला वह है जब CapSkip connection तो स्वीकार कर ले, पर कभी जवाब न दे। sync क्लाइंट अपनी HTTP कॉल पर कोई timeout सेट नहीं करता, इसलिए Windows पर सेल एक ऐसे socket read में अटका रहता है जहाँ तक कोई इंटरप्ट नहीं पहुँचता, और recaptchaTimeout से भी मदद नहीं मिलती, क्योंकि SDK उसे सिर्फ़ दो पोल के बीच जाँचता है। कर्नेल रीस्टार्ट करने से सेल छूट जाता है, लेकिन हर variable खो जाता है। CapSkip बंद करने से भी सेल छूट जाता है और variables बने रहते हैं, क्योंकि connection टूटने से read खत्म हो जाता है और आपका पहले भेजा इंटरप्ट तुरंत पहुँच जाता है। इंटरप्ट सिर्फ़ आपके सेल को रोकता है, CapSkip को कभी नहीं: SDK में किसी job को रद्द करने की कोई कॉल नहीं है, इसलिए छोड़ा गया solve चलता रहता है और उसका token कभी पढ़ा नहीं जाता।

स्टेप 3: सेल में async: टॉप लेवल पर await करें, asyncio.run कभी नहीं

script से async कोड चलाने का आम तरीका नोटबुक में फेल हो जाता है:

import asyncio
from capskip import AsyncCapSkip

asolver = AsyncCapSkip(host="127.0.0.1", port=8080, pollingInterval=1)

asyncio.run(asolver.recaptcha(sitekey=widget["data-sitekey"], url=PAGE_URL))
# RuntimeError: asyncio.run() cannot be called from a running event loop

ipykernel हर सेल को उस event loop के अंदर चलाता है जिसे वह पहले ही शुरू कर चुका है, और asyncio.run एक नया loop शुरू करने पर अड़ जाता है। पुराना तरीका, asyncio.get_event_loop().run_until_complete(), भी RuntimeError: This event loop is already running के साथ फेल होता है। इनमें से कोई भी कॉल CapSkip तक कभी नहीं पहुँचती, इसलिए कोई job सबमिट नहीं होती। आपने जो coroutine पास किया था वह कभी चलता ही नहीं, इसलिए बाद का कोई सेल RuntimeWarning दिखा सकता है कि उसे कभी await नहीं किया गया; इससे कोई नुकसान नहीं होता। इलाज समस्या से ज़्यादा आसान है। IPython आपको सेल के टॉप लेवल पर, किसी भी function के बाहर, await करने देता है:

# No asyncio.run: the kernel's loop runs this await.
from capskip import AsyncCapSkip

asolver = AsyncCapSkip(host="127.0.0.1", port=8080, pollingInterval=1)
result = await asolver.recaptcha(sitekey=widget["data-sitekey"], url=PAGE_URL)
token = result["code"]

इसीलिए नोटबुक के लिए AsyncCapSkip बेहतर क्लाइंट है, और इसकी दो वजहें शैली से आगे की हैं। इंटरप्ट await को फ़ौरन रद्द कर देता है, इसलिए सेल अगले पोल के बाद नहीं, तुरंत खत्म होता है। और इसकी HTTP कॉल httpx से होकर जाती हैं, जो डिफ़ॉल्ट रूप से पाँच सेकंड बाद किसी खामोश connection को छोड़ देता है। तब अगर CapSkip जवाब देना बंद कर दे, तो कॉल अटकने के बजाय एक SDK error के साथ खत्म होती है: अगर कोई पोल अटक जाए तो recaptchaTimeout बीतने के थोड़ी देर बाद TimeoutException, या अगर सबमिट अटक जाए तो पाँच सेकंड बाद NetworkException। स्टॉप बटन पूरे समय काम करता रहता है। उस इंतज़ार को छोटा करने के लिए constructor पर recaptchaTimeout सेट करें; Python SDK में recaptcha() कोई timeout argument नहीं लेता, और देने पर TypeError उठाता है।

एक साथ कई solve के लिए बस एक gather चाहिए। CapSkip डिफ़ॉल्ट रूप से reCAPTCHA को 10 threads पर हल करता है, जो उसकी सेटिंग्स में Max. Threads है, और उनके पीछे queue में लगा task 250 सेकंड तक इंतज़ार करता है, उसके बाद CapSkip उसे फेल कर देता है, इसलिए बैच को सीमित रखें:

import asyncio

# Max. Threads for reCAPTCHA is 10 by default; more than that queues.
limit = asyncio.Semaphore(10)

async def solve(sitekey, url):
    async with limit:
        return (await asolver.recaptcha(sitekey=sitekey, url=url))["code"]

# jobs: (sitekey, url) pairs; here the Step 2 page three times
jobs = [(widget["data-sitekey"], PAGE_URL)] * 3
tokens = await asyncio.gather(*(solve(k, u) for k, u in jobs), return_exceptions=True)

gather सारे tokens आख़िर में एक साथ लौटाता है, इसलिए उन्हें फ़ौरन पोस्ट करें, या asyncio.as_completed पर जाएँ और हर token आते ही पोस्ट करें। return_exceptions=True के साथ, फेल हुआ solve सेल से बाहर error उठाने के बजाय सूची में अपने exception के रूप में लौटता है, इसलिए सिर्फ़ वही entries पोस्ट करें जो स्ट्रिंग हैं। एक बैच के सारे solve एक ही CapSkip मशीन साझा करते हैं, इसलिए मानकर चलें कि हर solve अकेले solve से धीमा चलेगा। क्लाइंट हर पोल के लिए एक नया HTTP क्लाइंट भी बनाता है, जिसमें Windows पर हर बार कर्नेल का करीब सेकंड के दसवें हिस्से जितना CPU समय लगता है, इसलिए बड़ा बैच एक core को व्यस्त रखता है। बड़े बैच के पैटर्न यहाँ मिलेंगे: Python में समानांतर रूप से कैप्चा हल करने की गाइड.

asyncio.run error के पुराने जवाब nest_asyncio की सलाह देते हैं। इसे छोड़ दें। यह प्रोजेक्ट मार्च 2024 में archive हो गया था। Python 3.14 पर इसे लागू करने से कर्नेल टूट जाता है: अगले सेल से ही हर httpx कॉल NoneType object के weak reference वाले TypeError के साथ फेल होती है, AsyncCapSkip भी, जब तक आप रीस्टार्ट न करें। top-level await को किसी patch की ज़रूरत ही नहीं।

स्टेप 4: नोटबुक में ब्राउज़र

Selenium का अपना कोई event loop नहीं है, इसलिए webdriver session सेल में ठीक वैसे ही काम करता है जैसे script में: स्टेप 2 वाले sync क्लाइंट से हल करें और token को पेज में लिख दें। Playwright का sync API चल रहे loop की जाँच करता है और इस संदेश के साथ मना कर देता है: Error: It looks like you are using Playwright Sync API inside the asyncio loop. Please use the Async API instead. इसलिए नोटबुक में आप Playwright को उसके async API से चलाते हैं, टॉप लेवल पर await के साथ:

# pip install playwright into the kernel, then download a browser once:
%pip install playwright
import sys
!"{sys.executable}" -m playwright install chromium
from playwright.async_api import async_playwright

# start() instead of "async with", so the browser outlives this cell.
pw = await async_playwright().start()
browser = await pw.chromium.launch()
page = await browser.new_page()
await page.goto(PAGE_URL)

sitekey = await page.locator(".g-recaptcha").get_attribute("data-sitekey")
token = (await asolver.recaptcha(sitekey=sitekey, url=page.url))["code"]

# The widget's hidden textarea is the field the form submits.
await page.locator("#g-recaptcha-response").evaluate("(el, t) => { el.value = t; }", token)
await page.locator("form button[type=submit]").click()

pw और browser को variables में रखने से आप अगले सेलों से पेज को टटोल सकते हैं, और नोटबुक का असली फ़ायदा यही है। काम पूरा होने पर await browser.close() और await pw.stop() से दोनों को बंद करें। कर्नेल का अपना sys.executable कॉल करने से ब्राउज़र उसी Playwright से मेल खाता है जिसे आपके सेल import करते हैं, उसी वजह से जिस वजह से %pip ऐसा करता है। quotes की वजह से spaces वाला पाथ सही-सलामत रहता है, और ! वाली लाइन सेल में डाउनलोड की प्रगति दिखाती है। ब्राउज़र को खुद चलाने के बारे में और जानकारी यहाँ है: Playwright कैप्चा सॉल्वर गाइड.

Windows पर, Playwright को debug करने से पहले अपना ipykernel वर्ज़न जाँच लें। 7.3 तक, ipykernel वहाँ सेलों को selector event loop पर चलाता था, जो asyncio subprocesses शुरू नहीं कर सकता, इसलिए chromium.launch() बिना किसी संदेश वाले NotImplementedError के साथ फेल हो जाता है। सितंबर 2026 के आख़िर में आए वर्ज़न 7.4.0 ने Windows पर सेलों को Proactor loop पर कर दिया, और वही सेल काम करने लगता है। आपके पास कौन सा वर्ज़न है, यह देखने के लिए ipykernel.__version__ प्रिंट करें। अपग्रेड करने के लिए नोटबुक में %pip install -U ipykernel चलाएँ और कर्नेल रीस्टार्ट करें। अगर आप अपग्रेड नहीं कर सकते, तो worker thread से मदद नहीं मिलती, क्योंकि selector policy पूरे कर्नेल process पर लागू होती है। उसकी जगह ब्राउज़र को Selenium से चलाएँ, जो chromedriver को asyncio के बिना शुरू करता है।

जब कर्नेल आपके PC पर न हो: Server मोड

ऊपर की हर बात यह मानकर चलती है कि कर्नेल उसी मशीन पर चलता है जिस पर CapSkip है, इसलिए 127.0.0.1 उस तक पहुँच जाता है। Jupyter नोटबुक में कैप्चा का यही आम सेटअप है, लेकिन इकलौता नहीं। किसी सर्वर पर JupyterHub से चलने वाला नोटबुक, VS Code में रिमोट कर्नेल या Google Colab जैसा होस्टेड नोटबुक आपके सेल कहीं और चलाता है, और वहाँ 127.0.0.1 का मतलब वही मशीन है। तब पहली ही कॉल NetworkException और refused connection के साथ फेल हो जाती है। कनेक्शन सेटिंग्स में CapSkip को Server मोड पर कर दें, तो वह आपके नेटवर्क पते या पब्लिक IP पर सुनता है, ताकि रिमोट कर्नेल उसी पते को host बनाकर उसी API के ज़रिए उसे कॉल कर सके।

जब रास्ता इंटरनेट से होकर जाए तो स्टैटिक पब्लिक IP इस्तेमाल करें। Server मोड शुरू होते ही CapSkip अपना पोर्ट हर पते के लिए खोल देता है, इसलिए उसे सिर्फ़ अपेक्षित मशीनों तक सीमित रखने के लिए बाकी पतों के लिए Windows Firewall में एक block नियम जोड़ें। यह तब काम करता है जब कर्नेल का पता तय हो, जैसे आपके अपने JupyterHub सर्वर पर। होस्टेड नोटबुक का बाहर जाने वाला पता हर नए session में बदल सकता है, इसलिए वहाँ CapSkip की सेटिंग्स में API key validation चालू करें और key को apiKey के रूप में पास करें। यह अब भी आपका अपना हार्डवेयर है, और अब भी बिना मीटर वाला है। क्लाइंट कोई एनवायरनमेंट वेरिएबल खुद नहीं पढ़ता, इसलिए किसी सेल में CAPSKIP_HOST को os.getenv से पढ़ें और क्लाइंट को पास करें, जैसा पूरा उदाहरण करता है।

पूरा चलने वाला उदाहरण

तीन सेल, शुरू से आख़िर तक async: Jupyter नोटबुक में कैप्चा का सबसे छोटा पूरा वर्कफ़्लो, जो स्टॉप बटन दबाने पर भी टिका रहता है।

# Cell 1: pip install capskip requests beautifulsoup4, then restart the kernel.
%pip install capskip requests beautifulsoup4
# Cell 2: the client and a helper. Run once per kernel.
import os
from urllib.parse import urljoin

import requests
from bs4 import BeautifulSoup
from capskip import AsyncCapSkip, CapSkipError

PAGE_URL = "https://example.com/login"
solver = AsyncCapSkip(
    apiKey=os.getenv("CAPSKIP_API_KEY", "capskip"),
    host=os.getenv("CAPSKIP_HOST", "127.0.0.1"),
    port=int(os.getenv("CAPSKIP_PORT", "8080")),
    pollingInterval=1,
)

def form_fields(form):
    fields = {}
    for tag in form.select("input[name]"):
        kind = (tag.get("type") or "text").lower()
        if kind in ("submit", "button", "image", "file", "reset"):
            continue
        if kind in ("checkbox", "radio") and not tag.has_attr("checked"):
            continue
        fields[tag["name"]] = tag.get("value", "on" if kind in ("checkbox", "radio") else "")
    return fields
# Cell 3: fetch, solve, post. Run it again for a fresh token.
session = requests.Session()
page = session.get(PAGE_URL, timeout=30)
soup = BeautifulSoup(page.text, "html.parser")
widget = soup.select_one(".g-recaptcha[data-sitekey]")
if widget is None:
    raise RuntimeError("No reCAPTCHA widget in the HTML; the page may build it with JavaScript.")
form = widget.find_parent("form")

try:
    result = await solver.recaptcha(sitekey=widget["data-sitekey"], url=PAGE_URL)
except CapSkipError as err:
    raise RuntimeError(f"solve failed: {type(err).__name__}: {err}") from err

data = form_fields(form)
data["username"] = "YOUR_USERNAME"
data["g-recaptcha-response"] = result["code"]
res = session.post(urljoin(PAGE_URL, form.get("action") or PAGE_URL), data=data,
                   headers={"Referer": PAGE_URL}, timeout=30)
print(res.status_code)

दोबारा आप सेल 3 ही चलाते हैं, और हर रन एक नया solve और नया token है, क्योंकि reCAPTCHA token एक ही बार काम करता है। अगर पेज अपना widget JavaScript से बनाता है, तो fetch किए गए HTML में कोई sitekey नहीं होता; उसकी जगह स्टेप 4 वाले Playwright सेल इस्तेमाल करें, या sitekey उस script से पढ़ें जो widget को रेंडर करती है।

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

आप जो देखते हैंकारणफिक्स
RuntimeError: asyncio.run() cannot be called from a running event loopकर्नेल पहले से एक event loop चला रहा हैकॉल को सेल के टॉप लेवल पर await करें
RuntimeError: This event loop is already runningrun_until_complete को कर्नेल के अपने loop पर कॉल किया गयाकॉल को सेल के टॉप लेवल पर await करें
Playwright कहता है कि आप asyncio loop के अंदर Sync API इस्तेमाल कर रहे हैंजब कोई loop चल रहा हो, तब sync API चलने से मना कर देता हैawait के साथ playwright.async_api इस्तेमाल करें; ipykernel 7.4 या उसके बाद के वर्ज़न पर sync कोड को worker thread में चलाना भी काम करता है
Windows पर chromium.launch() से NotImplementedError7.4.0 से पहले का ipykernel सेलों को selector loop पर चलाता है, जो ब्राउज़र process शुरू नहीं कर सकता%pip install -U ipykernel चलाएँ, फिर कर्नेल रीस्टार्ट करें
capskip के लिए ModuleNotFoundError, या आपके इंस्टॉल किए वर्ज़न से पुराना वर्ज़नshell के pip ने कर्नेल वाले Python के बजाय किसी दूसरे Python में इंस्टॉल किया%pip से इंस्टॉल करें और sys.executable जाँचें
स्टॉप बटन कुछ नहीं करता और सेल चलता रहता हैsync क्लाइंट ऐसे CapSkip का इंतज़ार कर रहा है जिसने connection स्वीकार किया पर कभी जवाब नहीं दिया, और क्लाइंट कोई HTTP timeout सेट नहीं करताCapSkip बंद करें या कर्नेल रीस्टार्ट करें, और नोटबुक में AsyncCapSkip को प्राथमिकता दें
Python 3.14 पर हर httpx या AsyncCapSkip कॉल में weak reference वाला TypeErrorकिसी पिछले सेल में nest_asyncio लागू किया गया थाकर्नेल रीस्टार्ट करें और nest_asyncio हटा दें; top-level await को किसी patch की ज़रूरत नहीं
refused connection के साथ NetworkExceptionCapSkip चल नहीं रहा, या कर्नेल किसी दूसरी मशीन पर चलता हैCapSkip शुरू करें, या उसे Server मोड पर करके उसका पता पास करें
ERROR_CAPTCHA_UNSOLVABLE वाला ApiExceptionCapSkip ने solve फेल कर दिया, जैसे खाली thread के इंतज़ार में 250 सेकंड बीतने के बादबैच को Max. Threads के बराबर रखें, प्रॉक्सी जोड़ें, फिर दोबारा कोशिश करें
साइट CapSkip से मिला token ठुकरा देती हैपोस्ट पेज की cookies, उसके hidden फ़ील्ड या Referer के बिना गई, या token दो मिनट से ज़्यादा पुराना थाsolve के तुरंत बाद, उसी requests.Session से form_fields और Referer के साथ पोस्ट करें

FAQ

क्या यह JupyterLab और VS Code नोटबुक में काम करता है?

हाँ। JupyterLab, Notebook 7 और VS Code का नोटबुक एडिटर, तीनों आपके सेल ipykernel को सौंपते हैं, इसलिए top-level await, asyncio.run error और Windows वाला event loop, तीनों में एक जैसा व्यवहार करते हैं। फ़र्क सिर्फ़ इसमें है कि कर्नेल कौन सा Python इस्तेमाल करता है, इसलिए जो भी एडिटर खोलें, उसमें sys.executable जाँच लें।

क्या Google Colab जैसा होस्टेड नोटबुक मेरे PC पर चल रहे सॉल्वर का इस्तेमाल कर सकता है?

हाँ, Server मोड के ज़रिए। होस्टेड नोटबुक अपना कर्नेल provider की मशीनों पर चलाता है, इसलिए वहाँ 127.0.0.1 आपका PC नहीं है। कनेक्शन सेटिंग्स में CapSkip को Server मोड पर करें, PC को स्टैटिक पब्लिक IP दें, API key validation चालू करें, और अपना पता और key क्लाइंट को पास करें। होस्टेड नोटबुक का बाहर जाने वाला पता बदल सकता है, इसलिए अजनबियों को बाहर रखने का काम key करती है; फिर भी Windows Firewall का एक block नियम उन address ranges के लिए पोर्ट बंद कर सकता है जिन्हें आप कभी इस्तेमाल नहीं करते। सॉल्वर आपके अपने हार्डवेयर पर ही रहता है, इसलिए रीरन से भरा लंबा session भी आपका खर्च कभी नहीं बदलता।

क्या asyncio.run को चलाने के लिए मुझे nest_asyncio इस्तेमाल करना चाहिए?

नहीं। यह चल रहे loop को patch करता है ताकि asyncio.run उसके अंदर nest हो सके, और इस error का यह एक आम इलाज हुआ करता था। यह प्रोजेक्ट archive हो चुका है, और Python 3.14 पर यह patch कर्नेल में बाद की हर httpx कॉल को तोड़ देता है, AsyncCapSkip की कॉल समेत। top-level await पहले से वही करता है जो आप asyncio.run से चाहते थे, और loop में कुछ भी नहीं बदलता।

जब मैं किसी सेल को इंटरप्ट करता हूँ, तो solve का क्या होता है?

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

संक्षेप में

Jupyter नोटबुक में कैप्चा हल करना पाँच आदतों पर टिका है। %pip से इंस्टॉल करें, shell से कभी नहीं। sync CapSkip क्लाइंट सेल में वैसे ही काम करता है जैसे script में; pollingInterval को 1 पर सेट करें ताकि इंटरप्ट जल्दी पहुँचें। async के लिए, AsyncCapSkip को सेल के टॉप लेवल पर await करें और उसे कभी asyncio.run में न लपेटें, न ही nest_asyncio का सहारा लें। ब्राउज़र के लिए, Selenium को जैसा है वैसा ही इस्तेमाल करें या Playwright का async API, और Windows पर ipykernel 7.4 या उसके बाद का वर्ज़न चलाएँ। जब कर्नेल किसी दूसरी मशीन पर हो, तो CapSkip को Server मोड पर करें और उसका पता पास करें।

नोटबुक वह जगह है जहाँ स्क्रैपर एक-एक रीरन करके बनता है। अपनी ही मशीन पर चल रहे कैप्चा बायपास के साथ, इनमें से हर रीरन मुफ़्त है।