reCAPTCHA image clicking with Selenium
This guide opens Google’s reCAPTCHA demo page with Selenium 4 and completes the image challenge in a real browser: it reads the question and image, asks reCAPTCHA v2 image recognition (ReCaptchaV2Classification) which tiles to click, and clicks them. Unlike the token approach, the check is completed in the browser itself, which helps on sites where tokens rarely pass.
Without code, the browser extension does the same thing using the same API.
pip install requests selenium pillowexport YESCAPTCHA_CLIENT_KEY=your_client_keyChrome must be installed. Selenium 4 downloads a matching chromedriver automatically.
- Click the checkbox. It is inside the iframe titled “reCAPTCHA”; switch into it and click
#recaptcha-anchor. - Switch to the challenge iframe. The challenge is in another iframe whose title contains “challenge”.
- Read the question. The bold text in
.rc-imageselect-desc-wrapper strongis the target, for example “traffic lights”. The API needs the question ID, so map the name to an ID (/m/015qff) using the table in Question IDs. An English browser UI makes the mapping easiest. - Get the image. The tiles are slices of one large image; download the
srcoftd.rc-imageselect-tile img. Scale 9-tile (3x3) images to 300×300 and 16-tile (4x4) images to 450×450, then Base64-encode. - Recognize. Call
createTaskwith typeReCaptchaV2Classification,imageandquestion. Adding"confidence": 0.5makes 3x3 images return every matching tile at once, so you don’t need to handle the 1x1 tiles that fade in after clicking. - Click.
objectslists tile indexes from 0, left to right and top to bottom; click the matchingtdelements. - Verify. Click
#recaptcha-verify-button. If a new round appears, go back to step 3; when the challenge window closes, you have passed. - Submit the form on the main page.
Complete code
Section titled “Complete code”"""Click through a reCAPTCHA v2 image challenge with Selenium 4 and ReCaptchaV2Classification.
Flow: open the page -> click the checkbox -> read the target name and the grid image ->ask YesCaptcha which tiles to click -> click them -> press Verify. Repeat if a new challenge appears.
Run: pip install requests selenium pillow export YESCAPTCHA_CLIENT_KEY=your_client_key python recaptcha_click_selenium.pyEach recognized image is charged (see the price list). reCAPTCHA's page structure can change;adjust the selectors below if a step stops working."""import base64import ioimport osimport time
import requestsfrom PIL import Imagefrom selenium import webdriverfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.support import expected_conditions as ECfrom selenium.webdriver.support.wait import WebDriverWait
CLIENT_KEY = os.environ["YESCAPTCHA_CLIENT_KEY"]PAGE_URL = "https://www.google.com/recaptcha/api2/demo"
# Target names shown in the challenge (English UI) -> question IDs.# Full list: https://docs.yescaptcha.com/en/tasks/recaptcha-classification/QUESTION_IDS = { "taxis": "/m/0pg52", "bus": "/m/01bjv", "buses": "/m/01bjv", "motorcycles": "/m/04_sv", "tractors": "/m/013xlm", "chimneys": "/m/01jk_4", "crosswalks": "/m/014xcs", "traffic lights": "/m/015qff", "bicycles": "/m/0199g", "parking meters": "/m/015qbp", "cars": "/m/0k4j", "bridges": "/m/015kr", "boats": "/m/019jd", "palm trees": "/m/0cdl1", "mountains or hills": "/m/09d_r", "fire hydrants": "/m/01pns0", "a fire hydrant": "/m/01pns0", "stairs": "/m/01lynh",}
def classify(image_b64, question): task = {"type": "ReCaptchaV2Classification", "image": image_b64, "question": question, "confidence": 0.5} data = requests.post( "https://api.yescaptcha.com/createTask", json={"clientKey": CLIENT_KEY, "task": task}, timeout=30, ).json() if data.get("errorId"): raise RuntimeError(f"{data.get('errorCode')}: {data.get('errorDescription')}") return data["solution"] # synchronous: the solution comes back right away
def grid_image_b64(url, size): """Download the challenge image and scale it to the standard size (300 for 3x3, 450 for 4x4).""" img = Image.open(io.BytesIO(requests.get(url, timeout=30).content)).convert("RGB") img = img.resize((size, size)) buf = io.BytesIO() img.save(buf, format="JPEG") return base64.b64encode(buf.getvalue()).decode()
driver = webdriver.Chrome()wait = WebDriverWait(driver, 15)try: driver.get(PAGE_URL)
# 1. Click the checkbox (inside the "reCAPTCHA" iframe) wait.until(EC.frame_to_be_available_and_switch_to_it((By.CSS_SELECTOR, 'iframe[title="reCAPTCHA"]'))) wait.until(EC.element_to_be_clickable((By.ID, "recaptcha-anchor"))).click() driver.switch_to.default_content()
# 2. Switch to the challenge iframe and solve rounds until it closes wait.until(EC.frame_to_be_available_and_switch_to_it( (By.XPATH, '//iframe[contains(@title, "challenge")]'))) for _ in range(5): target = wait.until(EC.presence_of_element_located( (By.CSS_SELECTOR, ".rc-imageselect-desc-wrapper strong"))).text.strip().lower() question = QUESTION_IDS.get(target) if not question: print("unsupported target, reloading:", target) driver.find_element(By.ID, "recaptcha-reload-button").click() time.sleep(2) continue tiles = driver.find_elements(By.CSS_SELECTOR, "td.rc-imageselect-tile") size = 450 if len(tiles) == 16 else 300 url = driver.find_element(By.CSS_SELECTOR, "td.rc-imageselect-tile img").get_attribute("src") solution = classify(grid_image_b64(url, size), question) for index in solution.get("objects", []): tiles[index].click() time.sleep(0.3) time.sleep(1) driver.find_element(By.ID, "recaptcha-verify-button").click() time.sleep(3) driver.switch_to.default_content() frame = driver.find_element(By.XPATH, '//iframe[contains(@title, "challenge")]') if not frame.is_displayed(): # challenge closed: passed break driver.switch_to.frame(frame)
driver.switch_to.default_content() driver.find_element(By.ID, "recaptcha-demo-submit").click() time.sleep(2) print("passed" if "Verification Success" in driver.page_source else "not passed")finally: driver.quit()- reCAPTCHA’s page structure changes over time; adjust the selectors if a step stops working.
- Every recognized image is charged (prices on reCAPTCHA v2 image recognition). Tasks whose clicks were rejected can be reported within 1 hour for a refund.
- If a target isn’t in the mapping table, click the reload button for another challenge, or ask support to add it.