Selenium captcha solver in Python: working example (2026)
Selenium runs a real browser, so the page renders and its scripts run as they would for a person. The only step it cannot do is solve the PopularCaptcha widget. This guide adds that step: read the sitekey from the rendered page, get a token with one API call, write the token into the form, and submit.
It covers the PopularCaptcha widget, which Scarnox solves today on a proxyless basis. Start with free trial credit, no card needed and try the script on a test form first.
What Selenium has to do
The flow has four parts:
- Load the page and wait for the widget to render.
- Read the
data-sitekeyattribute from the widget. - Get a token from the API and put it in the form.
- Submit the form before the token expires.
The third step is the only one that touches the captcha. Everything else is normal Selenium work.
Install the packages
pip install selenium requests
Selenium 4.6 and later finds a matching ChromeDriver on its own, so you do not need to download the driver by hand.
The full script
import requests
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
API_KEY = "scarnox_YOURAPIKEY"
API_URL = "https://api.scarnox.com/api/tasks/create"
PAGE_URL = "https://example.com/signup"
def solve(sitekey: str, page_url: str, invisible: bool = False) -> str:
body = {
"captcha_type": "PopularCaptchaTaskProxyless",
"site_key": sitekey,
"site_url": page_url,
}
if invisible:
body["is_invisible"] = True
r = requests.post(
API_URL,
headers={"Authorization": f"Bearer {API_KEY}"},
json=body,
timeout=185, # blocks until the token is ready
)
data = r.json()
if data.get("errorId") != 0:
raise RuntimeError(f"{data.get('errorCode')}: {data.get('errorDescription')}")
return data["solution"]["token"]
INJECT_TOKEN_JS = """
const token = arguments[0];
let field = document.querySelector('[name="h-captcha-response"]');
if (!field) {
field = document.createElement("textarea");
field.name = "h-captcha-response";
field.style.display = "none";
document.querySelector("form").appendChild(field);
}
field.value = token;
let extra = document.querySelector('[name="g-recaptcha-response"]');
if (!extra) {
extra = document.createElement("textarea");
extra.name = "g-recaptcha-response";
extra.style.display = "none";
document.querySelector("form").appendChild(extra);
}
extra.value = token;
"""
def main() -> None:
driver = webdriver.Chrome()
try:
driver.get(PAGE_URL)
widget = WebDriverWait(driver, 15).until(
EC.presence_of_element_located((By.CSS_SELECTOR, "[data-sitekey]"))
)
sitekey = widget.get_attribute("data-sitekey")
invisible = widget.get_attribute("data-size") == "invisible"
token = solve(sitekey, driver.current_url, invisible=invisible)
# Fill the other fields first, then inject the token right before submit,
# because the token expires in about two minutes.
driver.find_element(By.NAME, "email").send_keys("you@example.com")
driver.execute_script(INJECT_TOKEN_JS, token)
driver.find_element(By.CSS_SELECTOR, "form button[type=submit]").click()
WebDriverWait(driver, 15).until(EC.url_changes(PAGE_URL))
print("Submitted. Now at:", driver.current_url)
finally:
driver.quit()
if __name__ == "__main__":
main()
The script does not need a proxy for a proxyless PopularCaptcha solve. Leave out the proxy field and let the site see your normal connection.
The order of operations is the part that most often goes wrong. Fill the rest of the form first, then solve, then inject, then click. If you solve before you type into the other fields, a slow page or a slow form can push the submit past the token's lifetime. Keep the gap between the solve and the click as short as you can, and check the result page for an error before you assume the captcha passed.
Why the token goes in with JavaScript
Most PopularCaptcha widgets write the token into a hidden textarea named h-captcha-response when a person solves them. Because Selenium never sees the person, the script writes the same field itself. The INJECT_TOKEN_JS snippet does two things:
- It reuses the textarea if the widget already created one.
- It creates the textarea if the page did not, and appends it to the form.
It also fills g-recaptcha-response, because many sites read that name for compatibility.
Invisible widgets
An invisible widget has no checkbox. It runs in the background and usually triggers on form submit. The script checks data-size="invisible" and sends is_invisible: true when it sees it. Some sites also attach a JavaScript callback to the widget. If the form stays put after you inject the token, look for a data-callback attribute and call that function with the token. Our guide on invisible captcha covers this case.
Using it in tests
Selenium is also the usual tool for end-to-end tests. If your own staging site shows a captcha, the same code lets a test pass it. Keep the test environment separate from production, and do not run it against sites you do not own. The setup for test suites is in captcha in automated tests.
Common problems
- The widget never appears. Increase the wait, or check that the widget sits inside an iframe. If it does, switch to the frame first with
driver.switch_to.frame(...). - The form submits but the site rejects the token. Check that you sent the token to the page where the widget was shown.
driver.current_urlmust match the page's real URL, including any query string. - The token expired. Move the
solvecall closer to the submit step. The token lasts about two minutes. - A timeout error from the API. Use a timeout of about 185 seconds, as in the script. Retry logic is covered in captcha solver error handling.
Pricing
You pay per successful solve. Failed solves are not charged. See current pricing for the rates, and start with free trial credit, no card needed.