{"id":24971,"date":"2026-08-06T10:17:14","date_gmt":"2026-08-06T10:17:14","guid":{"rendered":"https:\/\/capskip.com\/?p=24971"},"modified":"2026-08-06T10:17:14","modified_gmt":"2026-08-06T10:17:14","slug":"geetest-v3-python","status":"publish","type":"post","link":"https:\/\/capskip.com\/zh\/geetest-v3-python\/","title":{"rendered":"\u5982\u4f55\u7528 Python \u8bc6\u522b\u6781\u9a8c v3 \u5e76\u56de\u4f20"},"content":{"rendered":"<p>Most CAPTCHA code assumes one token in, one token out. GeeTest breaks that. A solve returns <strong>three<\/strong> values that have to be submitted together, and the challenge you fed in expires roughly sixty seconds after you fetched it. Both of those catch people out, and neither fails with a helpful message.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Two inputs that behave differently<\/h2>\n<table>\n<thead>\n<tr>\n<th>Value<\/th>\n<th>Lifetime<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>gt<\/code><\/td>\n<td>Static for the site. Cache it freely<\/td>\n<\/tr>\n<tr>\n<td><code>challenge<\/code><\/td>\n<td><strong>Single use, dead in about 60 seconds<\/strong><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Both come from the site&#8217;s own GeeTest init endpoint. The <code>gt<\/code> is effectively a site identifier. The <code>challenge<\/code> is per attempt, and treating it as reusable is the single most common reason a GeeTest integration works in testing and falls apart under load.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Setup<\/h2>\n<pre data-enlighter-language=\"bash\" class=\"EnlighterJSRAW\"># Python 3.10 or newer.\npip install capskip<\/pre>\n<pre data-enlighter-language=\"python\" class=\"EnlighterJSRAW\">from capskip import CapSkip\n\nsolver = CapSkip(\n    host=&quot;127.0.0.1&quot;,\n    port=8080,\n    recaptchaTimeout=300,   # GeeTest uses this, not defaultTimeout\n)<\/pre>\n<p>That timeout distinction is easy to miss. <code>defaultTimeout<\/code> only governs image CAPTCHAs; everything interactive uses <code>recaptchaTimeout<\/code>.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Solving<\/h2>\n<pre data-enlighter-language=\"python\" class=\"EnlighterJSRAW\">result = solver.geetest(\n    gt=&quot;81388ea1fc187e0c335c0a8907ff2625&quot;,\n    challenge=&quot;7cf6a8b1a2c34d5e6f7089abcdef0123&quot;,\n    url=&quot;https:\/\/example.com\/login&quot;,\n)\n\nprint(result[&quot;challenge&quot;])\nprint(result[&quot;validate&quot;])\nprint(result[&quot;seccode&quot;])<\/pre>\n<p>Those three keys are the answer. <code>result[\"code\"]<\/code> is populated too, but for GeeTest it holds the raw JSON string rather than something you can submit, so reaching for it out of habit produces a confusing failure.<\/p>\n<p>Use the <code>challenge<\/code> that comes back, not the one you sent in. They are not always the same value.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">The full flow, in one place<\/h2>\n<p>Fetching and solving have to be adjacent. Anything slow between them risks the expiry:<\/p>\n<pre data-enlighter-language=\"python\" class=\"EnlighterJSRAW\">import time\nimport requests\nfrom capskip import CapSkip\n\nsolver = CapSkip()\nLOGIN = &quot;https:\/\/example.com\/login&quot;\n\n# 1. Fresh pair, cache-busted. Cached init responses hand back dead challenges.\ninit = requests.get(\n    &quot;https:\/\/example.com\/geetest\/init&quot;,\n    params={&quot;t&quot;: int(time.time() * 1000)},\n).json()\n\n# 2. Solve immediately. Do not queue this.\nresult = solver.geetest(gt=init[&quot;gt&quot;], challenge=init[&quot;challenge&quot;], url=LOGIN)\n\n# 3. Post all three together, alongside the real form fields.\nresponse = requests.post(LOGIN, data={\n    &quot;geetest_challenge&quot;: result[&quot;challenge&quot;],\n    &quot;geetest_validate&quot;: result[&quot;validate&quot;],\n    &quot;geetest_seccode&quot;: result[&quot;seccode&quot;],\n    &quot;username&quot;: &quot;...&quot;,\n    &quot;password&quot;: &quot;...&quot;,\n})<\/pre>\n<p>The cache-busting timestamp matters more than it looks. GeeTest init endpoints are often cached by a CDN or a proxy, and a cached response hands you a challenge that was already spent.<\/p>\n<p>Those three field names are the usual GeeTest v3 convention, but a site can rename them. Check the real form before assuming.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Doing several at once<\/h2>\n<p>The trap here is batching wrong. Fetching a pile of challenges and then solving them all guarantees the later ones expire before their turn. Fetch and solve inside the same task:<\/p>\n<pre data-enlighter-language=\"python\" class=\"EnlighterJSRAW\">import asyncio\nfrom capskip import AsyncCapSkip\n\nasync def solve_one(solver, url):\n    init = await fetch_pair(url)          # your own fetch, awaited\n    return await solver.geetest(\n        gt=init[&quot;gt&quot;], challenge=init[&quot;challenge&quot;], url=url,\n    )\n\nasync def main():\n    solver = AsyncCapSkip()\n    return await asyncio.gather(*[solve_one(solver, u) for u in urls])\n\nasyncio.run(main())<\/pre>\n<p>Python is the only CapSkip SDK where <code>AsyncCapSkip<\/code> is a real async client rather than an alias, so this genuinely runs solves in parallel.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">When it fails<\/h2>\n<pre data-enlighter-language=\"python\" class=\"EnlighterJSRAW\">from capskip import (\n    ValidationException, NetworkException, ApiException, TimeoutException,\n)\n\ntry:\n    result = solver.geetest(gt=gt, challenge=challenge, url=page_url)\nexcept ValidationException:\n    pass   # missing gt or challenge\nexcept NetworkException:\n    pass   # CapSkip is not running\nexcept ApiException:\n    pass   # usually a challenge that expired before the solve finished\nexcept TimeoutException:\n    pass   # exceeded recaptchaTimeout<\/pre>\n<p>In practice most GeeTest failures arrive as <code>ApiException<\/code> and mean the challenge died. The fix is fetching later, not retrying with the same pair, because a spent challenge never becomes valid again.<\/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;\">Why is result[&#8220;code&#8221;] not usable?<\/h3>\n<\/summary>\n<p style=\"margin:12px 0 0;\">Because the answer is three values rather than one. <code>code<\/code> keeps the raw JSON for completeness while the SDK expands the useful parts into <code>challenge<\/code>, <code>validate<\/code> and <code>seccode<\/code>. Submit those three.<\/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 cache the challenge between runs?<\/h3>\n<\/summary>\n<p style=\"margin:12px 0 0;\">No. It is single use and expires in about a minute. The <code>gt<\/code> is safe to cache; the challenge never is.<\/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 this cover GeeTest v4?<\/h3>\n<\/summary>\n<p style=\"margin:12px 0 0;\">The <code>geetest<\/code> method targets v3, the slide puzzle built around a <code>gt<\/code> and <code>challenge<\/code> pair. v4 changed the parameter model, so check the current <a href=\"https:\/\/capskip.com\/api-docs\/\">API documentation<\/a> before assuming the same call applies.<\/p>\n<\/details>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Summary<\/h2>\n<p>Fetch <code>gt<\/code> and <code>challenge<\/code> immediately before solving with a cache-busted request, call <code>geetest<\/code>, then post <code>challenge<\/code>, <code>validate<\/code> and <code>seccode<\/code> together. Use the returned challenge rather than your input, and never batch challenge fetching ahead of solving.<\/p>\n<p>Other languages are on the <a href=\"https:\/\/capskip.com\/geetest-solver\/\">GeeTest solver<\/a> page, the wider Python surface on the <a href=\"https:\/\/capskip.com\/python-captcha-solver\/\">Python CAPTCHA solver<\/a> page, and there is a live puzzle on our <a href=\"https:\/\/capskip.com\/captcha-demo\/geetest-v3\/\">GeeTest v3 demo<\/a>. CapSkip handles <a href=\"https:\/\/capskip.com\/\">captcha bypass<\/a> locally, so solve volume is free.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u6781\u9a8c\u8fd4\u56de\u7684\u662f\u4e09\u4e2a\u503c\u800c\u975e token\uff0c\u4f60\u6240\u4f9d\u636e\u7684\u6311\u6218\u7ea6\u4e00\u5206\u949f\u540e\u5931\u6548\u3002\u4e0b\u9762\u662f\u80fd\u5e94\u5bf9\u8fd9\u4e00\u70b9\u7684 Python \u6d41\u7a0b\u3002<\/p>","protected":false},"author":1,"featured_media":24970,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"rank_math_title":"Solve GeeTest v3 in Python | CapSkip","rank_math_description":"GeeTest returns three values in Python, not one token, and the challenge expires in about a minute. Here is the full flow and the mistake that breaks it.","rank_math_focus_keyword":"solve geetest in python","footnotes":""},"categories":[70],"tags":[],"class_list":["post-24971","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\/24971","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=24971"}],"version-history":[{"count":1,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/posts\/24971\/revisions"}],"predecessor-version":[{"id":24991,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/posts\/24971\/revisions\/24991"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/media\/24970"}],"wp:attachment":[{"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/media?parent=24971"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/categories?post=24971"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/tags?post=24971"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}