{"id":26244,"date":"2026-10-05T07:28:16","date_gmt":"2026-10-05T07:28:16","guid":{"rendered":"https:\/\/capskip.com\/?p=26244"},"modified":"2026-10-05T07:28:16","modified_gmt":"2026-10-05T07:28:16","slug":"mechanicalsoup-captcha","status":"publish","type":"post","link":"https:\/\/capskip.com\/zh\/mechanicalsoup-captcha\/","title":{"rendered":"\u5982\u4f55\u5728 MechanicalSoup \u4e2d\u8bc6\u522b reCAPTCHA \u5e76\u63d0\u4ea4\u8868\u5355"},"content":{"rendered":"<p>MechanicalSoup never runs the CAPTCHA widget, so the field the widget would fill in is usually missing from the form, and setting it raises LinkNotFoundError. The fix for a MechanicalSoup CAPTCHA takes three steps. Read the sitekey from the page MechanicalSoup already parsed, and solve it with CapSkip running on your own Windows machine. Then add the g-recaptcha-response field yourself with new_control before you call submit_selected. MechanicalSoup handles the rest: the session cookie, the hidden CSRF field and the Referer header a strict server checks for. This guide covers the reCAPTCHA checkbox, the invisible button, v3, Enterprise, the other widgets CapSkip solves, many logins at once and running the solver on another machine.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">What you need<\/h2>\n<ul>\n<li>CapSkip running on a Windows machine, with the reCAPTCHA solver switched on.<\/li>\n<li>Python 3.10 or later, the capskip package and MechanicalSoup. The samples were run with capskip 1.3.0 and MechanicalSoup 1.4.0, which install requests, Beautiful Soup and lxml with them.<\/li>\n<li>The URL of the page with the form. The sitekey, the form fields and the widget type all come out of that page&#8217;s HTML.<\/li>\n<li>An address for the solver. In Local mode CapSkip answers on 127.0.0.1 for that device only. In Server mode it listens on your network address or public IP, so a script on a VPS or another PC can call it over the API. Both are set under <a href=\"https:\/\/capskip.com\/setup-guide\/#connection-settings\">connection settings<\/a>.<\/li>\n<\/ul>\n<div data-no-translation>\n<pre data-enlighter-language=\"bash\" class=\"EnlighterJSRAW\"># Python 3.10 or later; MechanicalSoup brings requests, bs4 and lxml\npip install capskip mechanicalsoup<\/pre>\n<\/div>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Step 1: open the page and find the sitekey<\/h2>\n<p>A StatefulBrowser is a requests session with a parsed page attached. It keeps every cookie the site sets, follows redirects, and remembers the address it ended up on. Open the login page and look for the reCAPTCHA element:<\/p>\n<div data-no-translation>\n<pre data-enlighter-language=\"python\" class=\"EnlighterJSRAW\"># pip install capskip mechanicalsoup\nimport mechanicalsoup\n\nPAGE_URL = &quot;https:\/\/example.com\/login&quot;\n\n# One StatefulBrowser per session: it keeps cookies, follows redirects\n# and sends the current page as Referer when it submits a form.\nbrowser = mechanicalsoup.StatefulBrowser(raise_on_404=True)\nbrowser.open(PAGE_URL)\n\n# The checkbox div, or the button on invisible reCAPTCHA, holds the key.\nwidget = browser.page.select_one(&quot;.g-recaptcha[data-sitekey]&quot;)\nif widget is None:\n    raise RuntimeError(&quot;No reCAPTCHA widget in the HTML; see the v3 section.&quot;)\nsitekey = widget[&quot;data-sitekey&quot;]\n\n# The form around the widget; a page often has a search form first.\nbrowser.select_form(widget.find_parent(&quot;form&quot;) or 'form[method=&quot;post&quot; i]')\nprint(sitekey, browser.url)<\/pre>\n<\/div>\n<p>The checkbox widget is a div with the class g-recaptcha and a data-sitekey attribute. Invisible reCAPTCHA usually puts the same class and attribute on the submit button instead, so one selector finds both. A page that loads api.js with render=explicit builds the widget from its own script and passes the key to grecaptcha.render, so read it from that call in DevTools. Select the form around the widget rather than the first form on the page. Many sites put a search form in the header, and select_form picks that one by default. When the widget sits outside its form, the fallback takes the first form that posts, so pass the login form&#8217;s id instead if the page has two.<\/p>\n<p>browser.url is the address the page came from after any redirects. That is the URL to send to the solver, because a sitekey is registered for its site&#8217;s domains and a solve for the wrong page is wasted. raise_on_404 makes a missing page fail loudly at open rather than further down.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Step 2: solve the reCAPTCHA with CapSkip<\/h2>\n<p>One call to the capskip package does the solving. CapSkip solves the CAPTCHA on your Windows machine and returns the token that the widget would have put in the form:<\/p>\n<div data-no-translation>\n<pre data-enlighter-language=\"python\" class=\"EnlighterJSRAW\">from capskip import CapSkip\n\nsolver = CapSkip(host=&quot;127.0.0.1&quot;, port=8080)\n\n# enterprise.js in place of api.js means reCAPTCHA Enterprise.\nenterprise = int(&quot;recaptcha\/enterprise.js&quot; in str(browser.page))\nif widget.name == &quot;button&quot; and widget.get(&quot;data-action&quot;):\n    # Google's v3 button binding: a g-recaptcha button with an action is v3.\n    result = solver.recaptcha(sitekey=sitekey, url=browser.url, version=&quot;v3&quot;,\n                              action=widget[&quot;data-action&quot;], enterprise=enterprise)\nelse:\n    # A g-recaptcha button, or data-size=&quot;invisible&quot;, is the invisible widget.\n    invisible = widget.name == &quot;button&quot; or widget.get(&quot;data-size&quot;) == &quot;invisible&quot;\n    result = solver.recaptcha(sitekey=sitekey, url=browser.url,\n                              invisible=int(invisible), enterprise=enterprise)\n\ntoken = result[&quot;code&quot;]\nprint(token[:40])   # what the widget would have written into the form<\/pre>\n<\/div>\n<p>The checkbox is the bare call, and invisible reCAPTCHA is the same call with invisible set to 1. A g-recaptcha button that also carries data-action is Google&#8217;s v3 button binding, so the script solves it as v3 with that action. Pages that load enterprise.js instead of api.js run reCAPTCHA Enterprise, and enterprise=1 tells CapSkip so. The token sits in result[&quot;code&quot;]. It is valid for about two minutes and verifies only once, so solve right before you submit and solve again for every login.<\/p>\n<p>recaptcha polls for up to recaptchaTimeout, 300 seconds by default. Set a different value on the constructor, because a timeout passed per call raises TypeError: the method already passes one of its own. CapSkip keeps its own clock as well. A task waits up to 250 seconds (Wait Timeout) for a free reCAPTCHA thread, and the solve then has a limit of its own, Row Timeout, 250 seconds by default. When one of those runs out, CapSkip fails the task and the SDK raises ApiException with ERROR_CAPTCHA_UNSOLVABLE. The CapSkip client used here sets no timeout on its own HTTP calls, so a CapSkip that accepts the connection and never answers holds the call open.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Step 3: add the field the widget would have written, then submit<\/h2>\n<p>This is the step that trips up every MechanicalSoup CAPTCHA script. In a real browser, Google&#8217;s script creates a hidden textarea named g-recaptcha-response and fills it when the CAPTCHA is solved. MechanicalSoup parses the HTML the server sent and runs no JavaScript, so on most pages that field does not exist. Assigning to it the usual way then raises LinkNotFoundError with the message No valid element named g-recaptcha-response. new_control adds the field to the selected form:<\/p>\n<div data-no-translation>\n<pre data-enlighter-language=\"python\" class=\"EnlighterJSRAW\"># This line raises LinkNotFoundError on most pages: the widget script\n# creates the field in a real browser, so the parsed HTML lacks it.\n# browser[&quot;g-recaptcha-response&quot;] = token\n\n# Add the field the widget would have written, replacing any copy.\nbrowser.new_control(&quot;hidden&quot;, &quot;g-recaptcha-response&quot;, token)\nbrowser[&quot;email&quot;] = &quot;YOUR_EMAIL&quot;\nbrowser[&quot;password&quot;] = &quot;YOUR_PASSWORD&quot;\n\nresponse = browser.submit_selected()\nprint(response.status_code, browser.url)<\/pre>\n<\/div>\n<p>new_control removes any field of the same name before adding its own, which matters on pages that keep Google&#8217;s noscript fallback. That fallback includes a textarea with the same name, and MechanicalSoup&#8217;s parser reads it as part of the form. On those pages the plain assignment works too, but new_control covers both kinds of page with one line.<\/p>\n<p>submit_selected sends every field the form carries, the hidden CSRF token included, on the same session that loaded the page. It also sends the page&#8217;s address as the Referer header. Plain requests sends none, and neither does MechanicalSoup&#8217;s lower-level Browser.submit method. Some frameworks, Django among them, refuse an HTTPS form post without a Referer or Origin, however right the cookie and token are. So stay with submit_selected on the browser that opened the page. If the form has several submit buttons, pass the name of the one you want as btnName. And a 200 does not prove the login worked. Many sites answer a refused CAPTCHA with the same form and a 200, so check browser.page for something only a signed-in page shows.<\/p>\n<h3 style=\"font-size:1.3rem;line-height:1.4;\">reCAPTCHA v3 has no widget to find<\/h3>\n<p>v3 asks the user nothing. The site loads Google&#8217;s script with its sitekey in the address, after render=, and its own script asks for a token with an action name such as login. Most v3 pages then write the token into a hidden input themselves. Read both values out of the HTML, note whether the script is api.js or enterprise.js, and pass them on with version set to v3. When a page binds v3 to a button instead, Step 2 already handles it.<\/p>\n<div data-no-translation>\n<pre data-enlighter-language=\"python\" class=\"EnlighterJSRAW\">import re\n\n# A v3 page has no widget element; the key sits in the script tags.\nhtml = str(browser.page)\nkey = re.search(r&quot;recaptcha\/(api|enterprise)\\.js\\?[^\\&quot;'&lt;&gt;\\s]*?\\brender=(?!explicit\\b)([\\w-]+)&quot;, html)\naction = re.search(r&quot;execute\\(\\s*[^,()]+,\\s*\\{\\s*[\\&quot;']?action[\\&quot;']?\\s*:\\s*[\\&quot;']([\\w\/]+)&quot;, html)\nif key is None:\n    raise RuntimeError(&quot;No v3 render key in the HTML; check DevTools.&quot;)\n\nv3 = solver.recaptcha(sitekey=key.group(2), url=browser.url, version=&quot;v3&quot;,\n                      enterprise=int(key.group(1) == &quot;enterprise&quot;),\n                      **({&quot;action&quot;: action.group(1)} if action else {}))\n\nbrowser.select_form('form[method=&quot;post&quot; i]')\n# The field the page's own script fills in; read its name in DevTools.\nbrowser.new_control(&quot;hidden&quot;, &quot;g-recaptcha-response&quot;, v3[&quot;code&quot;])<\/pre>\n<\/div>\n<p>On a v3 page, run Step 1 only as far as browser.open, then the lines of Step 2 that create the solver, then this block, then Step 3&#8217;s credentials and submit_selected. The action matters because the site&#8217;s backend usually checks that the token was issued for the action it expected. When the pattern finds no action, the call goes without one and CapSkip uses verify, which a site expecting login rejects. If the execute call sits in an external script, read the action in DevTools and pass it yourself. The field name is whatever the page&#8217;s script fills in, often g-recaptcha-response but not always, so check it in DevTools by submitting once by hand. Then fill in the credentials and call submit_selected as above. Google&#8217;s <a href=\"https:\/\/developers.google.com\/recaptcha\/docs\/v3\" rel=\"nofollow noopener\" target=\"_blank\">reCAPTCHA v3 documentation<\/a> shows the render and execute calls that these patterns match. The <a href=\"https:\/\/capskip.com\/recaptcha-v3-python\/\">guide to solving reCAPTCHA v3 in Python<\/a> covers the call itself in more depth.<\/p>\n<h3 style=\"font-size:1.3rem;line-height:1.4;\">Other widgets work the same way<\/h3>\n<p>Every widget CapSkip solves follows the same pattern in MechanicalSoup: read the key, solve, add the field the widget would have created, submit. Only the selector, the field name and the method change:<\/p>\n<table>\n<thead>\n<tr>\n<th>Widget<\/th>\n<th>Where the key is<\/th>\n<th>Field to add<\/th>\n<th>capskip method<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>reCAPTCHA v2 checkbox<\/td>\n<td>div.g-recaptcha, data-sitekey<\/td>\n<td>g-recaptcha-response<\/td>\n<td>solver.recaptcha()<\/td>\n<\/tr>\n<tr>\n<td>reCAPTCHA v2 invisible<\/td>\n<td>the button with the g-recaptcha class, or a div with data-size=invisible<\/td>\n<td>g-recaptcha-response<\/td>\n<td>solver.recaptcha() with invisible=1<\/td>\n<\/tr>\n<tr>\n<td>reCAPTCHA v3<\/td>\n<td>api.js?render= or enterprise.js?render= in the script address, or a g-recaptcha button with data-action<\/td>\n<td>the input the page&#8217;s script fills<\/td>\n<td>solver.recaptcha() with version=&quot;v3&quot; and the action<\/td>\n<\/tr>\n<tr>\n<td>reCAPTCHA Enterprise<\/td>\n<td>enterprise.js in place of api.js<\/td>\n<td>as for v2 or v3<\/td>\n<td>solver.recaptcha() with enterprise=1<\/td>\n<\/tr>\n<tr>\n<td>Cloudflare Turnstile widget<\/td>\n<td>div.cf-turnstile, data-sitekey<\/td>\n<td>cf-turnstile-response<\/td>\n<td>solver.turnstile()<\/td>\n<\/tr>\n<tr>\n<td>Friendly Captcha<\/td>\n<td>div.frc-captcha, data-sitekey<\/td>\n<td>frc-captcha-response on v2, frc-captcha-solution on v1<\/td>\n<td>solver.friendly_captcha() with version=&quot;v1&quot; or &quot;v2&quot;<\/td>\n<\/tr>\n<tr>\n<td>CaptchaFox<\/td>\n<td>div.captchafox, data-sitekey<\/td>\n<td>cf-captcha-response<\/td>\n<td>solver.captchafox()<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>CaptchaFox is the one with an extra rule. Its token belongs to the browser that minted it. So when the result carries a userAgent, set it on the browser with set_user_agent before you submit, because MechanicalSoup otherwise sends python-requests and its own name. The <a href=\"https:\/\/capskip.com\/captchafox-python\/\">guide to solving CaptchaFox in Python<\/a> covers the widget build and the retries.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Full working example<\/h2>\n<p>Here is a complete MechanicalSoup CAPTCHA login in one file. login opens the page in a new browser, finds the checkbox, invisible or v3 reCAPTCHA, solves it, adds the field and submits:<\/p>\n<div data-no-translation>\n<pre data-enlighter-language=\"python\" class=\"EnlighterJSRAW\"># pip install capskip mechanicalsoup\n# login.py: run with python login.py\nimport os\nimport re\n\nimport mechanicalsoup\nfrom capskip import CapSkip, CapSkipError, TimeoutException\n\nPAGE_URL = &quot;https:\/\/example.com\/login&quot;\n\n# render= carries a v3 key on api.js or enterprise.js; render=explicit is a v2 page.\nV3_KEY = re.compile(r&quot;recaptcha\/(api|enterprise)\\.js\\?[^\\&quot;'&lt;&gt;\\s]*?\\brender=(?!explicit\\b)([\\w-]+)&quot;)\n# The action passed to execute, whether the key is a string or a variable.\nV3_ACTION = re.compile(r&quot;execute\\(\\s*[^,()]+,\\s*\\{\\s*[\\&quot;']?action[\\&quot;']?\\s*:\\s*[\\&quot;']([\\w\/]+)&quot;)\n\nsolver = CapSkip(\n    apiKey=os.getenv(&quot;CAPSKIP_API_KEY&quot;, &quot;capskip&quot;),\n    host=os.getenv(&quot;CAPSKIP_HOST&quot;, &quot;127.0.0.1&quot;),\n    port=int(os.getenv(&quot;CAPSKIP_PORT&quot;, &quot;8080&quot;)),\n)\n\n\ndef solve_page(browser):\n    &quot;&quot;&quot;Solve the page's reCAPTCHA and select its form. Returns the token.&quot;&quot;&quot;\n    html = str(browser.page)\n    enterprise = int(&quot;recaptcha\/enterprise.js&quot; in html)\n    widget = browser.page.select_one(&quot;.g-recaptcha[data-sitekey]&quot;)\n    if widget is not None:\n        browser.select_form(widget.find_parent(&quot;form&quot;) or 'form[method=&quot;post&quot; i]')\n        if widget.name == &quot;button&quot; and widget.get(&quot;data-action&quot;):\n            # Google's v3 button binding: plain api.js, the action on the button.\n            return solver.recaptcha(sitekey=widget[&quot;data-sitekey&quot;], url=browser.url, version=&quot;v3&quot;,\n                                    action=widget[&quot;data-action&quot;], enterprise=enterprise)[&quot;code&quot;]\n        invisible = widget.name == &quot;button&quot; or widget.get(&quot;data-size&quot;) == &quot;invisible&quot;\n        return solver.recaptcha(sitekey=widget[&quot;data-sitekey&quot;], url=browser.url,\n                                invisible=int(invisible), enterprise=enterprise)[&quot;code&quot;]\n    key = V3_KEY.search(html)\n    if key is None:\n        raise RuntimeError(f&quot;No reCAPTCHA found on {browser.url}&quot;)\n    action = V3_ACTION.search(html)\n    browser.select_form('form[method=&quot;post&quot; i]')\n    return solver.recaptcha(sitekey=key.group(2), url=browser.url, version=&quot;v3&quot;,\n                            enterprise=int(key.group(1) == &quot;enterprise&quot;),\n                            **({&quot;action&quot;: action.group(1)} if action else {}))[&quot;code&quot;]\n\n\ndef login(email, password, page_url=PAGE_URL):\n    # A new browser per login, so sessions never share cookies.\n    with mechanicalsoup.StatefulBrowser(raise_on_404=True) as browser:\n        browser.open(page_url)\n        token = solve_page(browser)\n        browser.new_control(&quot;hidden&quot;, &quot;g-recaptcha-response&quot;, token)\n        browser[&quot;email&quot;] = email\n        browser[&quot;password&quot;] = password\n        response = browser.submit_selected()\n        return response.status_code, browser.url\n\n\nif __name__ == &quot;__main__&quot;:\n    try:\n        print(login(&quot;YOUR_EMAIL&quot;, &quot;YOUR_PASSWORD&quot;))\n    except TimeoutException:\n        raise SystemExit(&quot;gave up after recaptchaTimeout&quot;)\n    except CapSkipError as exc:\n        raise SystemExit(f&quot;solve failed: {type(exc).__name__}: {exc}&quot;)\n    except mechanicalsoup.LinkNotFoundError as exc:\n        # A 404, or a form or field the page does not have.\n        raise SystemExit(f&quot;not found: {str(exc) or PAGE_URL}&quot;)\n    except RuntimeError as exc:\n        raise SystemExit(str(exc))<\/pre>\n<\/div>\n<p>Each call to login builds its own StatefulBrowser and closes it when done, so two logins never share cookies. The with block closes the session even when a step fails. Most failures say where to look. A LinkNotFoundError means a 404, a missing form or a field name that does not match the page. A RuntimeError means the page shows no reCAPTCHA the script recognises, so open it in a browser and check what loads. If it loads fine there, print browser.open(page_url).status_code: a 403 means the site refused the default user agent. A CapSkipError comes from the solve itself, so the errors table below applies.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Many logins at once, and the solver on another machine<\/h2>\n<p>MechanicalSoup is synchronous, and so is the CapSkip client used here. So when a MechanicalSoup CAPTCHA job has many logins to get through, threads are the simplest way to run them side by side. Keep one StatefulBrowser per thread, which login already does, and cap the pool at ten, CapSkip&#8217;s default Max. Threads for reCAPTCHA. A task queued past that waits for a free thread, and a long queue eats into the 300 seconds the SDK polls for. Raise Max. Threads in CapSkip&#8217;s reCAPTCHA settings before you raise max_workers. Save the full example as login.py and run this from a second file next to it:<\/p>\n<div data-no-translation>\n<pre data-enlighter-language=\"python\" class=\"EnlighterJSRAW\"># pool.py: save next to login.py, the full example, and run this file\nfrom concurrent.futures import ThreadPoolExecutor\n\nfrom login import login\n\naccounts = [(&quot;YOUR_EMAIL_1&quot;, &quot;YOUR_PASSWORD_1&quot;), (&quot;YOUR_EMAIL_2&quot;, &quot;YOUR_PASSWORD_2&quot;)]\n\n\ndef attempt(account):\n    # One failed login must not stop the others.\n    try:\n        return login(*account)\n    except Exception as exc:\n        return f&quot;{type(exc).__name__}: {exc}&quot;\n\n\n# Ten at a time, to match CapSkip's default Max. Threads for reCAPTCHA.\nwith ThreadPoolExecutor(max_workers=10) as pool:\n    for account, outcome in zip(accounts, pool.map(attempt, accounts)):\n        print(account[0], outcome)<\/pre>\n<\/div>\n<p>pool.map returns results in the order the accounts were given, and attempt turns a failure into a value, so one bad login does not stop the rest. Twenty-five logins through this pool never had more than ten solves in flight against a mock solver.<\/p>\n<p>127.0.0.1 is right while the script and CapSkip share a Windows PC. Move the script to a VPS, a scheduled job on another box or a container, and loopback points at the wrong machine, so the first solve raises NetworkException. Switch CapSkip to Server mode and it listens on your network address or public IP, so any of those can call it over the same API. Use a static public IP when the route crosses the internet. CapSkip opens its port to every address when Server mode starts, so to keep it to the machines you expect, add a Windows Firewall block rule for the others. It is still your own machine, and solves are still unmetered. The client reads no environment variables by itself, which is why the full example reads CAPSKIP_HOST and passes it in.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Common errors and what they mean<\/h2>\n<table>\n<thead>\n<tr>\n<th>What you see<\/th>\n<th>Cause<\/th>\n<th>Fix<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>LinkNotFoundError: No valid element named g-recaptcha-response<\/td>\n<td>The widget&#8217;s script never ran, so the field it creates is not in the parsed form<\/td>\n<td>Add it with new_control before submitting<\/td>\n<\/tr>\n<tr>\n<td>The token is posted with the search form, or select_form raises LinkNotFoundError<\/td>\n<td>The page has another form first, or the widget sits outside its form<\/td>\n<td>Select the form around the widget, or the login form by its id<\/td>\n<\/tr>\n<tr>\n<td>403 or a CSRF error with a fresh token<\/td>\n<td>The post went out on a new session, through requests.post, or through Browser.submit, so the cookie or the Referer was missing<\/td>\n<td>Fill and submit on the browser that opened the page, with submit_selected<\/td>\n<\/tr>\n<tr>\n<td>The site says the CAPTCHA failed although CapSkip returned a token<\/td>\n<td>An invisible widget was solved as a checkbox, a v3 action differs from the page&#8217;s, an Enterprise page was solved without enterprise=1, or the token was older than two minutes<\/td>\n<td>Pass invisible=1, enterprise=1 or the page&#8217;s action, and solve right before submitting<\/td>\n<\/tr>\n<tr>\n<td>TypeError: capskip.solver.CapSkip.solve() got multiple values for keyword argument timeout<\/td>\n<td>A timeout was passed to recaptcha per call<\/td>\n<td>Set recaptchaTimeout on the CapSkip constructor instead<\/td>\n<\/tr>\n<tr>\n<td>ApiException with ERROR_CAPTCHA_UNSOLVABLE<\/td>\n<td>CapSkip failed the task on its own clock, or the sitekey and page URL do not belong together<\/td>\n<td>Send browser.url and the page&#8217;s own sitekey, and keep at most ten solves in flight<\/td>\n<\/tr>\n<tr>\n<td>TimeoutException after 300 seconds<\/td>\n<td>The wait for a free thread plus the solve ran past the SDK&#8217;s limit<\/td>\n<td>Cap the pool at Max. Threads, or raise recaptchaTimeout on the constructor<\/td>\n<\/tr>\n<tr>\n<td>No reCAPTCHA found, on a page that loads fine in a real browser<\/td>\n<td>The site answered with a 403 because it refuses the default user agent, which names python-requests and MechanicalSoup<\/td>\n<td>Pass a browser user agent as user_agent when you create the StatefulBrowser<\/td>\n<\/tr>\n<tr>\n<td>NetworkException on the first solve<\/td>\n<td>CapSkip is not running, the host or port is wrong, or the script runs on another machine<\/td>\n<td>Start CapSkip, then check whether it should be in Local mode or Server mode<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">FAQ<\/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 use MechanicalSoup instead of plain requests?<\/h3>\n<\/summary>\n<p style=\"margin:12px 0 0;\">It saves you rebuilding the form. With requests you collect the hidden fields yourself, keep the session, resolve the form&#8217;s action and set the Referer, and missing any one of those gets a good token rejected. MechanicalSoup does all four from the parsed page, so the only thing you add for the CAPTCHA is the one field. For pages that build their forms with JavaScript, neither is enough, and a browser tool is the better fit.<\/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 the session stay logged in after the CAPTCHA?<\/h3>\n<\/summary>\n<p style=\"margin:12px 0 0;\">Yes. The StatefulBrowser keeps the cookies the site sets after the login, so the pages you open next on the same browser are logged in. The full example closes its browser when login returns, so move your follow-up requests inside that with block. Or create the browser without with, return it, and call browser.close() when you are done.<\/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 a MechanicalSoup script on a VPS use CapSkip on my Windows PC?<\/h3>\n<\/summary>\n<p style=\"margin:12px 0 0;\">Yes. Put CapSkip in Server mode under connection settings so it listens on a network address instead of loopback. Then set CAPSKIP_HOST where the script runs, which the full example reads. A VPS, a cron job and a container all connect over the same HTTP API. Use a static public IP when the route crosses the internet, and add a Windows Firewall block rule for addresses that should not reach the port. The solver stays on hardware you own, so more logins never change what you pay.<\/p>\n<\/details>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">The short version<\/h2>\n<p>A MechanicalSoup CAPTCHA fix comes down to one missing field, because MechanicalSoup cannot run the widget that writes it. Open the page with a StatefulBrowser and select the form around the widget. Read the sitekey from the g-recaptcha element, or the render key and action from a v3 page&#8217;s scripts, and solve with CapSkip using browser.url. Add the g-recaptcha-response field with new_control, fill in the rest and call submit_selected on the same browser, which brings the cookies, the CSRF field and the Referer along. Solve right before you submit, keep at most ten logins in flight, and switch CapSkip to Server mode when the script runs elsewhere.<\/p>\n<ul>\n<li>How reCAPTCHA v2 works and what the solver covers: <a href=\"https:\/\/capskip.com\/recaptcha-v2-solver\/\">the reCAPTCHA v2 solver page<\/a>.<\/li>\n<li>Every CAPTCHA type the Python package handles: <a href=\"https:\/\/capskip.com\/python-captcha-solver\/\">the Python CAPTCHA solver page<\/a>.<\/li>\n<\/ul>\n<p>MechanicalSoup keeps a scraper light because it never starts a browser. Pair it with a <a href=\"https:\/\/capskip.com\/\">captcha solver<\/a> that runs on your own Windows machine, and the heavy work stays on hardware you control, with no per-solve bill attached.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>MechanicalSoup \u4ece\u4e0d\u8fd0\u884c\u5c0f\u7ec4\u4ef6\u7684 JavaScript\uff0c\u6240\u4ee5\u8868\u5355\u91cc\u7f3a\u5c11 g-recaptcha-response \u5b57\u6bb5\uff0c\u7ed9\u5b83\u8d4b\u503c\u4f1a\u629b\u51fa LinkNotFoundError\u3002\u672c\u6587\u7ed9\u51fa\u5b8c\u6574\u505a\u6cd5\uff1a\u7528 CapSkip \u8bc6\u522b reCAPTCHA\uff0c\u4eb2\u624b\u52a0\u4e0a\u8fd9\u4e2a\u5b57\u6bb5\uff0c\u7136\u540e\u63d0\u4ea4\u8868\u5355\uff0c\u9690\u5f62 reCAPTCHA \u548c v3 \u4e5f\u4e00\u5e76\u6db5\u76d6\u3002<\/p>","protected":false},"author":1,"featured_media":26243,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"rank_math_title":"MechanicalSoup CAPTCHA: Solve reCAPTCHA in Forms | CapSkip","rank_math_description":"A MechanicalSoup captcha fix: solve the reCAPTCHA with CapSkip, add the g-recaptcha-response field the widget never wrote, then call submit_selected.","rank_math_focus_keyword":"mechanicalsoup captcha","footnotes":""},"categories":[70],"tags":[],"class_list":["post-26244","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\/26244","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=26244"}],"version-history":[{"count":2,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/posts\/26244\/revisions"}],"predecessor-version":[{"id":26248,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/posts\/26244\/revisions\/26248"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/media\/26243"}],"wp:attachment":[{"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/media?parent=26244"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/categories?post=26244"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/tags?post=26244"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}