Modal açıldığı anda CAPTCHA'nın site anahtarı sayfa kaynağında yoktur — DOM'a modalla birlikte dinamik olarak eklenir ve modal genellikle birkaç saniye içinde kapanır ya da oturumu düşürür. Script'inizin bu dar pencerede dört işlemi sırayla bitirmesi gerekir: modalın açılmasını beklemek, içindeki CAPTCHA'yı algılamak, CaptchaAI API'siyle çözmek ve token'ı zaman aşımından önce enjekte etmek. Bu akış, kullanıcı "Gönder"e tıkladığında veya bot karşıtı sistem şüpheli davranış algıladığında devreye girer. Bu rehber, Playwright ve Puppeteer için hazır Python ve Node.js koduyla süreci adım adım gösterir.
Modal CAPTCHA'nın yaygın görüldüğü yerler
Modal içi CAPTCHA'yı en sık şu beş senaryoda görürsünüz:
| Model | Tetikleyici | Zorluk |
|---|---|---|
| Giriş modalı | "Giriş Yap" butonuna tıklama | CAPTCHA, modalın içine sonradan yükleniyor |
| Bot karşıtı ara sayfa | Şüpheli davranış sonrası otomatik açılır | Tam ekran modal sayfanın tamamını kilitliyor |
| Ödeme onayı | Ödeme formu gönderildiğinde | Doğrulama için modal beliriyor |
| Hız sınırı uyarısı | Çok sayıda istek algılandığında | CAPTCHA'lı modal devreye giriyor |
| Çerez onayı + CAPTCHA | İlk ziyarette | CAPTCHA, onay kutusunun içine gömülü |
Python ve Playwright ile modal CAPTCHA işleyicisi
Aşağıdaki script beş adımda ilerler: modalı tetikler, görünür olmasını bekler, içindeki CAPTCHA'yı detect_modal_captcha ile bulur, CaptchaAI üzerinden çözer ve token'ı inject_token_in_modal ile modalın DOM'una yazar. handle_modal_captcha bu beş adımı tek çağrıda birleştirir.
import requests
import time
from playwright.sync_api import sync_playwright
API_KEY = "YOUR_API_KEY"
SUBMIT_URL = "https://ocr.captchaai.com/in.php"
RESULT_URL = "https://ocr.captchaai.com/res.php"
def solve_captcha(sitekey, pageurl, method="userrecaptcha"):
"""Submit and poll a CAPTCHA."""
params = {
"key": API_KEY,
"method": method,
"json": 1,
}
if method == "userrecaptcha":
params["googlekey"] = sitekey
params["pageurl"] = pageurl
elif method == "turnstile":
params["sitekey"] = sitekey
params["pageurl"] = pageurl
resp = requests.post(SUBMIT_URL, data=params, timeout=30).json()
if resp.get("status") != 1:
raise RuntimeError(f"Submit failed: {resp.get('request')}")
task_id = resp["request"]
for _ in range(60):
time.sleep(5)
poll = requests.get(RESULT_URL, params={
"key": API_KEY, "action": "get",
"id": task_id, "json": 1,
}, timeout=15).json()
if poll.get("request") == "CAPCHA_NOT_READY":
continue
if poll.get("status") == 1:
return poll["request"]
raise RuntimeError(f"Solve failed: {poll.get('request')}")
raise RuntimeError("Timeout")
def detect_modal_captcha(page):
"""
Detect CAPTCHA inside a visible modal/dialog.
Returns (sitekey, method) or (None, None).
"""
return page.evaluate("""
() => {
// Find visible modals
const modalSelectors = [
'.modal.show',
'.modal[style*="display: block"]',
'dialog[open]',
'[role="dialog"]:not([aria-hidden="true"])',
'.overlay.visible',
'.popup.active',
'[class*="modal"][class*="open"]',
];
let modal = null;
for (const sel of modalSelectors) {
const el = document.querySelector(sel);
if (el && el.offsetParent !== null) {
modal = el;
break;
}
}
// If no modal found, search entire document
const searchRoot = modal || document;
// Check for reCAPTCHA
const recaptcha = searchRoot.querySelector('.g-recaptcha[data-sitekey]');
if (recaptcha) {
return { sitekey: recaptcha.dataset.sitekey, method: 'userrecaptcha' };
}
// Check for Turnstile
const turnstile = searchRoot.querySelector('.cf-turnstile[data-sitekey]');
if (turnstile) {
return { sitekey: turnstile.dataset.sitekey, method: 'turnstile' };
}
// Check for hCaptcha
const hcaptcha = searchRoot.querySelector('.h-captcha[data-sitekey]');
if (hcaptcha) {
return { sitekey: hcaptcha.dataset.sitekey, method: 'hcaptcha' };
}
return null;
}
""")
def inject_token_in_modal(page, token, method="userrecaptcha"):
"""Inject token into the CAPTCHA inside the modal."""
if method == "userrecaptcha":
page.evaluate("""
(token) => {
// Find response textarea (may be inside modal)
const textareas = document.querySelectorAll('#g-recaptcha-response, [name="g-recaptcha-response"]');
textareas.forEach(ta => {
ta.value = token;
ta.style.display = 'block';
});
// Trigger callback
if (typeof ___grecaptcha_cfg !== 'undefined') {
Object.values(___grecaptcha_cfg.clients).forEach(client => {
Object.values(client).forEach(val => {
if (val && typeof val === 'object') {
Object.values(val).forEach(v => {
if (v && typeof v.callback === 'function') v.callback(token);
});
}
});
});
}
}
""", token)
elif method == "turnstile":
page.evaluate("""
(token) => {
const inputs = document.querySelectorAll('[name="cf-turnstile-response"]');
inputs.forEach(inp => { inp.value = token; });
if (typeof window.turnstileCallback === 'function') {
window.turnstileCallback(token);
}
}
""", token)
def handle_modal_captcha(page, trigger_selector=None, timeout=10000):
"""
Full workflow: trigger modal, detect CAPTCHA, solve, inject.
"""
# Step 1: Trigger the modal if needed
if trigger_selector:
print(f"Clicking trigger: {trigger_selector}")
page.click(trigger_selector)
# Step 2: Wait for modal to become visible
print("Waiting for modal...")
modal_selectors = [
".modal.show",
"dialog[open]",
'[role="dialog"]:not([aria-hidden="true"])',
".popup.active",
]
modal_visible = False
for selector in modal_selectors:
try:
page.wait_for_selector(selector, timeout=timeout)
modal_visible = True
print(f" Modal detected: {selector}")
break
except Exception:
continue
if not modal_visible:
print(" No modal detected")
return None
# Step 3: Wait for CAPTCHA to render inside modal
time.sleep(2) # Brief pause for dynamic CAPTCHA loading
captcha_info = detect_modal_captcha(page)
if not captcha_info:
print(" No CAPTCHA found in modal")
return None
sitekey = captcha_info["sitekey"]
method = captcha_info["method"]
print(f" Found {method} CAPTCHA: {sitekey[:20]}...")
# Step 4: Solve via CaptchaAI
token = solve_captcha(sitekey, page.url, method)
print(f" Solved: {token[:30]}...")
# Step 5: Inject token
inject_token_in_modal(page, token, method)
print(" Token injected")
return token
def main():
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
page.goto("https://example.com")
page.wait_for_load_state("networkidle")
# Handle CAPTCHA that appears in login modal
token = handle_modal_captcha(
page,
trigger_selector="button#login-btn",
timeout=10000,
)
if token:
# Fill form fields inside modal
page.fill('dialog input[name="email"]', "user@example.com")
page.fill('dialog input[name="password"]', "password123")
# Submit modal form
page.click('dialog button[type="submit"]')
page.wait_for_load_state("networkidle")
browser.close()
main()
Kod, reCAPTCHA v2 ile Turnstile'ı ayırt etmek için data-sitekey taşıyan .g-recaptcha ve .cf-turnstile seçicilerine bakar. detect_modal_captcha bir de .h-captcha seçicisini tanır, ama bu yalnızca teşhis içindir — CaptchaAI hCaptcha çözmez. Akış hCaptcha algılarsa solve_captcha'yı çağırmadan durdurun.
JavaScript ve Puppeteer ile modal işleyici
Aynı akışın Puppeteer karşılığı: waitForModal görünürlüğü bekler, detectModalCaptcha site anahtarını çıkarır, solveCaptcha CaptchaAI'den token alır ve son blok token'ı ilgili input alanına yazar. Bu sürüm yalnızca reCAPTCHA v2 ve Turnstile'ı algılar; hCaptcha seçicisi burada yer almaz.
const puppeteer = require("puppeteer");
const API_KEY = "YOUR_API_KEY";
const SUBMIT_URL = "https://ocr.captchaai.com/in.php";
const RESULT_URL = "https://ocr.captchaai.com/res.php";
async function solveCaptcha(sitekey, pageurl, method = "userrecaptcha") {
const body = new URLSearchParams({ key: API_KEY, method, json: "1" });
if (method === "userrecaptcha") { body.set("googlekey", sitekey); body.set("pageurl", pageurl); }
else if (method === "turnstile") { body.set("sitekey", sitekey); body.set("pageurl", pageurl); }
const resp = await (await fetch(SUBMIT_URL, { method: "POST", body })).json();
if (resp.status !== 1) throw new Error(`Submit: ${resp.request}`);
const taskId = resp.request;
for (let i = 0; i < 60; i++) {
await new Promise((r) => setTimeout(r, 5000));
const url = `${RESULT_URL}?key=${API_KEY}&action=get&id=${taskId}&json=1`;
const poll = await (await fetch(url)).json();
if (poll.request === "CAPCHA_NOT_READY") continue;
if (poll.status === 1) return poll.request;
throw new Error(`Solve: ${poll.request}`);
}
throw new Error("Timeout");
}
async function waitForModal(page, timeout = 10000) {
const selectors = [".modal.show", "dialog[open]", '[role="dialog"]', ".popup.active"];
for (const sel of selectors) {
try {
await page.waitForSelector(sel, { visible: true, timeout });
return sel;
} catch {}
}
return null;
}
async function detectModalCaptcha(page) {
return page.evaluate(() => {
const checks = [
{ sel: ".g-recaptcha[data-sitekey]", method: "userrecaptcha" },
{ sel: ".cf-turnstile[data-sitekey]", method: "turnstile" },
];
for (const { sel, method } of checks) {
const el = document.querySelector(sel);
if (el && el.dataset.sitekey) return { sitekey: el.dataset.sitekey, method };
}
return null;
});
}
async function handleModalCaptcha(page, triggerSelector) {
// Trigger modal
if (triggerSelector) await page.click(triggerSelector);
// Wait for modal
const modalSel = await waitForModal(page);
if (!modalSel) { console.log("No modal found"); return null; }
console.log(`Modal visible: ${modalSel}`);
// Wait for CAPTCHA render
await new Promise((r) => setTimeout(r, 2000));
const info = await detectModalCaptcha(page);
if (!info) { console.log("No CAPTCHA in modal"); return null; }
console.log(`Found ${info.method}: ${info.sitekey.substring(0, 20)}...`);
const token = await solveCaptcha(info.sitekey, page.url(), info.method);
console.log(`Solved: ${token.substring(0, 30)}...`);
// Inject token
await page.evaluate((t, method) => {
if (method === "userrecaptcha") {
document.querySelectorAll("#g-recaptcha-response").forEach((el) => { el.value = t; });
} else if (method === "turnstile") {
document.querySelectorAll('[name="cf-turnstile-response"]').forEach((el) => { el.value = t; });
}
}, token, info.method);
return token;
}
(async () => {
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
await page.goto("https://example.com", { waitUntil: "networkidle2" });
const token = await handleModalCaptcha(page, "button#login-btn");
if (token) {
await page.type('dialog input[name="email"]', "user@example.com");
await page.click('dialog button[type="submit"]');
await page.waitForNavigation();
}
await browser.close();
})();
Modal zamanlama riskleri
Modal CAPTCHA'ların çoğu hatası çözümde değil, zamanlamada çıkar:
| Faktör | Etki | Azaltma |
|---|---|---|
| Modal otomatik kapanma zaman aşımı | Çözüm tamamlanmadan modal kapanabilir | Algılamanın hemen ardından çözmeye başlayın |
| Çözüm sırasında oturumun sona ermesi | Sunucu oturumu beklemede sona eriyor | Arka planda heartbeat ile oturumu canlı tutun |
| Modalda CAPTCHA render gecikmesi | Widget'ın yüklenmesi 1-3 saniye sürer | Site anahtarını çıkarmadan önce modal görünür olduktan sonra 2 saniye bekleyin |
| Form doldururken token'ın süresi dolması | Uzun formu doldururken token süresi doluyor | Diğer alanları doldurduktan sonra CAPTCHA'yı en son çözün |
Türkiye'den bir senaryo: ödeme modalını QA'da test etmek
Freelance bir otomasyon geliştiricisi olarak (Bionluk veya Upwork üzerinden) bir e-ticaret müşterisinin ödeme akışı için regresyon testi yazdığınızı düşünün. Ödeme formu gönderildiğinde site dolandırıcılık kontrolü için bir onay modalı açıyor ve modalın içine Cloudflare Turnstile yerleştiriyor; eski giriş modalı ise hâlâ reCAPTCHA v2 kullanıyor, çünkü müşteri geçişi kademeli yapıyor. detect_modal_captcha iki türü de tanıdığından tek script her iki akışı da kapsar.
QA ortamınızda test kullanıcı bilgisi gibi kişisel veri toplayacaksanız bunun KVKK kapsamına girdiğini unutmayın; test hedeflerinizi staging.example.com gibi kendi ortamınızla sınırlayın ve müşterinin canlı sayfalarını yetkisiz taramayın. Maliyet tarafında modal sayısı arttıkça thread ihtiyacınız da büyür, ama CaptchaAI çözüm başına değil thread başına ücretlendirir: BASIC ($15/ay, 5 thread) birkaç paralel modal testi için yeterlidir; günlük regresyon koşunuz büyüdükçe STANDARD ($30/ay, 15 thread) planına geçebilirsiniz. Fiyatlar USD üzerinden sabittir — TL'deki dalgalanma aylık faturanızı etkilemez.
Sık karşılaşılan sorunlar ve çözümleri
| Sorun | Sebep | Düzeltme |
|---|---|---|
| Modal algılandı ama CAPTCHA bulunamadı | CAPTCHA, modal açıldıktan sonra eşzamansız yükleniyor | Bekleme süresini artırın; widget eklenmesini yakalamak için MutationObserver kullanın |
| Token enjekte edildi ama modal kapanmıyor | Callback fonksiyonu tetiklenmedi | CAPTCHA callback'ini bulup açıkça çağırın |
| Çözüm sırasında modal kapanıyor | Otomatik kapatma zaman aşımı | Modal zamanlayıcısında clearTimeout() ile zaman aşımını devre dışı bırakın |
| CAPTCHA site anahtarı her seferinde farklı | Modal her açıldığında yeni bir CAPTCHA örneği oluşturuyor | Site anahtarını her zaman modal DOM'undan taze çekin, asla önbelleğe almayın |
| Tıklama tetikleyicisi modalı açmıyor | Öğe etkileşimli değil ya da bir katmanın arkasında | page.dispatchEvent kullanın veya öğenin tıklanabilir hale gelmesini bekleyin |
SSS
CaptchaAI modal içindeki hangi CAPTCHA türlerini çözer?
Bu akışta reCAPTCHA v2 ve Cloudflare Turnstile'ı çözersiniz. detect_modal_captcha fonksiyonu bir de hCaptcha seçicisini tanır, ama bu yalnızca teşhis amaçlıdır — CaptchaAI hCaptcha çözmez. hCaptcha algılanırsa akışı solve_captcha'yı çağırmadan durdurun.
Modal içindeki CAPTCHA hangi method parametresini kullanır?
reCAPTCHA v2 için method=userrecaptcha ve googlekey, Turnstile için method=turnstile ve sitekey gönderilir. İki tür de aynı in.php / res.php uç noktalarını paylaşır; yalnızca parametre adları değişir.
Aynı sayfada çerez onayı ve giriş modalı art arda açılırsa ne olur?
detect_modal_captcha her çağrıldığında görünür modalı DOM'dan yeniden arar, sonucu önbelleğe almaz. İki modal art arda açılıyorsa fonksiyonu her modal görünür olduğunda ayrı ayrı çağırın; aksi halde ilk modalın site anahtarını yanlışlıkla ikinci modala uygularsınız.
CAPTCHA, modal içindeki bir iframe'in içindeyse ne yapmalıyım?
Önce modalı tespit edin, sonra site anahtarını çıkarmadan önce modalın içindeki iframe bağlamına geçin. Bu yaklaşımı iframe işleme yöntemleriyle birleştirin.
Form alanlarını CAPTCHA'yı çözmeden önce mi doldurmalıyım?
Evet. Diğer form alanlarını önce doldurun, CAPTCHA'yı en son çözün. Bu, token alımıyla form gönderimi arasındaki süreyi kısaltır ve süre sonu riskini azaltır.
İlgili makaleler
- reCAPTCHA v2 callback sorununu API ile çözme
- GeeTest ve Cloudflare Turnstile karşılaştırması
- Aynı sitede reCAPTCHA v2 ve Turnstile'ı çözme
Sonraki adımlar
Açılır modallardaki CAPTCHA'ları sorunsuz şekilde çözün — CaptchaAI API anahtarınızı alın ve modal algılama akışını bugün devreye alın.
İlgili kılavuzlar: