# reCAPTCHA v2 이미지 인식 클릭 시뮬레이션(Selenium)

> Python Selenium 4와 ReCaptchaV2Classification으로 브라우저에서 reCAPTCHA v2 이미지 문제를 자동으로 클릭해요. 캡차 실행, 질문과 이미지 읽기, 인식 API 호출, 칸 클릭, 제출까지 전체 예제 코드를 제공해요.

Source: https://docs.yescaptcha.com/ko/guides/recaptcha-click-selenium/ · Last verified: 2026-10-15

> This page was machine-translated from the Chinese version. If anything differs, the Chinese version prevails: https://docs.yescaptcha.com/zh/guides/recaptcha-click-selenium/

이 튜토리얼에서는 Selenium 4로 Google의 reCAPTCHA 데모 페이지를 열고, 실제 브라우저에서 이미지 문제를 풀어요. 질문과 이미지를 읽은 뒤 [reCAPTCHA v2 이미지 인식](https://docs.yescaptcha.com/ko/tasks/recaptcha-classification.md)(`ReCaptchaV2Classification`)에 보내 어느 칸을 클릭해야 하는지 판단하게 하고, 그 칸을 클릭해요. 토큰을 받는 방식과 달리 브라우저 안에서 직접 검증을 통과하므로, 토큰 통과율이 낮은 사이트에 적합해요.

코드를 작성하고 싶지 않다면 [브라우저 확장 프로그램](https://docs.yescaptcha.com/ko/extension.md)을 바로 사용하세요. 같은 API를 기반으로 자동 클릭해요.

## 준비

```bash
pip install requests selenium pillow
export YESCAPTCHA_CLIENT_KEY=your_client_key
```

Chrome이 설치되어 있어야 해요. Selenium 4는 버전이 맞는 chromedriver를 자동으로 다운로드해요.

## 흐름

1.  **체크박스를 클릭해요.** 체크박스는 `title="reCAPTCHA"`인 iframe 안에 있어요. 먼저 그 iframe으로 전환한 뒤 `#recaptcha-anchor`를 클릭해요.
2.  **문제 iframe으로 전환해요.** 문제는 다른 iframe(title에 “challenge”가 포함됨) 안에 있어요.
3.  **질문을 읽어요.** `.rc-imageselect-desc-wrapper strong` 안의 굵은 글씨가 찾아야 할 대상이에요. 예를 들어 “traffic lights”예요. API에는 질문 ID가 필요하므로 대응표로 이름을 ID(`/m/015qff`)로 바꿔요. [질문 ID](https://docs.yescaptcha.com/ko/tasks/recaptcha-classification.md)를 참고하세요. 브라우저 화면을 영어로 두면 대응표와 비교하기 가장 편해요.
4.  **이미지를 가져와요.** 칸에 보이는 이미지는 사실 하나의 큰 이미지예요. `td.rc-imageselect-tile img`의 `src`를 다운로드해요. 9칸(3x3)은 300×300, 16칸(4x4)은 450×450으로 크기를 조정한 뒤 Base64로 변환해요.
5.  **인식해요.** `createTask`를 호출하고 유형은 `ReCaptchaV2Classification`, `image`와 `question`을 전달해요. `"confidence": 0.5`를 추가하면 3x3 이미지는 조건에 맞는 칸을 한 번에 모두 반환하므로, 클릭 후 새로 나타나는 작은 이미지를 따로 처리할 필요가 없어요.
6.  **클릭해요.** 반환된 `objects`는 칸 번호이며, 0부터 시작해 왼쪽에서 오른쪽, 위에서 아래 순서예요. 해당하는 `td`를 차례로 클릭해요.
7.  **검증해요.** `#recaptcha-verify-button`을 클릭해요. 새로운 문제가 다시 나오면 3단계로 돌아가요. 문제 창이 닫히면 통과한 거예요.
8.  **양식을 제출해요.** 메인 페이지로 돌아가 제출 버튼을 클릭해요.

## 전체 코드

recaptcha_click_selenium.py

```python
"""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.py
Each recognized image is charged (see the price list). reCAPTCHA's page structure can change;
adjust the selectors below if a step stops working.
"""
import base64
import io
import os
import time

import requests
from PIL import Image
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from 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의 페이지 구조는 업데이트되므로, 코드의 선택자가 동작하지 않으면 실제 페이지에 맞게 조정해야 해요.
-   이미지를 한 장 인식할 때마다 포인트가 차감돼요(가격은 [reCAPTCHA v2 이미지 인식](https://docs.yescaptcha.com/ko/tasks/recaptcha-classification.md) 참고). 클릭 후 검증에 실패한 작업은 1시간 안에 [신고](https://docs.yescaptcha.com/ko/api/report.md)하여 포인트 환불을 신청할 수 있어요.
-   대응표에 없는 질문이 나오면 새로고침 버튼을 눌러 다른 문제로 바꾸거나, 고객 지원에 문의해 지원을 추가해 달라고 요청하세요.
