{"id":25112,"date":"2026-08-11T19:22:30","date_gmt":"2026-08-11T19:22:30","guid":{"rendered":"https:\/\/capskip.com\/?p=25112"},"modified":"2026-08-11T19:22:30","modified_gmt":"2026-08-11T19:22:30","slug":"solve-captchas-parallel-python","status":"publish","type":"post","link":"https:\/\/capskip.com\/zh\/solve-captchas-parallel-python\/","title":{"rendered":"\u5982\u4f55\u7528 Python asyncio \u5e76\u884c\u8bc6\u522b\u9a8c\u8bc1\u7801"},"content":{"rendered":"<p>If you need to solve captchas in parallel from Python, use <code>AsyncCapSkip<\/code> with <code>asyncio.gather<\/code>. Python is the one CapSkip SDK where the async client is a real async implementation rather than an alias, so a batch of ten reCAPTCHAs finishes in roughly the time of the slowest one instead of the sum of all ten. This guide covers the working code, how to cap concurrency so you do not swamp the solver, and how to stop one failure killing the batch.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Why the Python client is the interesting one<\/h2>\n<p>All four SDKs export something called <code>AsyncCapSkip<\/code>. Only one of them is a separate implementation.<\/p>\n<table>\n<thead>\n<tr>\n<th>SDK<\/th>\n<th>What <code>AsyncCapSkip<\/code> is<\/th>\n<th>How you run work concurrently<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Python<\/td>\n<td>A genuine async client<\/td>\n<td><code>await asyncio.gather(...)<\/code><\/td>\n<\/tr>\n<tr>\n<td>Node.js<\/td>\n<td>An alias of <code>CapSkip<\/code><\/td>\n<td><code>await Promise.all([...])<\/code><\/td>\n<\/tr>\n<tr>\n<td>.NET<\/td>\n<td>An alias of <code>CapSkipClient<\/code><\/td>\n<td><code>await Task.WhenAll(...)<\/code><\/td>\n<\/tr>\n<tr>\n<td>PHP<\/td>\n<td>An alias only, for source parity<\/td>\n<td>Synchronous, no concurrency<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Node and .NET are already non-blocking, so the alias costs nothing there. PHP is synchronous and the alias buys you nothing at all. In Python the distinction matters: the plain <code>CapSkip<\/code> client blocks the event loop while it polls, so putting it inside a coroutine gives you concurrency on paper and sequential timing in practice.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">What you need<\/h2>\n<ul>\n<li>Python 3.10 or newer<\/li>\n<li><code>pip install capskip<\/code><\/li>\n<li>CapSkip running with its API server on, listening on <code>127.0.0.1:8080<\/code><\/li>\n<li>A list of sitekeys and page URLs to work through<\/li>\n<\/ul>\n<p>Nothing leaves your machine, so there is no rate limit to negotiate and no per-solve meter running while you experiment. Full method signatures for every type live on the <a href=\"https:\/\/capskip.com\/captcha-solving-sdk\/\">CAPTCHA solving SDK<\/a> page.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">The sequential version, and what it costs<\/h2>\n<p>Here is the shape most people start with. It is correct, and it is slow.<\/p>\n<pre data-enlighter-language=\"python\" class=\"EnlighterJSRAW\"># pip install capskip\nfrom capskip import CapSkip\n\nsolver = CapSkip(host=&quot;127.0.0.1&quot;, port=8080)\n\ntargets = [\n    (&quot;SITEKEY_A&quot;, &quot;https:\/\/example.com\/page-a&quot;),\n    (&quot;SITEKEY_B&quot;, &quot;https:\/\/example.com\/page-b&quot;),\n    (&quot;SITEKEY_C&quot;, &quot;https:\/\/example.com\/page-c&quot;),\n]\n\n# Each call blocks until that one CAPTCHA comes back.\nfor sitekey, url in targets:\n    result = solver.recaptcha(sitekey=sitekey, url=url)\n    print(result[&quot;code&quot;][:40])   # the token, truncated for the log<\/pre>\n<p>A reCAPTCHA solve is mostly waiting. Your process sits idle while the solver works, then moves to the next one and sits idle again. Three solves take three solves&#8217; worth of wall clock. Thirty take thirty.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Solve several CAPTCHAs at once with asyncio.gather<\/h2>\n<p>Swap the client, await the calls, and hand them all to <code>gather<\/code>. The types can be mixed: reCAPTCHA, Turnstile and GeeTest in the same batch is fine.<\/p>\n<pre data-enlighter-language=\"python\" class=\"EnlighterJSRAW\"># pip install capskip\nimport asyncio\nfrom capskip import AsyncCapSkip\n\nasync def main():\n    solver = AsyncCapSkip(host=&quot;127.0.0.1&quot;, port=8080)\n\n    # gather starts all three now and waits for the slowest.\n    results = await asyncio.gather(\n        solver.recaptcha(sitekey=&quot;SITEKEY_A&quot;, url=&quot;https:\/\/example.com\/page-a&quot;),\n        solver.turnstile(sitekey=&quot;SITEKEY_B&quot;, url=&quot;https:\/\/example.com\/page-b&quot;),\n        solver.normal(&quot;captcha.png&quot;),\n    )\n\n    for r in results:\n        print(r[&quot;code&quot;][:40])   # token for widgets, text for images\n\nasyncio.run(main())<\/pre>\n<p>Every method returns a dict with the same core fields: <code>captchaId<\/code> and <code>code<\/code>. Turnstile adds <code>userAgent<\/code>, which you must send back with the token when you submit a challenge-page solve. GeeTest adds <code>challenge<\/code>, <code>validate<\/code> and <code>seccode<\/code>, and puts the raw JSON in <code>code<\/code>.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Cap the concurrency with a semaphore<\/h2>\n<p>Do not fire two hundred solves at a local daemon and hope. Solving is CPU work happening on your own machine, so past a certain width you are just queueing against yourself and every individual solve gets slower. A semaphore keeps a fixed number in flight.<\/p>\n<pre data-enlighter-language=\"python\" class=\"EnlighterJSRAW\">import asyncio\nfrom capskip import AsyncCapSkip\n\n# Start at 4 or 5, then measure. More is not automatically faster.\nsem = asyncio.Semaphore(5)\n\nasync def solve_one(solver, sitekey, url):\n    async with sem:\n        return await solver.recaptcha(sitekey=sitekey, url=url)\n\nasync def run(targets):\n    solver = AsyncCapSkip()\n    tasks = [solve_one(solver, k, u) for k, u in targets]\n    return await asyncio.gather(*tasks)<\/pre>\n<p>Pick the number by timing it, not by guessing. Run the same 20 targets at 2, 5 and 10 and keep whichever finishes first on your hardware. The right answer depends on your CPU, not on the SDK.<\/p>\n<h3 style=\"font-size:1.3rem;line-height:1.4;\">One client, not one per task<\/h3>\n<p>Build a single <code>AsyncCapSkip<\/code> and share it across coroutines, as above. Constructing one per task is wasteful and gains you nothing: the client holds configuration, not per-solve state.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Stop one failure killing the batch<\/h2>\n<p>By default <code>gather<\/code> propagates the first exception and you lose the results of everything else that was in flight. Pass <code>return_exceptions=True<\/code> and the exceptions arrive as ordinary items in the results list, so you can sort the winners from the losers.<\/p>\n<pre data-enlighter-language=\"python\" class=\"EnlighterJSRAW\">from capskip import (\n    AsyncCapSkip, ApiException, NetworkException,\n    TimeoutException, ValidationException,\n)\n\nresults = await asyncio.gather(*tasks, return_exceptions=True)\n\nfor target, r in zip(targets, results):\n    if isinstance(r, TimeoutException):\n        print(&quot;timed out, worth retrying:&quot;, target)\n    elif isinstance(r, ApiException):\n        print(&quot;api rejected this one:&quot;, target, r)\n    elif isinstance(r, NetworkException):\n        print(&quot;solver unreachable, stop the run:&quot;, target)\n    elif isinstance(r, Exception):\n        raise r\n    else:\n        print(&quot;ok:&quot;, r[&quot;code&quot;][:40])<\/pre>\n<p>The four exception types are the same in every CapSkip SDK, and all of them derive from a base <code>CapSkipError<\/code> if you would rather catch one type. <code>ValidationException<\/code> means your parameters are wrong and a retry will fail identically. <code>NetworkException<\/code> usually means the app is not running, which is a whole-run problem rather than a per-target one.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Timeouts and polling, which behave differently per type<\/h2>\n<p>Two separate timeouts apply, and a batch that mixes types is governed by both.<\/p>\n<table>\n<thead>\n<tr>\n<th>Option<\/th>\n<th>Default<\/th>\n<th>Applies to<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>defaultTimeout<\/code><\/td>\n<td>120 seconds<\/td>\n<td>Image CAPTCHAs<\/td>\n<\/tr>\n<tr>\n<td><code>recaptchaTimeout<\/code><\/td>\n<td>300 seconds<\/td>\n<td>reCAPTCHA, Turnstile, GeeTest<\/td>\n<\/tr>\n<tr>\n<td><code>pollingInterval<\/code><\/td>\n<td>5 seconds<\/td>\n<td>The <strong>maximum<\/strong> gap between polls<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><code>pollingInterval<\/code> is worth understanding before you tune it. The SDK does not poll on a flat interval. It starts at 250ms and backs off towards the value you set, which is why an SDK solve usually returns sooner than a hand-rolled loop built from the raw API&#8217;s &#8220;wait, then poll every five seconds&#8221; advice. Raising it makes fast solves land later. Lowering it adds request volume for no gain.<\/p>\n<h3 style=\"font-size:1.3rem;line-height:1.4;\">GeeTest challenges expire, so do not pre-build a batch<\/h3>\n<p>This one bites specifically when you go parallel. A GeeTest <code>gt<\/code> value is static per site, but <code>challenge<\/code> is single-use and expires in about a minute. If you collect fifty challenges first and then start solving, the ones at the back of the queue are dead before they are submitted. Fetch each challenge immediately before the solve that uses it.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Full working example<\/h2>\n<pre data-enlighter-language=\"python\" class=\"EnlighterJSRAW\"># pip install capskip\nimport asyncio\nfrom capskip import AsyncCapSkip, ApiException, TimeoutException\n\nTARGETS = [\n    (&quot;SITEKEY_A&quot;, &quot;https:\/\/example.com\/page-a&quot;),\n    (&quot;SITEKEY_B&quot;, &quot;https:\/\/example.com\/page-b&quot;),\n    (&quot;SITEKEY_C&quot;, &quot;https:\/\/example.com\/page-c&quot;),\n]\n\nasync def solve_one(solver, sem, sitekey, url):\n    async with sem:\n        return await solver.recaptcha(sitekey=sitekey, url=url)\n\nasync def main():\n    solver = AsyncCapSkip(host=&quot;127.0.0.1&quot;, port=8080)\n    sem = asyncio.Semaphore(5)\n\n    tasks = [solve_one(solver, sem, k, u) for k, u in TARGETS]\n    results = await asyncio.gather(*tasks, return_exceptions=True)\n\n    tokens = {}\n    for (sitekey, url), r in zip(TARGETS, results):\n        if isinstance(r, (ApiException, TimeoutException)):\n            print(&quot;failed:&quot;, url, r)\n        else:\n            tokens[url] = r[&quot;code&quot;]\n\n    print(len(tokens), &quot;of&quot;, len(TARGETS), &quot;solved&quot;)\n    return tokens\n\nasyncio.run(main())<\/pre>\n<p>That is the whole pattern: one shared client, a semaphore, <code>return_exceptions=True<\/code>, and a dict of tokens at the end. Drop it into a scraper and the CAPTCHA step stops being the bottleneck. The <a href=\"https:\/\/capskip.com\/captcha-solver-for-web-scraping\/\">CAPTCHA solver for web scraping<\/a> page covers where it fits in a wider pipeline.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">The same idea in the other SDKs<\/h2>\n<p>If you are porting this, the concurrency primitive changes but the shape does not. Node is already non-blocking, so the plain client is all you need.<\/p>\n<pre data-enlighter-language=\"js\" class=\"EnlighterJSRAW\">\/\/ npm install capskip\nconst { CapSkip } = require('capskip');\n\nconst solver = new CapSkip({ host: '127.0.0.1', port: 8080 });\n\n\/\/ AsyncCapSkip here is just an alias. Promise.all does the work.\nconst results = await Promise.all([\n  solver.recaptcha('SITEKEY_A', 'https:\/\/example.com\/page-a'),\n  solver.turnstile('SITEKEY_B', 'https:\/\/example.com\/page-b'),\n]);\n\nconsole.log(results.map(r =&gt; r.code));<\/pre>\n<p>.NET is the same story with <code>Task.WhenAll<\/code>, and PHP has no concurrency to offer at all. If you need parallel solving and you get to choose the language, Python is the one with the purpose-built client. The <a href=\"https:\/\/capskip.com\/python-captcha-solver\/\">Python CAPTCHA solver<\/a> page has the rest of the surface.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Common mistakes<\/h2>\n<table>\n<thead>\n<tr>\n<th>Mistake<\/th>\n<th>What happens<\/th>\n<th>Fix<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Using <code>CapSkip<\/code> inside coroutines<\/td>\n<td>Blocks the event loop, so timing stays sequential<\/td>\n<td>Use <code>AsyncCapSkip<\/code><\/td>\n<\/tr>\n<tr>\n<td>No semaphore<\/td>\n<td>Every solve slows down once the queue is deep<\/td>\n<td>Cap in-flight work, start around 5<\/td>\n<\/tr>\n<tr>\n<td>Plain <code>gather<\/code><\/td>\n<td>One failure discards every other result<\/td>\n<td><code>return_exceptions=True<\/code><\/td>\n<\/tr>\n<tr>\n<td>Pre-fetching GeeTest challenges<\/td>\n<td>Later ones expire before submission<\/td>\n<td>Fetch each one just before solving<\/td>\n<\/tr>\n<tr>\n<td>Raising <code>pollingInterval<\/code><\/td>\n<td>Fast solves return later, not sooner<\/td>\n<td>Leave it at the default<\/td>\n<\/tr>\n<tr>\n<td>Proxy set on an image solve<\/td>\n<td>Not supported for image CAPTCHAs<\/td>\n<td>Proxies apply to reCAPTCHA, Turnstile and GeeTest only<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The raw request and response for each type, if you want to see what the SDK is sending, is in the <a href=\"https:\/\/capskip.com\/api-docs\/\">API documentation<\/a>. Python&#8217;s own <a href=\"https:\/\/docs.python.org\/3\/library\/asyncio-task.html\" rel=\"nofollow noopener\" target=\"_blank\">asyncio task reference<\/a> covers <code>gather<\/code> semantics in detail.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Frequently asked questions<\/h2>\n<details style=\"border:1px solid #e2e5ee;border-radius:10px;padding:14px 18px;margin:0 0 12px;\">\n<summary style=\"cursor:pointer;\">\n<h3 style=\"font-size:1.15rem;line-height:1.4;display:inline;margin:0;\">How many CAPTCHAs can I solve at once?<\/h3>\n<\/summary>\n<p style=\"margin:12px 0 0;\">There is no quota to hit, so the limit is your own hardware. Solving happens locally, so concurrency is bounded by CPU rather than by an account tier. Start at five in flight, time a fixed batch, and adjust from there.<\/p>\n<\/details>\n<details style=\"border:1px solid #e2e5ee;border-radius:10px;padding:14px 18px;margin:0 0 12px;\">\n<summary style=\"cursor:pointer;\">\n<h3 style=\"font-size:1.15rem;line-height:1.4;display:inline;margin:0;\">Can I mix CAPTCHA types in one gather call?<\/h3>\n<\/summary>\n<p style=\"margin:12px 0 0;\">Yes. <code>recaptcha<\/code>, <code>turnstile<\/code>, <code>geetest<\/code> and <code>normal<\/code> are all coroutines on the same client and can be awaited together. Remember that image solves use the 120 second timeout while the rest use 300.<\/p>\n<\/details>\n<details style=\"border:1px solid #e2e5ee;border-radius:10px;padding:14px 18px;margin:0 0 12px;\">\n<summary style=\"cursor:pointer;\">\n<h3 style=\"font-size:1.15rem;line-height:1.4;display:inline;margin:0;\">Should I use threads instead?<\/h3>\n<\/summary>\n<p style=\"margin:12px 0 0;\">Only if your surrounding code is already threaded. The work is I\/O bound waiting, which is exactly what asyncio is for, and one event loop is cheaper than a thread pool. If you are stuck on a sync codebase, a thread pool around the plain <code>CapSkip<\/code> client works too.<\/p>\n<\/details>\n<details style=\"border:1px solid #e2e5ee;border-radius:10px;padding:14px 18px;margin:0 0 12px;\">\n<summary style=\"cursor:pointer;\">\n<h3 style=\"font-size:1.15rem;line-height:1.4;display:inline;margin:0;\">Does AsyncCapSkip need to be closed?<\/h3>\n<\/summary>\n<p style=\"margin:12px 0 0;\">The SDK documents no close method or async context manager, so build one client, use it for the run, and let it go out of scope when the process ends.<\/p>\n<\/details>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Summary<\/h2>\n<p>Use <code>AsyncCapSkip<\/code>, share one client, cap in-flight solves with a semaphore, and pass <code>return_exceptions=True<\/code> so a single bad target does not discard the batch. That turns a queue of CAPTCHAs from a serial bottleneck into one wait.<\/p>\n<p>The reason you can widen concurrency freely is that the solver runs on your own machine. There is no per-solve bill and no shared queue to share with strangers, so scaling out is a question of your CPU rather than someone else&#8217;s rate limit. That is the practical difference a local <a href=\"https:\/\/capskip.com\/\">captcha bypass<\/a> tool makes once your batch sizes stop being small.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Python \u7684 AsyncCapSkip \u662f\u771f\u6b63\u7684\u5f02\u6b65\u5ba2\u6237\u7aef\uff0c\u800c\u975e\u522b\u540d\u3002\u4e0b\u9762\u4ecb\u7ecd\u5982\u4f55\u7528 asyncio.gather \u5e76\u53d1\u89e3\u51b3\u4e00\u6279\u9a8c\u8bc1\u7801\uff0c\u5e76\u505a\u597d\u6570\u91cf\u9650\u5236\u4e0e\u9519\u8bef\u5904\u7406\u3002<\/p>","protected":false},"author":1,"featured_media":25111,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"rank_math_title":"Solve CAPTCHAs in Parallel with Python asyncio | CapSkip","rank_math_description":"AsyncCapSkip is a real async client, so you can solve captchas in parallel with asyncio.gather. Here is the code, a concurrency cap, and error handling.","rank_math_focus_keyword":"solve captchas in parallel","footnotes":""},"categories":[70],"tags":[],"class_list":["post-25112","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-captcha"],"_links":{"self":[{"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/posts\/25112","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/comments?post=25112"}],"version-history":[{"count":1,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/posts\/25112\/revisions"}],"predecessor-version":[{"id":25172,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/posts\/25112\/revisions\/25172"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/media\/25111"}],"wp:attachment":[{"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/media?parent=25112"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/categories?post=25112"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/tags?post=25112"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}