Python में script टैग के सहारे Friendly Captcha कैसे हल करें

solve friendly captcha in python - How to Solve Friendly Captcha in Python From the Script Tag

Python में Friendly Captcha हल करने के लिए, frc-captcha element से data-sitekey पढ़ें, widget को लोड करने वाला script टैग खोजें, उसी script से पहचानें कि v1 है या v2, और sitekey, पेज URL, वर्ज़न और script का पूरा पता CapSkip के friendly_captcha method को पास करें। बदले में आपको एक token मिलता है, जिसे v1 पर frc-captcha-solution में या v2 पर frc-captcha-response में पोस्ट करना है। असली बात script टैग की है। Friendly Captcha एक ही नाम और एक ही sitekey फ़ॉर्मेट के तहत दो बिल्कुल अलग प्रोटोकॉल देता है, और गलत वाला हल करने पर ऐसा token मिलता है जो सही दिखता है पर चुपचाप ठुकरा दिया जाता है। CapSkip ने Friendly Captcha को वर्ज़न 1.4.0 में जोड़ा। यह गाइड पेज पढ़ना, कॉल, सबमिट, और AsyncCapSkip से एक साथ कई solve चलाना कवर करती है।

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

  • किसी Windows मशीन पर चल रहा CapSkip 1.4.0 या उसके बाद का संस्करण। Friendly Captcha उसी रिलीज़ में जोड़ा गया था, CaptchaFox और Capy Puzzle के साथ।
  • Python 3.10 या उसके बाद का संस्करण, और capskip पैकेज का 1.3.0 या उसके बाद का वर्ज़न, जो friendly_captcha वाली पहली रिलीज़ है। पेज लाने और फॉर्म पोस्ट करने के लिए सैंपल requests भी इस्तेमाल करते हैं।
  • उस पेज का URL जो widget दिखाता है। sitekey, script का पता और फ़ील्ड का नाम, तीनों उसी पेज के HTML से निकलते हैं, और स्टेप 1 दिखाता है कि कहाँ से।
  • सॉल्वर के लिए एक पता। Local मोड केवल उसी डिवाइस के लिए 127.0.0.1 पर जवाब देता है; Server मोड आपके नेटवर्क पते या पब्लिक IP पर सुनता है ताकि किसी दूसरी मशीन पर चल रही script API के ज़रिए उसे कॉल कर सके। दोनों कनेक्शन सेटिंग्स में मिलते हैं, और स्विच कब करना है, यह स्टेप 4 में बताया गया है।
# pip install capskip requests
pip install "capskip>=1.3.0" requests

इन quotes को बनाए रखें। cmd में बिना quotes वाला greater-than चिह्न एक redirect है: pip बिना किसी वर्ज़न जाँच के जो भी capskip मिले उसे इंस्टॉल कर देता है, और अपना आउटपुट 1.3.0 नाम की फ़ाइल में लिख देता है। PowerShell आर्गुमेंट को जस का तस आगे भेज देता है, लेकिन quotes हर shell में काम करते हैं।

स्टेप 1: widget और उसका script टैग पढ़ें

दोनों वर्ज़न एक ही element रेंडर करते हैं: frc-captcha class वाला एक div, जिस पर data-sitekey attribute होता है। यानी element से आपको sitekey मिलता है, प्रोटोकॉल के बारे में कुछ नहीं। वर्ज़न उस script टैग में होता है जो widget को लोड करता है, क्योंकि v1 और v2 अलग-अलग फ़ाइल नामों वाले अलग-अलग पैकेज हैं:

<!-- v2: the @friendlycaptcha/sdk package -->
<div class="frc-captcha" data-sitekey="YOUR_SITEKEY"></div>
<script type="module" src="https://cdn.jsdelivr.net/npm/@friendlycaptcha/sdk/site.min.js" async defer></script>
<script nomodule src="https://cdn.jsdelivr.net/npm/@friendlycaptcha/sdk/site.compat.min.js" async defer></script>

<!-- v1: the friendly-challenge package -->
<div class="frc-captcha" data-sitekey="YOUR_SITEKEY"></div>
<script type="module" src="https://cdn.jsdelivr.net/npm/friendly-challenge/widget.module.min.js" async defer></script>
<script nomodule src="https://cdn.jsdelivr.net/npm/friendly-challenge/widget.min.js" async defer></script>

ज़्यादातर साइटें उस पाथ के अंदर एक वर्ज़न नंबर पिन करती हैं, जिससे यहाँ कुछ नहीं बदलता। यह सब पढ़ने के लिए Python की standard library काफ़ी है। यह parser widget element के attributes रखता है, और हर उस script का पता भी जो nomodule fallback नहीं है, पेज URL के आधार पर पूरे पते में बदलकर:

# pip install capskip requests
from html.parser import HTMLParser
from urllib.parse import urljoin

import requests


class FriendlyPage(HTMLParser):
    """Collects the frc-captcha element and the page's script tags."""

    def __init__(self, page_url):
        super().__init__()
        self.page_url = page_url
        self.widget = {}
        self.scripts = []

    def handle_starttag(self, tag, attrs):
        a = dict(attrs)
        if "frc-captcha" in (a.get("class") or "").split():
            self.widget = a
        # Skip nomodule fallbacks; keep full addresses, never relative.
        if tag == "script" and a.get("src") and "nomodule" not in a:
            self.scripts.append(urljoin(self.page_url, a["src"]))


session = requests.Session()
page = FriendlyPage("https://example.com/signup")
page.feed(session.get(page.page_url, timeout=30).text)

urljoin कॉल यहाँ बेवजह नहीं है। खुद होस्ट किए गए widget का src relative हो सकता है, जैसे /js/site.min.js, और CapSkip को ऐसा पता चाहिए जिसे वह fetch कर सके: v2 पर solve चलाने के लिए वह वही script अपने ब्राउज़र में लोड करता है। सिर्फ़ पाथ पास करेंगे तो widget कभी लोड नहीं होगा।

स्टेप 2: वर्ज़न चुनें और friendly_captcha कॉल करें

CapSkip वर्ज़न एक तय क्रम में तय करता है और पहले जवाब पर रुक जाता है: पहले आपका पास किया वर्ज़न, फिर module_script के रूप में पास किया गया script का पता, फिर डिफ़ॉल्ट v1। फँसाने वाला क़दम यही आख़िरी है। जिस script से CapSkip वर्ज़न नहीं पढ़ पाता, जैसे कोई bundled /assets/app.4f2a.js, उसे बिना कुछ कहे v1 के रूप में हल कर दिया जाता है, और फिर v2 साइट हर token ठुकरा देती है।

इसलिए वर्ज़न अपने कोड में तय करें, जहाँ आप अनुमान लगाने से मना कर सकते हैं। CDN पते में पैकेज का नाम कोई शक नहीं छोड़ता। खुद होस्ट किया गया build अक्सर उसे हटा देता है पर फ़ाइल नाम रखता है, जैसा आधिकारिक WordPress प्लगइन करता है: site.min.js का मतलब v2, और widget.module.min.js या widget.min.js का मतलब v1। widget के अपने attributes आख़िरी सहारा हैं, क्योंकि दोनों वर्ज़न अपने ऑप्शंस के नाम अलग-अलग रखते हैं:

from urllib.parse import urlparse

V2_FILES = {"site.min.js", "site.compat.min.js"}
V1_FILES = {"widget.module.min.js", "widget.min.js", "widget.polyfilled.min.js"}

def friendly_version(page):
    # Package names are unambiguous, so check them first.
    for src in page.scripts:
        if "@friendlycaptcha/sdk" in src:
            return "v2", src
        if "friendly-challenge" in src:
            return "v1", src
    # Self-hosted builds usually keep the file name. Themes ship their own
    # site.min.js too, so only trust a path that says friendly.
    for src in [s for s in page.scripts if "friendly" in s.lower()]:
        name = urlparse(src).path.rsplit("/", 1)[-1]
        if name in V2_FILES:
            return "v2", src
        if name in V1_FILES:
            return "v1", src
    # Last resort: v2 and v1 name their widget options differently.
    if {"data-api-endpoint", "data-form-field-name"} & page.widget.keys():
        return "v2", None
    if {"data-puzzle-endpoint", "data-solution-field-name"} & page.widget.keys():
        return "v1", None
    raise RuntimeError("v1 or v2? Read the page and set it by hand.")

फ़ाइल नाम वाली जाँच सिर्फ़ उन paths पर भरोसा करती है जिनमें friendly लिखा हो, क्योंकि कोई theme अपनी खुद की site.min.js लोड कर सकती है जिसका widget से कोई लेना-देना नहीं।

फिर कॉल करें। जो वर्ज़न मिला वह भेजें, और अगर script का पता हो तो वह भी। स्पष्ट रूप से दिया गया वर्ज़न ही प्राथमिकता पाता है, और v2 पर पता CapSkip को बताता है कि ठीक उसी build से हल करे जिसे साइट लोड करती है। None वैल्यू को रिक्वेस्ट से बस हटा दिया जाता है।

from capskip import CapSkip

solver = CapSkip(host="127.0.0.1", port=8080)

version, script = friendly_version(page)
result = solver.friendly_captcha(
    page.widget["data-sitekey"],
    "https://example.com/signup",
    version=version,
    module_script=script,
    # data-api-endpoint="eu" (v2) or data-puzzle-endpoint (v1).
    api_server=page.widget.get("data-api-endpoint")
    or page.widget.get("data-puzzle-endpoint"),
)

print(result["token"][:40])   # v2 tokens start with AQQA.

api_server वाली लाइन Friendly Captcha के EU endpoint पर चलने वाली साइटों को संभालती है। EU साइट के sitekey को global endpoint से भी token मिल जाता है, इसलिए वहाँ हल करने पर नाकामी सिर्फ़ साइट के अपने वेरिफ़िकेशन में सामने आती है, बिल्कुल वर्ज़न वाली चुपचाप नाकामी की तरह। जब दोनों में से कोई attribute मौजूद न हो, तो वैल्यू None होती है और CapSkip global endpoint इस्तेमाल करता है।

नतीजा एक सादा dict है। result["token"] वह स्ट्रिंग है जिसे सबमिट करना है, result["code"] में वही स्ट्रिंग उन scripts के लिए होती है जो किसी दूसरे सॉल्वर से पोर्ट की गई हैं, और result["captchaId"] इस task के लिए CapSkip का रेफ़रेंस है। v1, v2, 1 या 2 के अलावा कोई और वर्ज़न, खाली sitekey, या ऐसा ऑप्शन जो यह method नहीं लेता, कुछ भी भेजे जाने से पहले ही ValidationException उठा देता है।

स्टेप 3: token को उसी फ़ील्ड में पोस्ट करें जिसे widget इस्तेमाल करता है

फ़ील्ड का नाम दूसरी चीज़ है जो दोनों वर्ज़न में अलग होती है, और साइट widget element पर उसका नाम बदल सकती है:

import requests

# A renamed field wins; otherwise the default for the version.
field = (
    page.widget.get("data-form-field-name")          # v2 rename
    or page.widget.get("data-solution-field-name")   # v1 rename
    or ("frc-captcha-response" if version == "v2" else "frc-captcha-solution")
)

# session is the requests.Session that fetched the page.
resp = session.post(
    "https://example.com/signup",
    data={"email": "YOUR_EMAIL", field: result["token"]},
    # Browsers send the page as Referer; some servers refuse a post without it.
    headers={"Referer": page.page_url},
    timeout=30,
)
print(resp.status_code)

v1 का एक खास मामला: अगर data-solution-field-name को एक अकेले हाइफ़न पर सेट किया गया है, तो इसका मतलब है कि widget कोई hidden फ़ील्ड लिखता ही नहीं, और साइट की अपनी script token को किसी और तरीके से भेजती है। उस रिक्वेस्ट की हूबहू नकल करें, जैसा नीचे बताया गया है।

फॉर्म का action attribute जहाँ इशारा करता है, वहीं पोस्ट करें, और फॉर्म के बाकी फ़ील्ड भी शामिल करें, CSRF token जैसे hidden फ़ील्ड भी। पेज लाना और फॉर्म पोस्ट करना एक ही requests.Session से करने पर साइट की cookies बनी रहती हैं, जिन पर वह CSRF token आमतौर पर निर्भर करता है। पेज URL को Referer के रूप में भी भेजें: requests न Referer header भेजता है न Origin, और कुछ frameworks, जिनमें Django भी है, ऐसी HTTPS फॉर्म पोस्ट को मना कर देते हैं जिसमें इन दोनों में से कोई न हो, चाहे cookie और token कितने भी सही हों।

token को data= के साथ बॉडी में भेजें, params= के साथ URL में कभी नहीं। v2 token लगभग छह किलोबाइट का होता है, जो किसी सर्वर की URL लंबाई सीमा पार करने के लिए काफ़ी है, जबकि v1 token डॉट से अलग किए गए चार हिस्सों वाला और कुछ सौ कैरेक्टर का होता है। इसे जस का तस आगे भेजें, बिना कुछ काटे या दोबारा encode किए। यह एक ही सबमिट के लिए है: Friendly Captcha का वेरिफ़िकेशन ऐसे response को ठुकरा देता है जो पहले इस्तेमाल हो चुका हो या expire हो चुका हो, इसलिए हर फॉर्म के लिए दोबारा हल करें।

कुछ साइटें फॉर्म पोस्ट के बजाय JavaScript और JSON बॉडी से फॉर्म भेजती हैं। जब फ़ील्ड का नाम सही होने पर भी सबमिट फेल हो, तो DevTools खोलें, एक बार हाथ से सबमिट करें, और पेज जो भेजता है ठीक वही कॉपी करें।

स्टेप 4: एक साथ कई फॉर्म, और सॉल्वर कहाँ चलता है

Python में AsyncCapSkip, httpx पर बना एक असली asynchronous क्लाइंट है, blocking क्लाइंट का उपनाम नहीं, इसलिए एक ही event loop कई solve एक साथ चालू रख सकता है। इन्हें एक semaphore से सीमित करें, ताकि उससे ज़्यादा solve कभी न चलें जितनों पर CapSkip एक साथ काम करेगा। CapSkip के Friendly Captcha सेक्शन में Max. Threads सेटिंग का डिफ़ॉल्ट 10 है।

import asyncio
from capskip import AsyncCapSkip

solver = AsyncCapSkip(host="127.0.0.1", port=8080)
limit = asyncio.Semaphore(10)   # match Friendly Captcha Max. Threads

async def solve(sitekey, url, version, script):
    async with limit:
        r = await solver.friendly_captcha(
            sitekey, url, version=version, module_script=script)
        return r["token"]

async def main(jobs):
    # jobs: (sitekey, url, version, script) tuples from Step 2
    return await asyncio.gather(*(solve(*j) for j in jobs))

मानकर चलें कि solve का समय हर बार अलग होगा। Friendly Captcha हर रिक्वेस्ट के लिए काम की मात्रा तय करता है और जिन पतों को वह पहले बहुत देख चुका है उनके लिए इसे बढ़ा देता है, और CapSkip हर v2 widget को असली ब्राउज़र में हल करता है। इसीलिए यह method 120 सेकंड वाले defaultTimeout पर नहीं, बल्कि recaptchaTimeout पर पोलिंग करता है, जो डिफ़ॉल्ट रूप से 300 सेकंड है। CapSkip की अपनी सीमाएँ भी हैं। उसकी Friendly Captcha सेटिंग्स में कोई task खाली thread के लिए 250 सेकंड इंतज़ार कर सकता है और फिर हल करने में 120 सेकंड लगा सकता है, जिसके बाद CapSkip उसे फेल कर देता है। इसलिए धीमे solve को ज़्यादा Row Timeout चाहिए, और SDK के अपने टाइमआउट को, जो डिफ़ॉल्ट रूप से 300 सेकंड है या हर कॉल के लिए दिया गया timeout=, उस Row Timeout के साथ-साथ वह समय भी कवर करना होगा जो task किसी thread के इंतज़ार में बिताता है। ऊपर वाला semaphore इस इंतज़ार को लगभग शून्य रखता है। अगर लंबे रन के दौरान solve धीमे होते जाएँ, तो आमतौर पर वजह एक ही पते पर बढ़ती कठिनाई होती है। हर रिक्वेस्ट के लिए प्रॉक्सी को type और uri keys वाले dict के रूप में पास करें, या CapSkip में प्रॉक्सी पूल कॉन्फ़िगर करें, ताकि हर solve एक ही पते से न आए।

सैंपल 127.0.0.1 इस्तेमाल करते हैं, क्योंकि जब आपकी script और सॉल्वर एक ही मशीन पर हों तो यही सही है। जैसे ही script कहीं और चलने लगती है, जैसे किसी VPS, कंटेनर या CI रनर पर, loopback गलत मशीन की ओर इशारा करता है और पहली ही कॉल NetworkException उठा देती है। CapSkip को Server मोड पर कर दें, तो वह आपके नेटवर्क पते या पब्लिक IP पर सुनता है, ताकि इनमें से कोई भी उसी API के ज़रिए उस तक पहुँच सके। अगर रास्ता इंटरनेट से होकर जाता है तो स्टैटिक पब्लिक IP इस्तेमाल करें, साथ में अपेक्षित पतों के लिए फ़ायरवॉल नियम रखें। यह अब भी आपका अपना हार्डवेयर है, और अब भी बिना मीटर वाला है। क्लाइंट एनवायरनमेंट वेरिएबल खुद नहीं पढ़ता, इसलिए CAPSKIP_HOST को अपने कोड में पढ़ें और क्लाइंट को पास करें, जैसा नीचे दिया गया पूरा उदाहरण करता है।

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

# pip install capskip requests
import os
from html.parser import HTMLParser
from urllib.parse import urljoin, urlparse

import requests
from capskip import CapSkip
from capskip.exceptions import CapSkipError, ValidationException

PAGE_URL = "https://example.com/signup"
V2_FILES = {"site.min.js", "site.compat.min.js"}
V1_FILES = {"widget.module.min.js", "widget.min.js", "widget.polyfilled.min.js"}


class FriendlyPage(HTMLParser):
    def __init__(self, page_url):
        super().__init__()
        self.page_url, self.widget, self.scripts = page_url, {}, []

    def handle_starttag(self, tag, attrs):
        a = dict(attrs)
        if "frc-captcha" in (a.get("class") or "").split():
            self.widget = a
        if tag == "script" and a.get("src") and "nomodule" not in a:
            self.scripts.append(urljoin(self.page_url, a["src"]))


def friendly_version(page):
    for src in page.scripts:
        if "@friendlycaptcha/sdk" in src:
            return "v2", src
        if "friendly-challenge" in src:
            return "v1", src
    for src in [s for s in page.scripts if "friendly" in s.lower()]:
        name = urlparse(src).path.rsplit("/", 1)[-1]
        if name in V2_FILES or name in V1_FILES:
            return ("v2" if name in V2_FILES else "v1"), src
    keys = page.widget.keys()
    if {"data-api-endpoint", "data-form-field-name"} & keys:
        return "v2", None
    if {"data-puzzle-endpoint", "data-solution-field-name"} & keys:
        return "v1", None
    raise SystemExit("v1 or v2? Read the page and set it by hand.")


solver = CapSkip(
    apiKey=os.getenv("CAPSKIP_API_KEY", "capskip"),
    host=os.getenv("CAPSKIP_HOST", "127.0.0.1"),
    port=int(os.getenv("CAPSKIP_PORT", "8080")),
)
session = requests.Session()

page = FriendlyPage(PAGE_URL)
page.feed(session.get(PAGE_URL, timeout=30).text)
if not page.widget.get("data-sitekey"):
    raise SystemExit("No frc-captcha widget in the HTML; it may be built by JS.")

version, script = friendly_version(page)
try:
    result = solver.friendly_captcha(
        page.widget["data-sitekey"], PAGE_URL,
        version=version, module_script=script,
        api_server=page.widget.get("data-api-endpoint")
        or page.widget.get("data-puzzle-endpoint"),
    )
except ValidationException as exc:
    raise SystemExit(f"not sent: {exc}")
except CapSkipError as exc:
    raise SystemExit(f"solve failed: {exc!r}")

field = (page.widget.get("data-form-field-name")
         or page.widget.get("data-solution-field-name")
         or ("frc-captcha-response" if version == "v2" else "frc-captcha-solution"))

# Post wherever the form's action points, with its other fields.
# Browsers send the page as Referer; some servers refuse a post without it.
resp = session.post(PAGE_URL, data={"email": "YOUR_EMAIL", field: result["token"]},
                    headers={"Referer": PAGE_URL}, timeout=30)
print(resp.status_code, version, field)

वर्ज़न एक बार तय होता है और दो बार इस्तेमाल होता है, solve के लिए और फ़ील्ड के नाम के लिए, इसलिए दोनों में कभी मतभेद नहीं हो सकता। जब parser को कोई widget नहीं मिलता, तो आमतौर पर पेज उसे JavaScript से बनाता है, और आपको sitekey रेंडर हुए पेज से या उसे बनाने वाली script से लेना होगा। raw endpoint जो भी पैरामीटर स्वीकार करता है, वे सब यहाँ दर्ज हैं: Friendly Captcha API रेफ़रेंस.

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

आप जो देखते हैंकारणफिक्स
साइट उस token को ठुकरा देती है जिसे CapSkip ने बिना किसी error के लौटाया थागलत वर्ज़न हल हुआ, अक्सर इसलिए कि चुपचाप डिफ़ॉल्ट v1 लागू हो गयास्टेप 2 की तरह वर्ज़न कोड में तय करें और उसे पास करें
ऐसी साइट पर token ठुकराया जाता है जिसके widget पर data-api-endpoint (v2) या data-puzzle-endpoint (v1) हैtoken global endpoint से आया, जबकि साइट EU endpoint इस्तेमाल करती हैattribute की वैल्यू को api_server के रूप में पास करें
token सही है, फिर भी सबमिट फेल हो जाता हैtoken गलत फ़ील्ड में गया, पोस्ट Referer के बिना गई, या साइट JSON पोस्ट करती हैनाम बदलने वाले attributes जाँचें और पेज URL को Referer के रूप में भेजें, फिर DevTools में दिखी रिक्वेस्ट की हूबहू नकल करें
widget को खुद होस्ट करने वाली साइट पर v2 solve फेल हो जाता हैmodule_script एक relative पाथ था, इसलिए widget कभी लोड ही नहीं हुआपास करने से पहले src को urljoin से पूरे पते में बदलें
हर बार कुछ ही सेकंड में ERROR_CAPTCHA_UNSOLVABLE वाला ApiExceptionFriendly Captcha sitekey या पेज के origin को मना कर देता है, या उस key वाले खाते में v2 या EU endpoint चालू नहीं हैsitekey, पेज URL और api_server जाँचें; दोबारा कोशिश करने से फ़ायदा नहीं होगा
कुछ भी भेजे जाने से पहले ही ValidationExceptionखाली sitekey, v1, v2, 1 या 2 के अलावा कोई और वर्ज़न, या कोई अनजान ऑप्शनजाँचें कि parser को widget मिला, और मैसेज में बताया गया ऑप्शन हटा दें
लंबे रन के साथ-साथ solve धीमे होते जाते हैंFriendly Captcha व्यस्त पते के लिए काम बढ़ा देता हैहर रिक्वेस्ट के लिए प्रॉक्सी जोड़ें, या CapSkip में प्रॉक्सी पूल बनाएँ
दो मिनट या उससे ज़्यादा समय बाद ERROR_CAPTCHA_UNSOLVABLE वाला ApiExceptionsolve CapSkip के 120 सेकंड वाले Row Timeout से आगे चला गया, या खाली thread के इंतज़ार में 250 सेकंड वाले Wait Timeout से ज़्यादा रुकाsemaphore को Max. Threads के बराबर रखें, प्रॉक्सी जोड़ें, या Friendly Captcha सेटिंग्स में Row Timeout बढ़ाएँ
300 सेकंड बाद TimeoutExceptiontask खाली thread के इंतज़ार के बाद हल हुआ, और कुल मिलाकर SDK की पोलिंग से ज़्यादा समय लगाsemaphore को Max. Threads के बराबर रखें, या लंबा timeout= पास करें
पहली कॉल पर NetworkExceptionCapSkip चल नहीं रहा, या host और port गलत हैंCapSkip शुरू करें, फिर देखें कि Local मोड चाहिए या Server मोड

FAQ

सिर्फ़ module_script भेजकर फ़ैसला CapSkip पर क्यों न छोड़ दें?

छोड़ सकते हैं, और CDN से लोड हुई script के लिए यह काम भी करता है, क्योंकि पैकेज का नाम पते में होता है। जोखिम fallback में है। जब पते में ऐसा कोई build नहीं होता जिसे CapSkip पहचानता हो, तो वह फेल होने के बजाय v1 हल कर देता है, और नतीजे में कुछ भी आपको यह नहीं बताता। अपने कोड में जाँच करने से वह चुपचाप लगने वाला डिफ़ॉल्ट एक ऐसे exception में बदल जाता है जिसे आप देख सकते हैं।

क्या solve के लिए मेरी तरफ़ Selenium या Playwright जैसा कोई ब्राउज़र चाहिए?

नहीं। आपकी script को सिर्फ़ HTML चाहिए, जिसे requests ले आता है। ब्राउज़र का सारा काम CapSkip के अंदर होता है, जो v2 widgets को अपने ब्राउज़र में और v1 puzzles को सीधे हल करता है। अगर आप किसी और वजह से पहले से ब्राउज़र चला रहे हैं, तो वही कॉल काम करती है; बस sitekey और script का पता लाइव पेज से पढ़ें।

क्या VPS पर या क्लाउड में चल रही script मेरे Windows PC पर चल रहे सॉल्वर का इस्तेमाल कर सकती है?

हाँ। कनेक्शन सेटिंग्स में CapSkip को Server मोड पर कर दें ताकि वह loopback के बजाय किसी नेटवर्क पते पर सुने, जहाँ script चलती है वहाँ CAPSKIP_HOST सेट करें, और उसे क्लाइंट को पास करें, जैसा पूरा उदाहरण करता है। VPS, कंटेनर और होस्टेड रनर, सब उसी HTTP API के ज़रिए जुड़ते हैं। जब रास्ता इंटरनेट से होकर जाए तो फ़ायरवॉल नियम के साथ स्टैटिक पब्लिक IP इस्तेमाल करें। सॉल्वर आपके अपने हार्डवेयर पर ही रहता है, इसलिए ज़्यादा solve चलाने से आपका खर्च कभी नहीं बदलता।

यह C# गाइड से कैसे अलग है?

raw endpoint, ऑप्शंस और नतीजे के फ़ील्ड हर CapSkip SDK में एक जैसे हैं; सिर्फ़ उनके नाम लिखने का ढंग बदलता है। असली फ़र्क उनके आस-पास के कोड में है: standard library का parser, साइट की cookies साथ रखने वाला requests session, और AsyncCapSkip, जो Python में एक अलग asynchronous क्लाइंट है, जबकि .NET में यह CapSkipClient का ही दूसरा नाम है, जो पहले से asynchronous है। इस वर्कफ़्लो का .NET वर्ज़न C# Friendly Captcha गाइड में है, और Python में एक साथ कई solve चलाने के बारे में ज़्यादा जानकारी यहाँ है: समानांतर हल गाइड.

संक्षेप में

Python में Friendly Captcha हल करने के लिए, पेज को parse करके frc-captcha element और उसके script टैग निकालें, और हर src को पूरे पते में बदलें। v1 या v2 पैकेज के नाम से तय करें, या widget के अपने attributes से, और अगर दोनों में से कोई न बताए तो exception उठाएँ। friendly_captcha को sitekey, पेज URL, उस वर्ज़न और script के पते के साथ कॉल करें, और EU साइटों के लिए api_server भी दें। token को एक बार, बॉडी में, उस फ़ील्ड में पोस्ट करें जिसका नाम वर्ज़न और widget तय करते हैं।

वॉल्यूम के बारे में एक और बात। Friendly Captcha अपनी कठिनाई की कीमत पैसे में नहीं, CPU समय में वसूलता है, इसलिए किसी व्यस्त दिन की लागत आपकी अपनी मशीन पर लगा solve का समय है। लोकल रूप से एक असीमित कैप्चा सॉल्वर चलाएँ, और तब आपकी सीमा सिर्फ़ वह मशीन और उसे दिए गए threads हैं, कोई मीटर नहीं।