How to Solve reCAPTCHA with the data-s Parameter in Python

Google’s own pages ship an extra value on the reCAPTCHA widget, and it is called data-s. Solve one of those the ordinary way and you get a token back that the page then rejects. The fix is one more argument: read data-s out of the page HTML, pass it to the solver, and submit the token straight away.
The recaptcha data-s parameter is the piece almost everyone misses, mostly because it does not exist on normal sites. Here is where it lives, what to call it, and a script that works end to end.
What data-s actually is
It is a short-lived, single-use value that Google attaches to reCAPTCHA widgets on its own properties. The “unusual traffic” interstitial on Google Search is the one you will hit most often. The value ties the challenge to that specific page load, so the token you get back is only valid against the same request context.
Three things follow from that, and all three cause failures:
- It expires. Treat it like a nonce. Fetch the page, solve, submit. Do not cache it between runs.
- It is Google-only. A reCAPTCHA on someone else’s site has no
data-s. Sending one anyway is a parameter error, not a harmless extra. - It is not the Enterprise flag. These get confused constantly.
data-sandenterpriseare separate options that solve different problems, and the reCAPTCHA Enterprise solver page covers the second one.
What you need
- CapSkip running locally. It listens on
127.0.0.1:8080by default, and the setup guide covers the install. - Python 3.10 or newer.
- The SDK and an HTTP client.
# the CapSkip SDK plus requests for fetching the page pip install capskip requests
Where to find the data-s value
Both values you need sit on the same element. The widget renders as a div carrying data-sitekey and, on Google’s pages only, data-s:
<div class="g-recaptcha"
data-sitekey="6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-"
data-s="SGVsbG8gdGhlcmUsIHRoaXMgaXMgYSBvbmUtdGltZSB2YWx1ZQ">
</div>The sitekey is stable and you can hardcode it. The data-s value changes on every load, so it has to be scraped at run time:
# pip install capskip requests import re import requests PAGE = "https://www.google.com/search?q=example" # Keep one session. The value is tied to the request that # produced it, cookies included. session = requests.Session() html = session.get(PAGE, timeout=30).text sitekey = re.search(r'data-sitekey="([^"]+)"', html).group(1) datas = re.search(r'data-s="([^"]+)"', html).group(1) print(sitekey, datas) # second value is single use
If the second match comes back empty, you are not on a Google page and you do not need this parameter at all. Solve it as an ordinary v2 challenge instead.
Pass it as datas, not data-s
This is the naming trap. The raw HTTP API takes the parameter exactly as it appears in the HTML, data-s. The SDKs cannot use that name, because a hyphen is not legal in a Python keyword argument, so every SDK exposes it as datas.
| Where | Name |
|---|---|
| Page HTML | data-s |
Raw API on in.php | data-s |
| Python, Node.js, PHP, .NET SDKs | datas |
With that sorted, the solve is one call:
from capskip import CapSkip
solver = CapSkip(host="127.0.0.1", port=8080)
result = solver.recaptcha(
sitekey=sitekey,
url=PAGE,
datas=datas, # the data-s value, scraped seconds ago
)
print(result["code"]) # g-recaptcha-response tokenFull working example
Fetch, scrape, solve, submit, all on one session so the cookies and the IP stay consistent:
# pip install capskip requests
import re
import requests
from capskip import (
CapSkip, ApiException, NetworkException, TimeoutException,
)
PAGE = "https://www.google.com/search?q=example"
session = requests.Session()
solver = CapSkip(host="127.0.0.1", port=8080, recaptchaTimeout=300)
html = session.get(PAGE, timeout=30).text
sitekey = re.search(r'data-sitekey="([^"]+)"', html).group(1)
datas = re.search(r'data-s="([^"]+)"', html).group(1)
try:
result = solver.recaptcha(sitekey=sitekey, url=PAGE, datas=datas)
except NetworkException:
raise SystemExit("CapSkip is not running on 127.0.0.1:8080")
except TimeoutException:
raise SystemExit("solve took longer than recaptchaTimeout")
except ApiException as err:
raise SystemExit(f"API refused the task: {err}")
# Submit immediately. The token is good for about two minutes.
response = session.post(
PAGE,
data={"g-recaptcha-response": result["code"]},
timeout=30,
)
print(response.status_code)Two details in there matter more than they look. The session is reused, so the solve and the submit share cookies. And the submit happens right after the solve, because a reCAPTCHA token stops being accepted roughly two minutes after it is issued.
Behind a proxy
If you fetched the page through a proxy, solve through the same one. The challenge is bound to the request that created it, and a solve from a different network path is the second most common cause of a rejected token.
result = solver.recaptcha(
sitekey=sitekey,
url=PAGE,
datas=datas,
proxy={"type": "HTTPS", "uri": "user:[email protected]:3128"},
)Proxies work for reCAPTCHA, Turnstile and GeeTest. They do nothing for image CAPTCHAs, which never touch the target site.
The same call without the SDK
The API is 2captcha compatible and runs on your machine, so any HTTP client will do. Here the parameter keeps its hyphen:
# submit the task, get an ID back
curl -s -X POST "http://127.0.0.1:8080/in.php" \
-d "key=capskip" \
-d "method=userrecaptcha" \
-d "googlekey=YOUR_SITEKEY" \
-d "pageurl=https://www.google.com/search?q=example" \
-d "data-s=THE_DATA_S_VALUE" \
-d "json=1"
# {"status":1,"request":"2122988149"}Then poll res.php for the token. The full two-call flow, including the polling delays, is in solving CAPTCHA with cURL.
Other SDKs use the same datas spelling. Node.js takes it in the options object, and .NET puts it in the options dictionary:
// npm install capskip
const { CapSkip } = require('capskip');
const solver = new CapSkip({ host: '127.0.0.1', port: 8080 });
const result = await solver.recaptcha(sitekey, pageUrl, {
datas: dataS, // same value, same rules
});
console.log(result.code);Common errors
| What you see | Cause | Fix |
|---|---|---|
ERROR_GOOGLEKEY | The sitekey never made it into the request, or the regex matched the wrong attribute | Print the scraped value before solving. Details in fixing ERROR_GOOGLEKEY |
ERROR_BAD_PARAMETERS | data-s sent as an empty string, or sent to a non-Google page | Only pass it when the attribute is actually present |
ERROR_CAPTCHA_UNSOLVABLE | Usually a stale data-s: the page was fetched minutes before the solve | Fetch and solve back to back. See ERROR_CAPTCHA_UNSOLVABLE |
| Token accepted by the solver, rejected by Google | Different cookies or a different IP between fetch and submit | Reuse one session, and use the same proxy for both |
NetworkException | CapSkip is not running | Start the app, confirm the port in settings |
Every parameter and response shape is listed in the API documentation.
Frequently asked questions
Do I need data-s on a normal website?
No. It only appears on Google’s own pages. If data-s is not in the HTML, leave the argument out entirely and solve it as a standard v2 challenge, the way the reCAPTCHA v2 solver page describes.
Is data-s the same as the Enterprise flag?
No, and they are independent. enterprise=1 tells the solver which reCAPTCHA product it is dealing with. datas carries a one-time value from the page. A Google Search challenge needs the second one, and you can pass both if a page genuinely calls for it.
How long does the value stay good?
Assume seconds, not minutes. It is issued with the page and dies with it. If your scraper queues pages and solves them later, re-fetch the page at solve time rather than storing the value.
Why is it datas in code but data-s in the HTML?
Because a hyphen cannot appear in a Python keyword argument or a C# identifier. The SDKs drop it and send the hyphenated name on the wire for you. The raw in.php call still expects data-s.
Summary
Scrape data-sitekey and data-s from the same element, pass the second one as datas, and submit the token on the same session within a couple of minutes. Skip any of those three and you get a token that looks fine and fails.
The rest of the Python surface is on the Python CAPTCHA solver page. CapSkip does all of this as a local captcha bypass service on your own machine, so scraping a page twice to get a fresh value costs you nothing but the request.
