Seorang pengguna mengklik "Kirim" dan sebuah modal muncul dengan tantangan CAPTCHA. Kunci situs tidak ada di sumber laman awal - ia dimuat secara dinamis saat modal terbuka. Skrip automasi Anda perlu memicu modal, tunggu hingga CAPTCHA dirender, mengekstrak parameter, menyelesaikan, dan memasukkan token sebelum waktu modal habis atau sesi pengguna berakhir.
Pola Modal CAPTCHA
| Pola | Pemicu | Tantangan |
|---|---|---|
| Modal masuk | Klik tombol "Masuk". | CAPTCHA dimuat di dalam div overlay |
| Interstisial anti-bot | Otomatis setelah perilaku mencurigakan | Halaman blok modal layar penuh |
| Konfirmasi pembayaran | Kirim formulir pembayaran | Modal muncul untuk verifikasi |
| Dialog batas tarif | Terlalu banyak permintaan yang terdeteksi | Modal dengan gerbang CAPTCHA |
| Persetujuan cookie + CAPTCHA | Kunjungan pertama | CAPTCHA tertanam dalam dialog persetujuan |
Python: Handler CAPTCHA Modal Playwright
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()
JavaScript: Handler Modal Puppeteer
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();
})();
Pertimbangan Waktu Modal
| Faktor | Dampak | Mitigasi |
|---|---|---|
| Batas waktu penutupan otomatis modal | Modal mungkin ditutup sebelum penyelesaian selesai | Mulailah menyelesaikan segera setelah terdeteksi |
| Sesi berakhir selama penyelesaian | Sesi server berakhir dalam mode tunggu | Jaga sesi tetap hidup dengan detak jantung latar belakang |
| Penundaan render CAPTCHA dalam modal | Widget memerlukan waktu 1–3 detik untuk dimuat dalam modal | Tunggu 2 detik setelah modal terlihat sebelum mengekstraksi kunci situs |
| Kedaluwarsa token saat pengisian formulir | Token kedaluwarsa saat mengisi formulir modal | Selesaikan CAPTCHA terakhir, setelah mengisi kolom lainnya |
Pemecahan Masalah
| Masalah | Penyebab | Solusi |
|---|---|---|
| Modal terdeteksi tetapi CAPTCHA tidak ditemukan | CAPTCHA dimuat async setelah modal dibuka | Tingkatkan waktu tunggu; gunakan MutationObserver untuk mendeteksi penyisipan widget |
| Token diinjeksi tetapi modal tidak ditutup | Fungsi callback tidak terpicu | Temukan dan aktifkan callback CAPTCHA secara eksplisit |
| Modal ditutup selama solve | Auto-close timeout | Nonaktifkan timeout modal via JS: clearTimeout() pada timer modal |
| Sitekey CAPTCHA berbeda setiap saat | Modal menghasilkan instance CAPTCHA dinamis | Selalu ekstrak sitekey baru dari DOM modal, jangan pernah di-cache |
| Klik trigger tidak membuka modal | Elemen tidak interaktif atau di balik overlay | Gunakan page.dispatchEvent atau tunggu elemen dapat diklik |
Pertanyaan Umum
Bagaimana cara mendeteksi modal yang terbuka secara otomatis tanpa pemicu klik?
Gunakan MutationObserver untuk melihat elemen baru yang muncul di DOM. Atur sebelum menavigasi ke halaman. Saat elemen modal ditambahkan dan terlihat, pengamat Anda akan aktif dan Anda dapat memulai aliran deteksi CAPTCHA.
Bagaimana jika CAPTCHA ada di dalam modal iframe?
Jika modal berisi iframe dengan CAPTCHA, gabungkan pendekatan ini dengan penanganan iframe. Setelah mendeteksi modal, beralihlah ke konteks iframe di dalam modal untuk mengekstrak kunci situs.
Haruskah saya mengisi kolom formulir sebelum atau sesudah menyelesaikan CAPTCHA?
Sebelum. Isi semua kolom formulir lainnya terlebih dahulu, lalu selesaikan CAPTCHA terakhir. Hal ini meminimalkan waktu antara mendapatkan token dan mengirimkan formulir, sehingga mengurangi risiko kedaluwarsa.
Artikel Terkait
- Memecahkan reCAPTCHA v2 dengan Callback via API
- Menangani reCAPTCHA v2 dan Turnstile di Halaman yang Sama
- Menangani Beberapa CAPTCHA dalam Satu Halaman
- Penanganan CAPTCHA Shadow DOM
- Ekstraksi CAPTCHA iframe dari Nested Frames
Langkah Selanjutnya
Tangani CAPTCHA dalam modal pop-up dengan lancar — dapatkan kunci API CaptchaAI Anda dan implementasikan deteksi modal.