Cara mengatasi CAPTCHA di iframe bersarang, singkatnya: telusuri pohon frame dari terluar ke terdalam, ambil sitekey dari iframe yang benar-benar merender widget (bukan iframe Google paling dalam), solve via API CaptchaAI, lalu suntikkan token ke frame yang sama. Salah pilih iframe atau salah kirim pageurl, dan hasilnya ERROR_WRONG_GOOGLEKEY atau token yang ditolak situs walau status CaptchaAI sudah sukses.
Empat langkah intinya:
- Masuk ke frame terluar —
switch_to.frame()di Selenium ataucontentFrame()di Puppeteer, menuju iframe pembayaran atau modal yang membungkus widget CAPTCHA. - Temukan iframe target di dalamnya — pola paling umum adalah
iframe[src*="recaptcha/api2/anchor"], atau selector setara untuk Turnstile. - Ekstrak sitekey dari atribut
data-sitekeypada elemen itu, atau dari parameterk=disrc-nya. - Solve via API CaptchaAI, lalu suntik token ke frame yang sama tempat sitekey diambil — bukan ke halaman utama.
Skenario ini akrab bagi developer scraping dan automation freelance di Indonesia — kontrak Upwork/Fastwork, tim price-monitoring: widget CAPTCHA di checkout sering ada di iframe payment gateway, yang dirender lagi di iframe modal. Dua-tiga level bersarang bukan kasus langka.
Catatan kepatuhan: saat mengotomasi form checkout pihak ketiga, pastikan Anda hanya memproses data yang berwenang Anda proses, sejalan dengan UU Pelindungan Data Pribadi (UU 27/2022) dan UU ITE. Panduan ini bukan nasihat hukum.
Struktur iframe bersarang dan lokasi sitekey
Pola yang paling sering ditemui di checkout atau form pembayaran:
Main page
└── iframe#payment-frame (cross-origin)
└── iframe[src*="recaptcha/api2/anchor"] (Google-hosted)
└── reCAPTCHA widget
Sitekey ada di iframe lapisan luar — bukan iframe Google paling dalam, yang cuma visual challenge dan tidak pernah Anda sentuh langsung. Target ekstraksi: atribut data-sitekey di elemen itu, atau parameter k= pada src-nya.
Catatan: pola ekstraksi ini sama untuk widget yang dimuat lewat script tag pihak ketiga, bukan cuma markup iframe statis.
Yang perlu diperiksa sebelum menulis kode ekstraksi:
- Apakah widget dirender langsung, atau lewat iframe di dalam iframe (payment gateway di dalam modal checkout)?
- Apakah iframe itu cross-origin? Kalau ya, Anda tetap bisa baca
srcdandata-sitekeydari parent frame — jangan jalankanexecute_scriptdi dalamnya. - Apakah situs memakai reCAPTCHA, Turnstile, atau GeeTest v3? Selector dan parameter
methoddiin.phpberbeda untuk masing-masing.
Selenium + Python: berpindah antar-iframe langkah demi langkah
Alurnya empat langkah: masuk ke iframe pembayaran, cari iframe reCAPTCHA di dalamnya, ekstrak sitekey dari src, lalu solve dan suntik token balik ke frame yang tepat:
Catatan: contoh ini pakai
time.sleepdi loop polling agar ringkas dibaca; untuk beban produksi tinggi, pertimbangkan backoff berjenjang alih-alih interval tetap.
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import requests
import re
import time
API_KEY = "YOUR_API_KEY"
driver = webdriver.Chrome()
driver.get("https://example.com/checkout")
# Step 1: Switch into the outer iframe
wait = WebDriverWait(driver, 15)
outer_iframe = wait.until(
EC.presence_of_element_located((By.CSS_SELECTOR, "iframe#payment-frame"))
)
driver.switch_to.frame(outer_iframe)
# Step 2: Find the reCAPTCHA iframe and extract sitekey
recaptcha_iframe = wait.until(
EC.presence_of_element_located(
(By.CSS_SELECTOR, 'iframe[src*="recaptcha/api2/anchor"]')
)
)
src = recaptcha_iframe.get_attribute("src")
sitekey = re.search(r"k=([A-Za-z0-9_-]+)", src).group(1)
page_url = driver.current_url
print(f"Sitekey: {sitekey}")
print(f"Page URL: {page_url}")
# Step 3: Solve with CaptchaAI
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": page_url,
"json": "1"
})
task_id = resp.json()["request"]
token = None
for _ in range(24):
time.sleep(5)
poll = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get", "id": task_id, "json": "1"
}).json()
if poll["status"] == 1:
token = poll["request"]
break
if poll["request"] != "CAPCHA_NOT_READY":
raise Exception(poll["request"])
print(f"Token: {token[:50]}...")
# Step 4: Inject token (still inside the outer iframe)
driver.execute_script("""
document.querySelector('textarea[name="g-recaptcha-response"]').value = arguments[0];
if (typeof grecaptcha !== 'undefined') {
grecaptcha.getResponse = function() { return arguments[0]; };
}
""", token)
# Step 5: Switch back to main page
driver.switch_to.default_content()
print("Token injected, switched back to main frame")
Hasil yang diharapkan:
Sitekey: 6Le-SITEKEY-abc123
Page URL: https://example.com/checkout
Token: 03AGdBq26ZfPxL...
Token injected, switched back to main frame
Puppeteer + Node.js: frame handling tanpa switch manual
Puppeteer punya API frame kelas satu, jadi tidak perlu switch_to sama sekali — cukup ambil contentFrame() dari handle iframe-nya, lalu kerjakan ekstraksi dan inject langsung di objek frame itu.
const puppeteer = require('puppeteer');
const axios = require('axios');
const API_KEY = 'YOUR_API_KEY';
(async () => {
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
await page.goto('https://example.com/checkout', {
waitUntil: 'networkidle2'
});
// Step 1: Find the payment iframe
const paymentFrameHandle = await page.waitForSelector('iframe#payment-frame');
const paymentFrame = await paymentFrameHandle.contentFrame();
// Step 2: Find reCAPTCHA iframe inside the payment frame
const recaptchaFrameHandle = await paymentFrame.waitForSelector(
'iframe[src*="recaptcha/api2/anchor"]'
);
const src = await paymentFrame.evaluate(
el => el.getAttribute('src'), recaptchaFrameHandle
);
const sitekey = src.match(/k=([A-Za-z0-9_-]+)/)[1];
const pageUrl = page.url();
console.log(`Sitekey: ${sitekey}`);
// Step 3: Solve with CaptchaAI
const submitResp = await axios.post('https://ocr.captchaai.com/in.php', null, {
params: {
key: API_KEY,
method: 'userrecaptcha',
googlekey: sitekey,
pageurl: pageUrl,
json: 1
}
});
const taskId = submitResp.data.request;
let token = null;
for (let i = 0; i < 24; i++) {
await new Promise(r => setTimeout(r, 5000));
const pollResp = await axios.get('https://ocr.captchaai.com/res.php', {
params: { key: API_KEY, action: 'get', id: taskId, json: 1 }
});
if (pollResp.data.status === 1) {
token = pollResp.data.request;
break;
}
if (pollResp.data.request !== 'CAPCHA_NOT_READY') {
throw new Error(pollResp.data.request);
}
}
console.log(`Token: ${token.substring(0, 50)}...`);
// Step 4: Inject token into the payment frame
await paymentFrame.evaluate((tkn) => {
const textarea = document.querySelector(
'textarea[name="g-recaptcha-response"]'
);
textarea.value = tkn;
const callback = document.querySelector('.g-recaptcha')
?.getAttribute('data-callback');
if (callback && typeof window[callback] === 'function') {
window[callback](tkn);
}
}, token);
console.log('Token injected into payment iframe');
})();
Latensi cross-region dan waktu tunggu iframe pihak ketiga
Kalau worker automation Anda jalan dari region cloud yang berbeda dari payment gateway pihak ketiga, iframe pembayaran bisa butuh lebih lama untuk selesai render — dan kalau kode ekstraksi sitekey Anda jalan sebelum itu, hasilnya NoSuchElementException atau nilai null, bukan error dari CaptchaAI.
Praktik yang membantu di setup region Indonesia (AWS ap-southeast-1 Singapura, ap-southeast-3 Jakarta, atau GCP asia-southeast2 Jakarta):
- Gunakan
WebDriverWaitatauwaitForSelectordengan timeout eksplisit untuk iframe pembayaran — jangantime.sleeptetap. - Naikkan timeout kalau payment gateway di-host di region lain dari worker Anda; jaringan mobile-first juga menambah variasi latensi ini.
- Log kapan setiap level iframe berhasil ditemukan, supaya saat debugging solve yang gagal Anda tahu level mana yang paling lambat.
Catatan performa: deploy worker di region yang sama dengan mayoritas trafik pengguna (misalnya
ap-southeast-3) biasanya mengecilkan variasi waktu render iframe pihak ketiga dibanding setup cross-region penuh.
Iframe berlapis tiga atau lebih: merangkai frame switch
Untuk nesting lebih dari dua level, prinsipnya tetap sama, hanya diulang per level:
- Masuk ke level terluar, lalu cari iframe berikutnya di dalam context itu.
- Ulangi switch untuk setiap level sampai Anda sampai di context yang benar-benar merender widget CAPTCHA.
- Setelah inject token, keluar level demi level (atau langsung ke
default_content()/frame utama), jangan lompat langsung dari level terdalam.
Selenium
# Main → iframe-1 → iframe-2 → CAPTCHA
driver.switch_to.frame(driver.find_element(By.ID, "iframe-1"))
driver.switch_to.frame(driver.find_element(By.ID, "iframe-2"))
# Now extract sitekey from this context
# To go back up one level:
driver.switch_to.parent_frame()
# To go back to main:
driver.switch_to.default_content()
Puppeteer
const frame1 = await (await page.$('iframe#iframe-1')).contentFrame();
const frame2 = await (await frame1.$('iframe#iframe-2')).contentFrame();
// Extract sitekey from frame2
Kesalahan yang sering bikin solve gagal
Empat pola kegagalan ini menyumbang hampir semua tiket dukungan seputar CAPTCHA di iframe bersarang:
| Kesalahan | Yang terjadi | Solusi |
|---|---|---|
Pakai URL halaman utama sebagai pageurl |
Situs menolak token meski status CaptchaAI sudah sukses | Kirim URL frame yang benar-benar merender widget CAPTCHA |
| Lupa switch balik ke parent frame | Aksi Selenium/Puppeteer berikutnya gagal total | Panggil switch_to.parent_frame() atau switch_to.default_content() tepat setelah inject token |
| Ambil sitekey dari iframe yang salah | CaptchaAI membalas ERROR_WRONG_GOOGLEKEY |
Sitekey ada di iframe yang membungkus .g-recaptcha, bukan iframe Google challenge paling dalam |
| Iframe cross-origin memblokir eksekusi JS | Konsol browser melempar SecurityError |
Jangan jalankan execute_script di dalam iframe cross-origin — baca sitekey langsung dari atribut src-nya |
Checklist cepat sebelum deploy ke production:
- Sitekey diambil dari iframe yang benar-benar merender
.g-recaptcha, bukan iframe Google paling dalam. pageurlyang dikirim kein.phpadalah URL frame yang merender widget, bukan URL halaman utama.- Kode Anda memanggil
switch_to.parent_frame()/switch_to.default_content()(ataucontentFrame()setara di Puppeteer) tepat setelah inject token. - Tidak ada
execute_scriptyang dijalankan di dalam iframe cross-origin.
Urutan debug cepat kalau status CaptchaAI sudah sukses tapi situs tetap menolak:
- Bandingkan
pageurlyang Anda kirim dengan URL frame yang benar-benar merender.g-recaptcha— bukan URL halaman utama. - Pastikan token disuntikkan ke frame yang sama tempat sitekey diambil, bukan ke
default_content(). - Cek apakah situs punya
data-callbackkustom yang perlu dipanggil manual setelah token disuntik, bukan cuma mengisitextarea.
Pertanyaan seputar ekstraksi CAPTCHA dari iframe bersarang
Iframe-nya cross-origin — apakah sitekey tetap bisa diambil?
Bisa. Sitekey ada di atribut src iframe atau atribut data-sitekey-nya, dan keduanya tetap terbaca dari parent frame walau kontennya cross-origin — Anda tidak perlu menjalankan JavaScript di dalam iframe itu sama sekali.
Apakah pola ekstraksi yang sama berlaku untuk Cloudflare Turnstile di dalam iframe, bukan hanya reCAPTCHA?
Ya. Turnstile juga sering dirender di iframe pihak ketiga dengan pola serupa: ambil sitekey Turnstile, kirim dengan method: turnstile, lalu suntik hasilnya ke cf-turnstile-response — bukan g-recaptcha-response, yang khusus reCAPTCHA.
Selector iframe[src*="recaptcha/api2/anchor"] tidak ketemu — apakah beda untuk reCAPTCHA invisible?
Polanya sama. reCAPTCHA invisible tetap merender iframe recaptcha/api2/anchor begitu widget dipicu (biasanya saat tombol submit diklik), hanya saja iframe itu belum ada di DOM sampai trigger-nya jalan. Tunggu event pemicunya dulu (klik tombol, submit form) baru jalankan pencarian iframe — jangan cari sitekey sebelum widget benar-benar dirender.
Apakah teknik ini juga jalan untuk widget GeeTest v3 yang dirender di dalam iframe?
Ya, prinsipnya sama — bedanya cuma method di in.php (geetest, bukan userrecaptcha) dan parameternya.
Checkout-nya SPA (React/Vue) dan iframe payment gateway baru muncul setelah render sisi klien — apakah pola ekstraksinya beda?
Tidak. SPA hanya menunda kapan iframe-nya muncul di DOM, bukan cara Anda membacanya. Pastikan WebDriverWait/waitForSelector menunggu elemen iframe itu sendiri (bukan elemen lain di halaman) sebelum switch_to.frame() atau contentFrame() dijalankan — selector dan langkah ekstraksi sitekey setelahnya persis sama.
Panduan Terkait
- Menangani beberapa CAPTCHA dalam satu halaman
- Cara mengekstrak parameter reCAPTCHA dari page source
- Solve CAPTCHA dengan Selenium + Python