Scarnox · Blog

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:

  1. Load the page and wait for the widget to render.
  2. Read the data-sitekey attribute from the widget.
  3. Get a token from the API and put it in the form.
  4. 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 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

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.

Next steps