Tutorials

Pemuatan CAPTCHA Dinamis: Deteksi CAPTCHA Lazy-Loaded

Kalau skrip Anda mengecek DOM begitu page.goto() selesai lalu menyimpulkan "tidak ada CAPTCHA di halaman ini", kesimpulan itu sering keliru. Banyak situs — terutama yang memakai reCAPTCHA v2, Cloudflare Turnstile, atau proteksi checkout — baru merender elemen CAPTCHA setelah pengguna mengklik tombol, memfokuskan field, scroll ke section tertentu, atau setelah delay beberapa detik lewat JavaScript. Saat halaman pertama kali dimuat, container dan sitekey CAPTCHA itu belum ada sama sekali di source. Panduan ini menjabarkan tiga cara mendeteksi CAPTCHA yang dimuat secara dinamis — MutationObserver, menunggu library JavaScript siap, dan intercept fungsi render — plus versi Python/Selenium dan satu flow lengkap dari deteksi sampai token terselesaikan lewat CaptchaAI.


Ringkasan: 4 Langkah Universal Deteksi-ke-Solve

Apa pun teknik deteksinya, alur setelah CAPTCHA-nya ketahuan selalu sama:

  1. Deteksi — tangkap CAPTCHA begitu dirender, lewat MutationObserver, waitForFunction, atau intercept fungsi render
  2. Kirim — kirim sitekey dan tipe CAPTCHA-nya ke in.php
  3. Polling — cek res.php setiap 5 detik sampai status bernilai 1
  4. Pakai token — injeksikan token ke field yang CAPTCHA-nya harapkan

Kapan CAPTCHA Lazy-Loaded Muncul di Halaman?

Sebelum menulis observer atau wait condition apa pun, kenali dulu pemicunya. Lima pola berikut paling sering ditemukan di form login, checkout, dan pendaftaran akun:

  • Klik tombol — tombol Submit menambahkan reCAPTCHA ke form; picu dengan mengklik tombolnya lebih dulu.
  • Form focus — CAPTCHA muncul saat field difokuskan; fokuskan field email atau password.
  • Posisi scroll — CAPTCHA di-load saat section-nya terlihat; scroll ke form.
  • Timer — CAPTCHA di-load setelah beberapa detik; tunggu delay-nya selesai.
  • Kondisi JavaScript — CAPTCHA di-load setelah response AJAX; trigger dulu request prasyaratnya.

Tim QA dan automation di Indonesia yang menguji platform e-commerce, travel, atau ticketing lokal biasanya menambahkan trigger eksplisit ini sebelum mulai polling — kalau tidak, sitekey belum ter-render dan permintaan ke in.php gagal karena parameter kosong. Ini makin terasa saat testing dari jaringan mobile-first atau region seperti AWS ap-southeast-3 (Jakarta): beri buffer waktu tunggu yang cukup di setiap metode di bawah ini.


Metode 1: Deteksi dengan MutationObserver

MutationObserver adalah cara paling andal untuk menangkap CAPTCHA yang ditambahkan ke DOM belakangan, karena dia memantau perubahan struktur halaman secara real-time tanpa perlu polling berulang di sisi JavaScript. Setup di bawah memeriksa dulu apakah elemen CAPTCHA sudah ada — untuk kasus di mana trigger-nya sudah terjadi sebelum observer dipasang — lalu memasang observer pada document.body dengan childList dan subtree aktif supaya elemen yang muncul di kedalaman DOM mana pun tetap terdeteksi.

Puppeteer

const puppeteer = require('puppeteer');

const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
await page.goto('https://staging.example.com/qa-login');

// Set up MutationObserver before triggering the CAPTCHA
const captchaInfo = await page.evaluate(() => {
  return new Promise((resolve) => {
    // Check if already present
    const existing = document.querySelector('.g-recaptcha, .cf-turnstile, .h-captcha');
    if (existing) {
      resolve({
        type: existing.className,
        sitekey: existing.getAttribute('data-sitekey'),
      });
      return;
    }

    // Watch for new elements
    const observer = new MutationObserver((mutations) => {
      for (const mutation of mutations) {
        for (const node of mutation.addedNodes) {
          if (node.nodeType !== 1) continue;

          const captcha = node.matches?.('.g-recaptcha, .cf-turnstile, .h-captcha')
            ? node
            : node.querySelector?.('.g-recaptcha, .cf-turnstile, .h-captcha');

          if (captcha) {
            observer.disconnect();
            resolve({
              type: captcha.className,
              sitekey: captcha.getAttribute('data-sitekey'),
            });
            return;
          }
        }
      }
    });

    observer.observe(document.body, { childList: true, subtree: true });

    // Timeout after 30 seconds
    setTimeout(() => {
      observer.disconnect();
      resolve(null);
    }, 30000);
  });
});

console.log('Detected CAPTCHA:', captchaInfo);

Observer di atas otomatis berhenti begitu elemen .g-recaptcha, .cf-turnstile, atau .h-captcha ditemukan, dan sudah dilengkapi timeout 30 detik sebagai pengaman kalau trigger-nya ternyata tidak pernah terjadi.

Memicu Loading CAPTCHA

// Klik tombol submit untuk trigger CAPTCHA
await page.click('#submit-btn');

// Atau fokuskan input
await page.focus('#email');

// Atau scroll ke form
await page.evaluate(() => {
  document.querySelector('#signup-form').scrollIntoView();
});

Metode 2: Menunggu Script Siap dan Intercept Render

Dua taktik lain, dipakai saat MutationObserver saja kurang presisi.

Deteksi lewat loading script CAPTCHA

Alternatif dari memantau DOM adalah memantau kapan library JavaScript CAPTCHA-nya selesai dimuat dan siap dipakai. Ini cocok dikombinasikan dengan Metode 1, karena elemen div bisa saja sudah ada di DOM sementara script grecaptcha atau turnstile masih dalam proses loading — sitekey baru valid dibaca setelah script itu benar-benar siap.

// Tunggu script reCAPTCHA selesai di-load
await page.waitForFunction(() => {
  return typeof window.grecaptcha !== 'undefined' 
    && typeof window.grecaptcha.render === 'function';
}, { timeout: 30000 });

// Sekarang ekstrak parameter
const sitekey = await page.evaluate(() => {
  const el = document.querySelector('.g-recaptcha');
  return el?.getAttribute('data-sitekey');
});

Untuk Turnstile

await page.waitForFunction(() => {
  return typeof window.turnstile !== 'undefined';
}, { timeout: 30000 });

const sitekey = await page.evaluate(() => {
  const el = document.querySelector('.cf-turnstile');
  return el?.getAttribute('data-sitekey');
});

Intercept fungsi render sebelum CAPTCHA tampil

Teknik paling presisi: hook langsung ke fungsi render milik library CAPTCHA sebelum halaman sempat memanggilnya. Dengan page.evaluateOnNewDocument, kode injeksi berjalan sebelum script apa pun di halaman dieksekusi, sehingga Anda menangkap parameter sitekey, nama fungsi callback, dan container-nya persis pada saat CAPTCHA di-render — bukan menebak dari atribut DOM setelahnya. Metode ini paling berguna ketika CAPTCHA di-render ulang berkali-kali dalam satu sesi, misalnya setiap kali percobaan submit form gagal.

// Inject before page scripts run
await page.evaluateOnNewDocument(() => {
  window.__captchaDetected = null;

  // Hook grecaptcha.render
  let _grecaptcha;
  Object.defineProperty(window, 'grecaptcha', {
    set(val) {
      _grecaptcha = val;
      const origRender = val.render;
      val.render = function(container, params) {
        window.__captchaDetected = {
          type: 'recaptcha',
          sitekey: params.sitekey,
          callback: params.callback?.name,
          container: typeof container === 'string' ? container : container.id,
        };
        return origRender.apply(this, arguments);
      };
    },
    get() { return _grecaptcha; },
  });
});

await page.goto('https://example.com/signup');

// Trigger the CAPTCHA (click, scroll, etc.)
await page.click('#show-form');

// Wait for detection
await page.waitForFunction(() => window.__captchaDetected !== null, {
  timeout: 30000,
});

const detected = await page.evaluate(() => window.__captchaDetected);
console.log('Detected:', detected);
// { type: 'recaptcha', sitekey: '6Le-wvkS...', callback: 'onCaptcha', container: 'recaptcha-box' }

Metode 3: Python dan Selenium untuk CAPTCHA Lazy-Loaded

Pola yang sama berlaku di Selenium: trigger dulu, baru WebDriverWait menunggu elemen CAPTCHA-nya muncul. expected_conditions.presence_of_element_located dengan selector gabungan (.g-recaptcha, .cf-turnstile, .h-captcha) menangkap tiga tipe CAPTCHA paling umum dalam satu wait, lalu class name elemennya yang menentukan tipe mana yang sedang dihadapi.

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

driver = webdriver.Chrome()
driver.get("https://staging.example.com/qa-login")

# Trigger the CAPTCHA loading
submit = driver.find_element(By.ID, "submit-btn")
submit.click()

# Wait for CAPTCHA to appear
try:
    captcha_el = WebDriverWait(driver, 30).until(
        EC.presence_of_element_located((
            By.CSS_SELECTOR,
            ".g-recaptcha, .cf-turnstile, .h-captcha"
        ))
    )
    sitekey = captcha_el.get_attribute("data-sitekey")
    captcha_class = captcha_el.get_attribute("class")

    if "g-recaptcha" in captcha_class:
        captcha_type = "recaptcha"
    elif "cf-turnstile" in captcha_class:
        captcha_type = "turnstile"
    else:
        captcha_type = "hcaptcha"

    print(f"Type: {captcha_type}, Sitekey: {sitekey}")
except Exception:
    print("No CAPTCHA appeared within 30 seconds")

Khusus reCAPTCHA, div .g-recaptcha kadang sudah ada di DOM padahal iframe berisi widget-nya belum selesai dimuat — kalau Anda langsung mengekstrak sitekey di titik ini, hasilnya kosong. Tunggu iframe-nya secara eksplisit:

# reCAPTCHA me-load iframe bahkan ketika div ada tapi script masih loading
WebDriverWait(driver, 30).until(
    EC.presence_of_element_located((
        By.CSS_SELECTOR,
        "iframe[src*='recaptcha'], iframe[src*='challenges.cloudflare.com']"
    ))
)
print("CAPTCHA iframe loaded")

Flow Lengkap: dari Deteksi sampai Token Terselesaikan

Fungsi detect_and_solve di bawah menyatukan seluruh alurnya jadi satu pemanggilan: trigger CAPTCHA lewat callback opsional trigger_action, tunggu elemennya muncul, baca sitekey dan tentukan method API-nya, kirim ke in.php, polling res.php sampai status bernilai 1, lalu injeksikan token ke field yang sesuai — pola empat langkah yang sama seperti di ringkasan awal, konsisten di semua tipe CAPTCHA yang didukung CaptchaAI.

import requests
import time

def detect_and_solve(driver, api_key, trigger_action=None):
    """Detect a lazy-loaded CAPTCHA, solve it, and inject the token."""

    # 1. Trigger the CAPTCHA
    if trigger_action:
        trigger_action(driver)

    # 2. Wait for it to appear
    captcha_el = WebDriverWait(driver, 30).until(
        EC.presence_of_element_located((
            By.CSS_SELECTOR,
            ".g-recaptcha, .cf-turnstile, .h-captcha"
        ))
    )

    sitekey = captcha_el.get_attribute("data-sitekey")
    page_url = driver.current_url
    captcha_class = captcha_el.get_attribute("class")

    # 3. Determine type and method
    if "g-recaptcha" in captcha_class:
        method, key_param, token_field = "userrecaptcha", "googlekey", "g-recaptcha-response"
    elif "cf-turnstile" in captcha_class:
        method, key_param, token_field = "turnstile", "sitekey", "cf-turnstile-response"
    else:
        method, key_param, token_field = "hcaptcha", "sitekey", "h-captcha-response"

    # 4. Solve with CaptchaAI
    resp = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": api_key, "method": method,
        key_param: sitekey, "pageurl": page_url, "json": "1",
    }).json()

    task_id = resp["request"]
    for _ in range(24):
        time.sleep(5)
        result = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": api_key, "action": "get", "id": task_id, "json": "1"
        }).json()
        if result["status"] == 1:
            token = result["request"]
            break

    # 5. Inject
    driver.execute_script(f"""
        const el = document.querySelector('textarea[name="{token_field}"], input[name="{token_field}"]');
        if (el) el.value = arguments[0];
    """, token)

    return token

Untuk pipeline QA yang menjalankan banyak sesi deteksi-dan-solve paralel, jumlah thread yang dibutuhkan mengikuti jumlah sesi browser yang berjalan bersamaan, bukan jumlah CAPTCHA yang diselesaikan.


Troubleshooting Deteksi CAPTCHA Dinamis

Empat masalah berikut paling sering muncul saat CAPTCHA-nya dimuat secara dinamis:

  • CAPTCHA tidak pernah muncul — trigger action-nya salah; periksa halaman untuk menemukan apa yang sebenarnya memicu CAPTCHA.
  • Sitekey bernilai null — elemen sudah ada tapi script-nya belum berjalan; tunggu iframe CAPTCHA-nya, bukan hanya div-nya.
  • Observer tidak mendeteksinya — CAPTCHA-nya sudah ada sebelum observer dipasang; periksa elemen yang sudah ada lebih dulu sebelum setup observer.
  • Timeout terus terjadi — CAPTCHA hanya di-load untuk pengguna asli; gunakan browser penuh dengan sinyal browser yang realistis.

Pertanyaan seputar CAPTCHA Lazy-Loaded

Bagaimana cara memastikan sebuah CAPTCHA memang lazy-loaded, bukan gagal render?

Buka source halaman awal (Ctrl+U) sebelum berinteraksi apa pun. Kalau elemen atau script CAPTCHA-nya tidak ada di situ tapi muncul setelah Anda klik, scroll, atau menunggu, berarti memang lazy-loaded — bukan bug.

Berapa idealnya timeout untuk MutationObserver atau WebDriverWait?

30 detik seperti pada contoh di atas aman untuk sebagian besar situs, tapi kalau target Anda diakses dari jaringan mobile atau region dengan latensi tinggi, naikkan ke 45–60 detik daripada memperbanyak retry. Timeout yang terlalu pendek adalah penyebab paling umum dari "CAPTCHA tidak pernah muncul", padahal sebenarnya cuma butuh waktu lebih lama.

Apakah teknik deteksi ini tetap berfungsi di halaman yang dibangun dengan React atau framework SPA lain?

Ya. MutationObserver dan waitForFunction bekerja di level DOM dan window object, jadi tidak peduli apakah CAPTCHA-nya di-render lewat komponen React, Vue, atau vanilla JavaScript. Yang perlu disesuaikan hanya selector-nya, kalau framework tersebut membungkus CAPTCHA dalam custom element atau shadow DOM.

Apakah metode ini tetap jalan di browser headless?

Sebagian situs sengaja hanya me-render CAPTCHA untuk browser non-headless sebagai sinyal deteksi bot tambahan. Gunakan headless: 'new' di Puppeteer atau konfigurasi Selenium standar dengan sinyal browser yang realistis — kalau CAPTCHA tetap tidak muncul di mode headless, itu indikasi situsnya memang memeriksa mode browser, bukan masalah di kode deteksi Anda.

Setelah CAPTCHA-nya terdeteksi, apakah CaptchaAI bisa langsung menyelesaikan semua tipenya?

CaptchaAI mendukung reCAPTCHA v2/v3, Cloudflare Turnstile dan Challenge, GeeTest v3, image/OCR, grid image, dan BLS secara penuh, plus CaptchaFox, Friendly Captcha, dan Lemin dalam status beta. hCaptcha dan FunCaptcha belum didukung — kalau observer Anda mendeteksi .h-captcha, task tersebut perlu ditangani lewat layanan lain.


Selesaikan CAPTCHA yang Terdeteksi Langsung dengan CaptchaAI

Setelah sitekey dan tipe CAPTCHA-nya ketahuan lewat salah satu metode di atas, tinggal kirim ke CaptchaAI. Dapatkan API key Anda di captchaai.com.


Panduan terkait

Komentar dinonaktifkan untuk artikel ini.