C# में HttpClient से DataDome कैप्चा कैसे हल करें

C# में DataDome कैप्चा हल करने के लिए आप 403 ब्लॉक पेज पढ़ते हैं, उससे चैलेंज का पता बनाते हैं, और उसे CapSkip DataDome सॉल्वर को DataDomeAsync के साथ भेजते हैं, फिर लौटी cookie के साथ HttpClient से अपनी रिक्वेस्ट दोहराते हैं। जवाब एक cookie है, फ़ॉर्म के लिए कोई टोकन नहीं, और CapSkip के दस्तावेज़ कहते हैं कि वह सिर्फ़ उसी User-Agent और उसी IP पते के साथ काम करती है जिसके साथ वह मिली थी। यह गाइड पूरा loop एक console प्रोजेक्ट में सिर्फ़ CapSkip NuGet package के साथ चलाती है, और किसी चीज़ की ज़रूरत नहीं। CapSkip आपके अपने Windows PC पर चलता है और प्रति-हल कोई शुल्क नहीं लगता, इसलिए दोबारा कोशिश करने में कुछ खर्च नहीं होता।
आपको क्या चाहिए
- CapSkip 3.2.0 या बाद का, जो किसी Windows PC पर चल रहा हो। उस रिलीज़ में स्लाइडर और डिवाइस चेक के लिए DataDome हल करने की सुविधा जुड़ी थी। पहली बार शुरू करने के चरण सेटअप गाइड में समझाए गए हैं।
- कम से कम .NET 8 SDK और CapSkip NuGet package का वर्ज़न 1.5.0 या बाद का। पुराने वर्ज़न में DataDomeAsync method नहीं है। नीचे का कोड .NET SDK 10.0.100 पर, net10.0 और net8.0 पर CapSkip 1.5.0 के साथ चला।
- एक PowerShell prompt। कमांड PowerShell syntax में लिखी गई हैं।
- ऐसा पेज जो आपका अपना हो या जिसे ऑटोमेट करने की आपको अनुमति हो, और जो DataDome के पीछे हो।
- सॉल्वर के लिए एक पता। Local मोड में CapSkip केवल उसी डिवाइस के लिए 127.0.0.1 पर जवाब देता है। Server मोड में वह आपके नेटवर्क पते या पब्लिक IP पर सुनता है, ताकि किसी दूसरे PC, VPS या होस्टेड प्लेटफ़ॉर्म पर चल रहा प्रोग्राम API के ज़रिए उसे कॉल कर सके। दोनों यहाँ सेट किए जाते हैं: कनेक्शन सेटिंग्स.
- टेस्टिंग से आगे बढ़ने पर हर टास्क के लिए एक sticky proxy (जो पूरे टास्क में एक ही IP पता बनाए रखे)। cookie उस IP पते से बँधी होती है जिससे वह मिली थी, जिसे चरण 3 समझाता है।
यह कैसे टेस्ट किया गया: कोड तीन mock सर्वर के सामने चला जो मैंने खुद लिखे थे (यानी टेस्ट के लिए बनाए नकली सर्वर), किसी असली DataDome साइट के सामने नहीं। उनमें से एक उन जाँचों की नक़ल करता है जो CapSkip के API दस्तावेज़ के DataDome सेक्शन में सूचीबद्ध हैं, दूसरा एक संरक्षित साइट की भूमिका निभाता है जो 403 ब्लॉक पेज देती है और सिर्फ़ वही cookie स्वीकार करती है जो CapSkip ने जारी की हो, उसी User-Agent के साथ जिसके लिए वह जारी हुई थी, और तीसरा forward proxy की भूमिका निभाता है। SDK NuGet का असली CapSkip 1.5.0 package था। CapSkip app मेरे PC पर चल नहीं रहा था, इसलिए यहाँ कुछ नहीं दिखाता कि असली हल में कितना समय लगता है या कोई असली DataDome साइट क्या स्वीकार करती है। cookie के नियम CapSkip के दस्तावेज़ों से आते हैं। DataDome के अपने cookie दस्तावेज़ के अनुसार datadome cookie एक साल चलती है, और उसके Domain या Path को बदलने से आगाह किया गया है। यह नहीं बताया गया कि cookie किससे बँधी है।
चरण 1: प्रोजेक्ट बनाएँ और ब्लॉक पेज पढ़ें
एक console प्रोजेक्ट बनाएँ और package जोड़ें। default template implicit usings और nullable types चालू करता है, और कोड दोनों पर निर्भर है।
dotnet new console -o datadome-demo cd datadome-demo dotnet add package CapSkip
जब DataDome को किसी रिक्वेस्ट पर शक होता है, तो साइट कंटेंट की जगह status 403, कभी-कभी 401, और एक छोटा HTML पेज लौटाती है। उस पेज में dd नाम का एक script object होता है और वह geo.captcha-delivery.com से एक iframe में चैलेंज लोड करता है। CapSkip को iframe का वही पता चाहिए, और दस्तावेज़ उसे captcha_url कहते हैं। अगर पेज में iframe पहले से मौजूद है, तो आप उसका src जैसा है वैसा ले सकते हैं। ज़्यादातर ब्लॉक पेज iframe को script से बनाते हैं, इसलिए पहला ब्लॉक dd object से वही पता जोड़कर बनाता है। पहली रिक्वेस्ट सादे Chrome User-Agent के साथ जाती है। वह string कभी CapSkip को नहीं भेजी जाती, इसलिए उसके Chrome वर्ज़न का किसी चीज़ से मेल खाना ज़रूरी नहीं, और मैंने यह नहीं आज़माया कि कोई असली DataDome साइट किसी दूसरे User-Agent को वही चैलेंज देती है या नहीं। Program.cs की सामग्री को इस गाइड के चारों ब्लॉक से बदलें, एक के बाद एक, इसी ब्लॉक से शुरू करके।
rt की value रास्ता तय करती है। c का मतलब /captcha/ के नीचे का स्लाइडर है और i का मतलब /interstitial/ के नीचे का डिवाइस चेक। t की value भी मायने रखती है: fe एक चैलेंज है, और bv का मतलब है कि पेज लाने वाले IP पते को DataDome ने बैन कर दिया है।
using System.Net;
using System.Text.Json;
using System.Text.RegularExpressions;
using CapSkip;
static class BlockPage
{
// Build the challenge address from a DataDome block page, or return null.
public static string? CaptchaUrl(HttpStatusCode status, string html, string pageUrl)
{
if (status != HttpStatusCode.Forbidden && status != HttpStatusCode.Unauthorized) return null;
var found = Regex.Match(html, @"var\s+dd\s*=\s*(\{[^}]*\})");
if (!found.Success) return null;
using var doc = JsonDocument.Parse(found.Groups[1].Value.Replace('\'', '"'));
var dd = doc.RootElement;
string? Get(string name) => dd.TryGetProperty(name, out var v)
? (v.ValueKind == JsonValueKind.String ? v.GetString() : v.GetRawText())
: null;
var query = new List<(string Key, string? Value)>
{
("initialCid", Get("cid")), ("hash", Get("hsh")), ("cid", Get("cookie")),
};
if (!string.IsNullOrEmpty(Get("t"))) query.Add(("t", Get("t"))); // fe challenge, bv banned
query.Add(("referer", pageUrl));
query.Add(("s", Get("s")));
foreach (var key in new[] { "e", "b" })
if (!string.IsNullOrEmpty(Get(key))) query.Add((key, Get(key)));
query.Add(("dm", "cd"));
var path = Get("rt") == "i" ? "interstitial" : "captcha";
var qs = string.Join("&", query.Select(p => p.Key + "=" + Uri.EscapeDataString(p.Value ?? "")));
return $"https://{Get("host") ?? "geo.captcha-delivery.com"}/{path}/?{qs}";
}
}method किसी भी ऐसे response के लिए null लौटाता है जो ब्लॉक पेज नहीं है, ताकि कॉल करने वाला “हल करने को कुछ नहीं” और “हल हो गया” में फ़र्क़ कर सके। हर ब्लॉक पेज एक नया चैलेंज होता है। उसके पते का जल्दी इस्तेमाल करें, और अगले टास्क के लिए पुराने पते को दोबारा इस्तेमाल करने के बजाय पेज फिर से रिक्वेस्ट करें।
चरण 2: DataDomeAsync से हल करें
SDK का method DataDomeAsync(captchaUrl, url, options) है, जो 2Captcha के argument क्रम का पालन करता है। दोनों पते positional हैं, और userAgent व proxy options dictionary में जाते हैं। CapSkip में दोनों वैकल्पिक हैं। अगर आप कोई User-Agent नहीं भेजते, तो CapSkip अपने ब्राउज़र वाला इस्तेमाल करता है और उसे नतीजे में UserAgent के रूप में लौटाता है। अगर आप भेजते हैं, तो वह CapSkip के ब्राउज़र के ही major वर्ज़न वाला Windows के लिए Chrome होना चाहिए, इसलिए उसे छोड़ देना आसान चुनाव है। method 300 सेकंड के उस लंबे timeout के साथ इंतज़ार करता है जो reCAPTCHA इस्तेमाल करता है, क्योंकि CapSkip चैलेंज को असली ब्राउज़र में पास करता है। API दस्तावेज़ कहते हैं कि एक हल में आम तौर पर 3 से 20 सेकंड लगते हैं, और मैंने किसी का समय नहीं नापा।
proxy वही है जिससे आपकी बाद की रिक्वेस्ट जाएँगी। अगर आपका प्रोग्राम proxy से होकर जाता है तो उसे दें, और अगर रिक्वेस्ट उसी मशीन से निकलती हैं जिस पर CapSkip चलता है तो कुछ न दें। अगर CapSkip किसी server पर चल रहा है, तो वह मशीन वही server है। Proxy class type और पता लेती है, जैसे HTTP और user:[email protected]:8080।
static class Solve
{
// Local mode. In Server mode, use your server's address instead.
static readonly CapSkipClient Solver = new(host: "127.0.0.1", port: 8080);
public static Task<SolveResult> Run(string captchaUrl, string pageUrl, string? proxyUrl)
{
var options = new Dictionary<string, object?>();
if (proxyUrl != null)
{
var i = proxyUrl.IndexOf("://", StringComparison.Ordinal);
options["proxy"] = new Proxy(proxyUrl[..i].ToUpperInvariant(), proxyUrl[(i + 3)..]);
}
// No userAgent option: CapSkip uses its own browser's and reports it.
return Solver.DataDomeAsync(captchaUrl, pageUrl, options);
}
}अगर डिवाइस चेक पास नहीं हो पाता, तो DataDome स्लाइडर पर चला जाता है। CapSkip के दस्तावेज़ों के अनुसार वही एक call उसके पीछे वहाँ तक जाती है, इसलिए दोनों चैलेंज के लिए अलग कोड की ज़रूरत नहीं।
चरण 3: cookie को उसके User-Agent और IP पते के साथ भेजें
नतीजा एक SolveResult है जिसकी चार properties मायने रखती हैं। नतीजे की Code property वह cookie है जैसी DataDome ने सेट की थी, Max-Age, Domain और बाकी attributes के साथ, और cookie jar को यही चाहिए। उसकी Cookie property सिर्फ़ datadome नाम और value की जोड़ी है, और Cookie header को यही चाहिए। UserAgent वह User-Agent है जिसके साथ वह मिली थी। Proxy property तभी सेट होती है जब आपने कोई proxy नहीं भेजा और CapSkip ने अपनी DataDome proxy list से हल किया। CapSkip के दस्तावेज़ कहते हैं कि DataDome cookie को किसी दूसरे IP पते से या दूसरे User-Agent के साथ ठुकरा देता है, इसलिए हर बार वही User-Agent भेजें, और जब नतीजा किसी proxy का नाम दे, तो रिक्वेस्ट उसी से भेजें। वह Proxy property सिर्फ़ host और port है, बिना scheme और बिना login के। प्रोग्राम http:// जोड़ देता है और इस तरह मान लेता है कि list proxy को login नहीं चाहिए। अगर आपकी proxy को चाहिए, तो उसे जोड़ें।
यह helper Cookie header को हाथ से सेट करता है और UseCookies बंद कर देता है, ताकि HttpClient उसे वैसे ही भेजे जैसा लिखा है। वह response से बदली हुई cookie भी पढ़ता है, क्योंकि DataDome बाद के किसी response में नई cookie भेज सकता है और ब्राउज़र उसे रख लेता। कोड छोटा रखने के लिए वह हर call पर एक HttpClient बनाता है। कई रिक्वेस्ट के लिए इसके बजाय हर proxy के लिए एक client रखें।
static class Web
{
static HttpClient Client(string? proxyUrl)
{
// UseCookies is off, so the Cookie header set by hand goes out as it is.
var handler = new HttpClientHandler { UseCookies = false };
if (proxyUrl != null)
{
var uri = new Uri(proxyUrl);
var proxy = new WebProxy($"{uri.Scheme}://{uri.Host}:{uri.Port}");
if (uri.UserInfo.Length > 0)
{
var login = uri.UserInfo.Split(':', 2);
proxy.Credentials = new NetworkCredential(
Uri.UnescapeDataString(login[0]),
login.Length > 1 ? Uri.UnescapeDataString(login[1]) : "");
}
handler.Proxy = proxy;
}
return new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(30) };
}
// Returns the status, the body and a replacement datadome cookie if the site sent one.
public static async Task<(HttpStatusCode Status, string Body, string? Fresh)> Get(
string url, string userAgent, string? cookie, string? proxyUrl)
{
using var http = Client(proxyUrl);
using var request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.TryAddWithoutValidation("User-Agent", userAgent);
if (cookie != null) request.Headers.TryAddWithoutValidation("Cookie", cookie);
using var response = await http.SendAsync(request);
string? fresh = null;
if (response.Headers.TryGetValues("Set-Cookie", out var lines))
fresh = lines.Select(l => Regex.Match(l, "^datadome=[^;]+"))
.FirstOrDefault(m => m.Success)?.Value;
return (response.StatusCode, await response.Content.ReadAsStringAsync(), fresh);
}
}चरण 4: पूरा काम करने वाला उदाहरण
आख़िरी ब्लॉक C# में DataDome CAPTCHA का पूरा flow जोड़ता है और retry भी जोड़ता है। इसे dotnet run से चलाएँ। हर कोशिश पेज को दोबारा रिक्वेस्ट करती है, इसलिए हर बार नया चैलेंज साथ जाता है। नाकाम हल ApiException throw करता है और loop दोबारा कोशिश करता है। जिस cookie पर फिर भी 403 मिले, वह भी loop को नए ब्लॉक पेज के साथ दोबारा घुमा देती है।
static class Program
{
const string PageUrl = "https://www.example.com/"; // a page behind DataDome
const string Browser = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " +
"(KHTML, like Gecko) Chrome/156.0.0.0 Safari/537.36";
// The proxy your requests use, e.g. "http://user:[email protected]:8080". Null means
// they leave from the PC that runs CapSkip.
static readonly string? ProxyUrl = null;
static async Task<int> Main()
{
for (var attempt = 1; attempt <= 3; attempt++)
{
var (status, body, _) = await Web.Get(PageUrl, Browser, null, ProxyUrl); // fresh block page
var captchaUrl = BlockPage.CaptchaUrl(status, body, PageUrl);
if (captchaUrl is null)
{
Console.WriteLine($"{(int)status}: no DataDome block page on this response");
return 0;
}
SolveResult result;
try { result = await Solve.Run(captchaUrl, PageUrl, ProxyUrl); }
catch (ApiException ex)
{
Console.WriteLine($"attempt {attempt}: {ex.Message}"); // the next try gets a new page
continue;
}
// The cookie works only from the address and User-Agent that earned it.
var via = ProxyUrl ?? (result.Proxy is null ? null : "http://" + result.Proxy);
var cookie = result.Cookie;
var (code, page, fresh) = await Web.Get(PageUrl, result.UserAgent!, cookie, via);
if (code != HttpStatusCode.OK)
{
Console.WriteLine($"attempt {attempt}: HTTP {(int)code} with the cookie");
continue;
}
cookie = fresh ?? cookie; // DataDome may rotate the cookie; keep the newest
Console.WriteLine($"{(int)code} {page[..Math.Min(40, page.Length)]}");
var (again, _, _) = await Web.Get(PageUrl, result.UserAgent!, cookie, via); // reuse it
Console.WriteLine((int)again);
return 0;
}
Console.WriteLine("DataDome not passed in 3 attempts");
return 1;
}
}SDK captchaUrl को t=bv के साथ कुछ भी भेजने से पहले ही ठुकरा देता है, क्योंकि उस पते से कोई हल पास नहीं हो सकता। वह ValidationException एक ApiException नहीं है, इसलिए loop उसे नहीं पकड़ता। उसे सामने आने दें: यह संकेत है कि पता बदलकर फिर से शुरू करें। CapSkip जिस बैन को ERROR_PROXY_BANNED के रूप में बताता है, वह ApiException बनकर आता है और loop उसे उसी पते पर दोबारा आज़माता है, इसलिए अगर आउटपुट में वह error दिखे, तो proxy बदलकर ही दोबारा चलाएँ। proxy rotation गाइड में proxies को बारी-बारी से बदलने का तरीका है, और C# CAPTCHA डिटेक्टर में बताया गया है कि method चुनने से पहले यह कैसे पता करें कि पेज कौन-सा CAPTCHA इस्तेमाल करता है।
mock रन में प्रोग्राम ने इस तरह व्यवहार किया:
| mock सेटिंग | प्रोग्राम ने क्या किया |
|---|---|
| स्लाइडर ब्लॉक पेज, हर हल का जवाब दिया गया | एक टास्क, फिर cookie के साथ status 200, और दोबारा इस्तेमाल पर फिर 200 |
| डिवाइस चेक ब्लॉक पेज | वही कोड चला: captchaUrl /interstitial/ की ओर था और नतीजा वही रहा |
| पहले दो हल ठुकराए गए | दो attempt लाइनें, फिर तीसरे टास्क पर status 200 |
| पहले तीन हल ठुकराए गए | तीन attempt लाइनें, फिर यह संदेश कि DataDome पास नहीं हुआ, और exit code 1 |
| नतीजा एक proxy का नाम देता है, जैसा CapSkip की अपनी list करती है | cookie वाली रिक्वेस्ट उसी proxy से गई और 200 मिला |
| ProxyUrl में सेट किया गया आपका अपना proxy | in.php को proxy और उसका type मिला, और रिक्वेस्ट उसी से गईं |
| t=bv वाला ब्लॉक पेज | बिना पकड़ा हुआ ValidationException, और mock CapSkip को कोई रिक्वेस्ट मिली ही नहीं |
proxy वाले रन ने एक बात दिखाई: scheme के बाद का text CapSkip को वैसे ही जाता है जैसा आपने लिखा। ProxyUrl में percent-encoded लिखा login, जैसे dollar sign वाले password के लिए pa%24s, in.php तक अब भी encoded ही पहुँचा। login में ऐसे अक्षर न रखें जिन्हें encoding चाहिए, या Proxy argument को decoded values से खुद बनाएँ।
आम errors
| त्रुटि | कारण | फिक्स |
|---|---|---|
| ValidationException | ऐसा captchaUrl जो DataDome का चैलेंज पता नहीं है, t=bv वाला ब्लॉक पेज, ऐसा url जो http या https नहीं है, या ऐसा userAgent जो Windows के लिए Chrome नहीं है। संदेश बताता है कि इनमें से कौन-सा है | ताज़ा ब्लॉक पेज से पता दोबारा बनाएँ, अगर IP पता बैन हो तो उसे बदलें, और userAgent option हटा दें |
| ApiException, ERROR_WRONG_USERAGENT के साथ | User-Agent Windows के लिए Chrome है, पर उसका major वर्ज़न CapSkip के ब्राउज़र से अलग है | userAgent न भेजें और नतीजे में जो बताया जाए वही इस्तेमाल करें |
| ERROR_CAPTCHA_UNSOLVABLE वाला ApiException | चैलेंज पास नहीं हुआ | पेज को फिर से रिक्वेस्ट करें और उसका नया captchaUrl सबमिट करें |
| ApiException, ERROR_PROXY_BANNED के साथ | CapSkip ऐसे टास्क को ठुकरा देता है जिसका ब्लॉक पेज t=bv बताता है, API दस्तावेज़ों के अनुसार | proxy बदलें और पेज को फिर से रिक्वेस्ट करें |
| ApiException, ERROR_PROXY_CONNECT_REFUSED के साथ | CapSkip इसे तब बताता है जब वह proxy के ज़रिए कनेक्ट नहीं हो पाता। मैंने इसे दोहराया नहीं | proxy का पता, login और type जाँचें |
| CapSkip.TimeoutException | CapSkip ने 300 सेकंड के इंतज़ार में कोई जवाब नहीं दिया। यह ApiException नहीं है, इसलिए loop इसे नहीं पकड़ता। पूरा नाम लिखें, क्योंकि System में भी एक TimeoutException है | जाँचें कि CapSkip चल रहा है और हल कर रहा है, फिर प्रोग्राम को दोबारा चलाएँ |
| NetworkException | CapSkip चल नहीं रहा, या host या port गलत है | CapSkip शुरू करें और कनेक्शन सेटिंग्स जाँचें, फिर यह देखें: |
| cookie के साथ भी 403 आ रहा है | रिक्वेस्ट किसी दूसरे IP पते से निकली, दूसरा User-Agent इस्तेमाल हुआ, या cookie की अवधि खत्म हो गई | हर बार नतीजे का UserAgent और Proxy भेजें, या नए ब्लॉक पेज के साथ loop फिर से चलाएँ |
सॉल्वर को किसी server पर चलाना
प्रोग्राम को CapSkip से दो चीज़ें चाहिए: एक host और एक port। Local mode में वह 127.0.0.1 और आपका अपना PC है। Server mode में CapSkip आपके नेटवर्क पते या पब्लिक IP पर सुनता है, ताकि किसी दूसरे PC, VPS या hosted platform पर चल रहा प्रोग्राम उसी API के ज़रिए उसे कॉल कर सके। CapSkipClient constructor में host और port बदलें, कोड में और कुछ नहीं बदलता। स्थिर रिमोट एक्सेस के लिए स्टैटिक पब्लिक IP सुझाया जाता है। Server mode फिर भी आपका अपना हार्डवेयर है और फिर भी बिना किसी प्रति-हल शुल्क के है। यह पोर्ट को किसी भी पते के लिए खोल देता है, इसलिए जिन पतों को वहाँ तक नहीं पहुँचना चाहिए उनके लिए Windows Firewall का block rule जोड़ें। याद रखें कि हल उसी मशीन पर चलता है, इसलिए cookie उस server के IP पते से मिलती है। जब तक आप proxy नहीं देते, cookie ले जाने वाली रिक्वेस्ट को भी उसी server से निकलना होगा। proxy दें तो हल और अपनी रिक्वेस्ट, दोनों को वही दें। मोड कैसे बदलें, यह कनेक्शन सेटिंग्स में दिखाया गया है।
FAQ
क्या DataDomeAsync टोकन लौटाता है?
नहीं। वह datadome cookie लौटाता है। आप उस cookie को उस रिक्वेस्ट के Cookie header में भेजते हैं जो ब्लॉक हुई थी, उसी User-Agent के साथ। भरने के लिए कोई फ़ॉर्म फ़ील्ड नहीं है।
क्या DataDome के लिए proxy चाहिए?
टेस्ट के लिए नहीं। पहले रन के लिए एक IP पता काफ़ी है। CapSkip के दस्तावेज़ों के अनुसार cookie सिर्फ़ उसी IP पते से काम करती है जिससे वह मिली थी, इसलिए जब आप कई रिक्वेस्ट भेजें, तो हर टास्क के लिए एक sticky proxy रखें और वही proxy हल को और अपनी रिक्वेस्ट को दें।
क्या कोई hosted platform इसे आपके CapSkip के सामने चला सकता है?
हाँ, Server mode के ज़रिए। CapSkip को अपने नेटवर्क या पब्लिक IP पर सुनने के लिए सेट करें, CapSkipClient constructor को उसी host और port पर पॉइंट करें, और loop वैसे ही काम करता है, बस एक बदलाव के साथ: हल अब server पर चलता है, इसलिए हल को और अपनी रिक्वेस्ट को वही proxy दें। सॉल्वर आपके हार्डवेयर पर ही रहता है।
संक्षेप में
C# में DataDome CAPTCHA हल करने में चार काम होते हैं: ब्लॉक पेज पढ़ें, captchaUrl बनाएँ, DataDomeAsync को call करें, और cookie, उसके User-Agent और IP पते के साथ रिक्वेस्ट दोहराएँ। अपनी मशीन पर दोबारा कोशिश मुफ़्त है, और जब कोई संरक्षित साइट बार-बार नए चैलेंज थमाती रहे, तो आप यही अपने Windows PC पर चल रहे कैप्चा सॉल्वर से पाना चाहते हैं। SDK की बाकी जानकारी के लिए देखें: C# कैप्चा सॉल्वर पेज.
