콘텐츠로 이동
서비스 상태대시보드

가이드

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

이 페이지는 중국어 버전을 기계 번역한 것이라 부정확한 내용이 있을 수 있어요. 중국어 버전을 기준으로 해 주세요.

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

코드를 작성하고 싶지 않다면 브라우저 확장 프로그램을 바로 사용하세요. 같은 API를 기반으로 자동 클릭해요.

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를 참고하세요. 브라우저 화면을 영어로 두면 대응표와 비교하기 가장 편해요.
  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
"""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 이미지 인식 참고). 클릭 후 검증에 실패한 작업은 1시간 안에 신고하여 포인트 환불을 신청할 수 있어요.
  • 대응표에 없는 질문이 나오면 새로고침 버튼을 눌러 다른 문제로 바꾸거나, 고객 지원에 문의해 지원을 추가해 달라고 요청하세요.