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

recaptcha data-s parameter - 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-s and enterprise are 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:8080 by 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.

WhereName
Page HTMLdata-s
Raw API on in.phpdata-s
Python, Node.js, PHP, .NET SDKsdatas

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 token

Full 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 seeCauseFix
ERROR_GOOGLEKEYThe sitekey never made it into the request, or the regex matched the wrong attributePrint the scraped value before solving. Details in fixing ERROR_GOOGLEKEY
ERROR_BAD_PARAMETERSdata-s sent as an empty string, or sent to a non-Google pageOnly pass it when the attribute is actually present
ERROR_CAPTCHA_UNSOLVABLEUsually a stale data-s: the page was fetched minutes before the solveFetch and solve back to back. See ERROR_CAPTCHA_UNSOLVABLE
Token accepted by the solver, rejected by GoogleDifferent cookies or a different IP between fetch and submitReuse one session, and use the same proxy for both
NetworkExceptionCapSkip is not runningStart 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.