Tutorials

Ekstraksi CAPTCHA dari Iframe Bersarang

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:

  1. Masuk ke frame terluarswitch_to.frame() di Selenium atau contentFrame() di Puppeteer, menuju iframe pembayaran atau modal yang membungkus widget CAPTCHA.
  2. Temukan iframe target di dalamnya — pola paling umum adalah iframe[src*="recaptcha/api2/anchor"], atau selector setara untuk Turnstile.
  3. Ekstrak sitekey dari atribut data-sitekey pada elemen itu, atau dari parameter k= di src-nya.
  4. 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 src dan data-sitekey dari parent frame — jangan jalankan execute_script di dalamnya.
  • Apakah situs memakai reCAPTCHA, Turnstile, atau GeeTest v3? Selector dan parameter method di in.php berbeda 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.sleep di 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 WebDriverWait atau waitForSelector dengan timeout eksplisit untuk iframe pembayaran — jangan time.sleep tetap.
  • 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:

  1. Masuk ke level terluar, lalu cari iframe berikutnya di dalam context itu.
  2. Ulangi switch untuk setiap level sampai Anda sampai di context yang benar-benar merender widget CAPTCHA.
  3. 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.
  • pageurl yang dikirim ke in.php adalah URL frame yang merender widget, bukan URL halaman utama.
  • Kode Anda memanggil switch_to.parent_frame() / switch_to.default_content() (atau contentFrame() setara di Puppeteer) tepat setelah inject token.
  • Tidak ada execute_script yang dijalankan di dalam iframe cross-origin.

Urutan debug cepat kalau status CaptchaAI sudah sukses tapi situs tetap menolak:

  1. Bandingkan pageurl yang Anda kirim dengan URL frame yang benar-benar merender .g-recaptcha — bukan URL halaman utama.
  2. Pastikan token disuntikkan ke frame yang sama tempat sitekey diambil, bukan ke default_content().
  3. Cek apakah situs punya data-callback kustom yang perlu dipanggil manual setelah token disuntik, bukan cuma mengisi textarea.

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

Komentar dinonaktifkan untuk artikel ini.