Tutorials

Açılır Modallarda CAPTCHA: Algılama ve Token Enjeksiyonu

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 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 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.

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

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:

Bu makale için yorumlar devre dışı bırakılmıştır.