Integrations

Selenium Wire + CaptchaAI: Request Interception untuk Solve CAPTCHA

Kalau skrip Selenium Anda menebak jenis CAPTCHA dari DOM, cepat atau lambat tebakan itu meleset. Widget dimuat di dalam iframe lintas-origin, sitekey baru dikirim setelah satu panggilan AJAX, dan halaman terlihat bersih padahal challenge sudah aktif. Selenium Wire menutup celah itu: setiap request yang dikeluarkan browser tersedia sebagai objek Python, jadi jenis CAPTCHA dan sitekey bisa dibaca langsung dari lalu lintas jaringan, lalu dikirim ke CaptchaAI untuk diselesaikan.

Seluruh contoh di bawah memakai satu kelas Python yang sama dan menutup empat hal:

  • Deteksi reCAPTCHA v2, reCAPTCHA v3, dan Cloudflare Turnstile dari driver.requests
  • Interceptor untuk menambah header, memblokir skrip pelacakan, dan mengubah isi respons
  • Rotasi proxy per request serta ekspor HAR sebagai bahan debugging
  • Perhitungan thread agar biaya bulanan bisa diprediksi sejak awal

Kenapa deteksi lewat jaringan lebih andal daripada parsing DOM

Selenium standar hanya melihat hasil akhir render. Pada iframe lintas-origin, document.querySelector('[data-sitekey]') mengembalikan null walaupun challenge-nya jelas ada. Iframe itu tetap harus meminta recaptcha/api2/anchor?k=... lewat jaringan — dan di URL itulah sitekey terbaca apa adanya.

Selenium Wire membungkus WebDriver dengan proxy lokal dan menyediakan setiap pasangan request-respons sebagai objek Python:

Kemampuan Selenium standar Selenium Wire
Membaca request Tidak Ya
Mengubah header Terbatas Kontrol penuh
Mencegat respons Tidak Ya
Dukungan proxy Dasar Termasuk proxy dengan autentikasi
Log jaringan Hanya lewat DevTools Objek Python
Ekspor HAR Tidak Ya

Instalasi dan hal yang perlu disiapkan

Satu perintah pip cukup; webdriver-manager menyamakan versi ChromeDriver dengan Chrome di runner.

pip install seleniumwire selenium webdriver-manager requests

Selenium Wire memasang sertifikatnya sendiri agar bisa membaca lalu lintas HTTPS. Di CI, pastikan port lokal tidak diblokir kebijakan jaringan.


Kelas integrasi: deteksi, solve, lalu inject token

Kelas berikut dipakai ulang di sisa artikel, mengikuti empat langkah standar integrasi CaptchaAI:

  1. Kirim task ke in.php berisi method, sitekey, dan pageurl
  2. Simpan task ID dari respons pengiriman
  3. Polling res.php sampai statusnya bukan CAPCHA_NOT_READY
  4. Pakai token yang dikembalikan pada form target
from seleniumwire import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
import requests
import time
import json

class SeleniumWireCaptchaSolver:
    BASE_URL = "https://ocr.captchaai.com"

    def __init__(self, api_key, proxy=None):
        self.api_key = api_key
        self.proxy = proxy
        self.driver = None

    def create_driver(self, headless=True):
        options = webdriver.ChromeOptions()
        if headless:
            options.add_argument("--headless=new")
        options.add_argument("--no-sandbox")
        options.add_argument("--window-size=1920,1080")

        wire_options = {}
        if self.proxy:
            wire_options["proxy"] = {
                "http": self.proxy,
                "https": self.proxy,
            }

        self.driver = webdriver.Chrome(
            service=Service(ChromeDriverManager().install()),
            options=options,
            seleniumwire_options=wire_options,
        )
        return self.driver

    def detect_captcha_from_requests(self):
        """Detect CAPTCHA type from intercepted network requests."""
        for request in self.driver.requests:
            url = request.url

            if "recaptcha/api2/anchor" in url or "recaptcha/enterprise/anchor" in url:
                # Extract sitekey from reCAPTCHA iframe URL
                import re
                match = re.search(r"k=([A-Za-z0-9_-]+)", url)
                if match:
                    return {
                        "type": "recaptcha_v2",
                        "sitekey": match.group(1),
                        "page_url": self.driver.current_url,
                    }

            if "recaptcha/api2/reload" in url or "recaptcha/enterprise/reload" in url:
                return {
                    "type": "recaptcha_v3",
                    "sitekey": self._extract_v3_sitekey(),
                    "page_url": self.driver.current_url,
                }

            if "challenges.cloudflare.com" in url:
                sitekey = self._extract_turnstile_sitekey()
                if sitekey:
                    return {
                        "type": "turnstile",
                        "sitekey": sitekey,
                        "page_url": self.driver.current_url,
                    }

        return None

    def _extract_v3_sitekey(self):
        for request in self.driver.requests:
            if "recaptcha" in request.url and "render=" in request.url:
                import re
                match = re.search(r"render=([A-Za-z0-9_-]+)", request.url)
                if match:
                    return match.group(1)
        return self.driver.execute_script(
            "return document.querySelector('[data-sitekey]')?.getAttribute('data-sitekey')"
        )

    def _extract_turnstile_sitekey(self):
        return self.driver.execute_script(
            "return document.querySelector('.cf-turnstile[data-sitekey]')?.getAttribute('data-sitekey')"
        )

    def solve_captcha(self, captcha_info):
        """Solve detected CAPTCHA via CaptchaAI API."""
        params = {"key": self.api_key, "json": 1}

        if captcha_info["type"] == "recaptcha_v2":
            params.update({
                "method": "userrecaptcha",
                "googlekey": captcha_info["sitekey"],
                "pageurl": captcha_info["page_url"],
            })
        elif captcha_info["type"] == "recaptcha_v3":
            params.update({
                "method": "userrecaptcha",
                "googlekey": captcha_info["sitekey"],
                "pageurl": captcha_info["page_url"],
                "version": "v3",
                "action": "verify",
            })
        elif captcha_info["type"] == "turnstile":
            params.update({
                "method": "turnstile",
                "key": captcha_info["sitekey"],
                "pageurl": captcha_info["page_url"],
            })

        # Submit
        resp = requests.post(f"{self.BASE_URL}/in.php", data=params)
        data = resp.json()
        if data["status"] != 1:
            raise Exception(f"Submit: {data['request']}")

        task_id = data["request"]

        # Poll
        for _ in range(60):
            time.sleep(5)
            resp = requests.get(f"{self.BASE_URL}/res.php", params={
                "key": self.api_key,
                "action": "get",
                "id": task_id,
                "json": 1,
            })
            data = resp.json()
            if data["request"] == "CAPCHA_NOT_READY":
                continue
            if data["status"] != 1:
                raise Exception(f"Solve: {data['request']}")
            return data["request"]

        raise Exception("Timeout")

    def inject_token(self, captcha_type, token):
        """Inject solved token into the page."""
        if captcha_type in ("recaptcha_v2", "recaptcha_v3"):
            self.driver.execute_script(f"""
                document.querySelector('#g-recaptcha-response').value = '{token}';
                // Try hidden textareas in iframes
                document.querySelectorAll('[name="g-recaptcha-response"]').forEach(
                    el => el.value = '{token}'
                );
            """)
        elif captcha_type == "turnstile":
            self.driver.execute_script(f"""
                const input = document.querySelector('[name="cf-turnstile-response"]');
                if (input) input.value = '{token}';
            """)

    def close(self):
        if self.driver:
            self.driver.quit()

Perhatikan pemetaan method-nya:

  • reCAPTCHA v2 dan v3 → userrecaptcha; v3 menambahkan version dan action
  • Cloudflare Turnstile → turnstile
  • GeeTest v3 → geetest

Token reCAPTCHA masuk ke g-recaptcha-response, token Turnstile ke cf-turnstile-response; tertukar sedikit saja, form menolak tanpa pesan yang jelas.


Alur kerja lengkap pada halaman login staging

Skenario umumnya: agensi price-monitoring di Jakarta menjalankan regression test malam hari terhadap halaman login staging miliknya sendiri, dengan runner di AWS ap-southeast-3. Karena challenge dilayani dari edge Cloudflare, latency ikut menentukan berapa lama jeda perlu ditahan sebelum request CAPTCHA tercatat.

def automate_login():
    solver = SeleniumWireCaptchaSolver(
        api_key="YOUR_API_KEY",
        proxy="http://user:pass@proxy.example.com:8080"
    )

    try:
        driver = solver.create_driver(headless=True)

        # Navigate and wait for CAPTCHA resources to load
        driver.get("https://staging.example.com/qa-login")
        time.sleep(3)

        # Fill form
        driver.find_element("id", "email").send_keys("user@example.com")
        driver.find_element("id", "password").send_keys("password123")

        # Detect CAPTCHA from network requests
        captcha = solver.detect_captcha_from_requests()

        if captcha:
            print(f"Found {captcha['type']} (sitekey: {captcha['sitekey'][:20]}...)")
            token = solver.solve_captcha(captcha)
            solver.inject_token(captcha["type"], token)
            print(f"Token injected: {token[:50]}...")

        # Submit
        driver.find_element("id", "submit").click()
        time.sleep(3)

        print(f"Current URL: {driver.current_url}")

    finally:
        solver.close()

automate_login()

Kalau detect_captcha_from_requests() mengembalikan None, biasanya halaman selesai dimuat sebelum iframe CAPTCHA mengeluarkan request pertamanya.


Mencegat dan memodifikasi request

Tiga interceptor berikut menutupi kebutuhan harian.

Menambahkan header sendiri

request_interceptor dipanggil sebelum setiap request dikirim, jadi header bisa ditambah atau ditimpa dari satu tempat.

def add_headers_interceptor(request):
    request.headers["X-Custom-Header"] = "value"
    request.headers["Accept-Language"] = "en-US,en;q=0.9"

driver = solver.create_driver()
driver.request_interceptor = add_headers_interceptor

Memblokir skrip pelacakan

Halaman berat menghabiskan detik pertama untuk analytics dan iklan; menggugurkannya memangkas waktu muat sekaligus merapikan driver.requests.

def block_trackers(request):
    blocked = ["analytics", "tracking", "ads", "facebook.net", "doubleclick"]
    if any(b in request.url for b in blocked):
        request.abort()

driver.request_interceptor = block_trackers

Mengubah isi respons

response_interceptor menerima request beserta responsnya, sehingga body bisa diubah sebelum sampai ke halaman — berguna untuk menguji cabang error di aplikasi Anda sendiri.

def modify_response(request, response):
    if "captcha-config" in request.url:
        # Modify CAPTCHA configuration response
        import json
        body = json.loads(response.body.decode("utf-8"))
        body["difficulty"] = "easy"
        response.body = json.dumps(body).encode("utf-8")

driver.response_interceptor = modify_response

Menarik parameter CAPTCHA dari panggilan AJAX

Sebagian situs tidak menaruh sitekey di HTML; nilainya datang lewat endpoint konfigurasi. Fungsi berikut mengawasi jaringan sampai ada body JSON yang memuat sitekey.

def extract_captcha_from_api(driver, timeout=10):
    """Watch network for CAPTCHA configuration API calls."""
    start = time.time()

    while time.time() - start < timeout:
        for request in driver.requests:
            if request.response and "captcha" in request.url.lower():
                try:
                    data = json.loads(request.response.body.decode())
                    if "sitekey" in str(data) or "siteKey" in str(data):
                        return data
                except (json.JSONDecodeError, UnicodeDecodeError):
                    pass
        time.sleep(0.5)

    return None

Rotasi proxy per request

Konfigurasi proxy ditulis langsung di seleniumwire_options, termasuk proxy yang memerlukan autentikasi — di Selenium standar hal ini butuh ekstensi tambahan.

from seleniumwire import webdriver

proxies = [
    "http://user:pass@proxy1.example.com:8080",
    "http://user:pass@proxy2.example.com:8080",
    "http://user:pass@proxy3.example.com:8080",
]

current_proxy = [0]

def rotate_proxy(request):
    proxy = proxies[current_proxy[0] % len(proxies)]
    request.headers["Proxy-Authorization"] = None  # Reset
    current_proxy[0] += 1

wire_options = {
    "proxy": {
        "http": proxies[0],
        "https": proxies[0],
    }
}

driver = webdriver.Chrome(seleniumwire_options=wire_options)

Pakai hanya egress jaringan yang Anda miliki atau sewa resmi. Untuk uji regional, satu egress di Singapura (ap-southeast-1) dan satu di Jakarta cukup untuk melihat beda perilaku challenge antar-region.

Catatan kepatuhan: UU Pelindungan Data Pribadi (UU 27/2022) membuat prinsip "ambil hanya data yang boleh Anda proses" bukan sekadar anjuran etika.


Membaca lalu lintas CAPTCHA apa adanya

Ketika integrasi tiba-tiba berhenti, periksa request CAPTCHA mana yang terjadi dan status apa yang dikembalikan.

def analyze_captcha_traffic(driver):
    """Analyze all CAPTCHA-related network traffic."""
    captcha_requests = []

    for request in driver.requests:
        if any(k in request.url for k in ["recaptcha", "turnstile", "hcaptcha", "geetest"]):
            captcha_requests.append({
                "url": request.url,
                "method": request.method,
                "status": request.response.status_code if request.response else None,
                "content_type": request.response.headers.get("Content-Type") if request.response else None,
                "size": len(request.response.body) if request.response and request.response.body else 0,
            })

    print(f"\nCAPTCHA Network Traffic ({len(captcha_requests)} requests):")
    for req in captcha_requests:
        print(f"  [{req['status']}] {req['method']} {req['url'][:80]}...")

    return captcha_requests

Ekspor HAR sebagai bahan debugging

HAR berguna saat masalahnya sulit diulang: simpan satu file per kegagalan, lalu buka di DevTools atau kirim ke rekan setim.

from seleniumwire.utils import decode

def export_har(driver, filename="captcha_traffic.har"):
    """Export HAR file for debugging CAPTCHA issues."""
    import json

    har = {
        "log": {
            "version": "1.2",
            "entries": []
        }
    }

    for request in driver.requests:
        if not request.response:
            continue

        entry = {
            "request": {
                "method": request.method,
                "url": request.url,
                "headers": [{"name": k, "value": v} for k, v in request.headers.items()],
            },
            "response": {
                "status": request.response.status_code,
                "headers": [{"name": k, "value": v} for k, v in request.response.headers.items()],
            },
        }
        har["log"]["entries"].append(entry)

    with open(filename, "w") as f:
        json.dump(har, f, indent=2)

    print(f"HAR exported: {filename} ({len(har['log']['entries'])} entries)")

Masalah umum dan cara mengatasinya

Gejala Penyebab umum Yang perlu dilakukan
seleniumwire gagal di-import Paket belum terpasang pip install seleniumwire
driver.requests kosong Halaman selesai dimuat sebelum request CAPTCHA keluar Tambahkan time.sleep(3) setelah navigasi
Kesalahan SSL saat memakai proxy Sertifikat proxy tidak dipercaya Setel seleniumwire_options['verify_ssl'] = False
Pemakaian memori naik terus Riwayat request menumpuk pada proses yang berjalan lama Panggil del driver.requests secara berkala
Halaman terasa lambat Seluruh lalu lintas melewati proxy lokal Batasi dengan exclude_hosts di seleniumwire_options
Deteksi mengembalikan None Widget dimuat di dalam iframe Telusuri driver.requests untuk URL iframe-nya

Urutan diagnosis yang paling cepat menemukan penyebabnya:

  1. Cetak jumlah driver.requests tepat sebelum deteksi
  2. Saring URL yang memuat recaptcha atau cloudflare
  3. Ekspor HAR-nya kalau kegagalan hanya muncul sesekali

Menghitung thread untuk pipeline Selenium Wire

Biaya CaptchaAI dihitung per thread yang berjalan bersamaan, bukan per solve. Jadi angka yang perlu diperkirakan cuma satu: berapa browser yang menunggu token pada saat bersamaan.

  • Regression run malam hari dengan 3–5 browser paralel: BASIC ($15/bulan, 5 threads) sudah memadai
  • Suite QA yang jalan di beberapa cabang sekaligus, sekitar 10–15 browser: STANDARD ($30/bulan, 15 threads)
  • Crawler produksi dengan puluhan worker: ADVANCE ($90/bulan, 50 threads)

Karena tagihannya tetap berapa pun jumlah solve-nya, biaya bulanan bisa dikunci sejak awal — penting untuk kontrak beranggaran tetap yang jamak di komunitas otomasi Indonesia.


Pertanyaan yang sering muncul

Kenapa driver.requests kosong padahal CAPTCHA jelas muncul di layar?

Hampir selalu soal urutan waktu: daftar baru terisi setelah browser mengirim request, sedangkan detect_captcha_from_requests() umumnya dipanggil tepat setelah driver.get(). Beri jeda beberapa detik atau tunggu container widget muncul, baru periksa daftarnya.

Berapa thread yang dibutuhkan kalau saya menjalankan 10 browser Selenium Wire sekaligus?

Satu thread setara satu CAPTCHA yang sedang diselesaikan, jadi patokannya jumlah browser yang menunggu token bersamaan, bukan total solve per hari. Sepuluh browser paralel nyaman di STANDARD ($30/bulan, 15 threads); kalau hanya tiga sampai lima yang benar-benar bersamaan, BASIC ($15/bulan, 5 threads) sudah cukup.

Apakah proxy dengan autentikasi bisa dipakai bersama Selenium Wire?

Bisa, dan itu salah satu alasan utama orang memilihnya: kredensial cukup ditulis di URL pada seleniumwire_options, tanpa ekstensi tambahan. Untuk host yang tidak perlu lewat proxy, pakai exclude_hosts agar halaman tidak melambat.

CAPTCHA di traffic ternyata hCaptcha — apa yang bisa dilakukan?

hCaptcha tidak didukung saat ini, begitu pula FunCaptcha (Arkose Labs); GeeTest v4 masih berstatus segera hadir. Lewat API Anda bisa menyelesaikan reCAPTCHA v2 dan v3 termasuk Enterprise, Cloudflare Turnstile dan Challenge, GeeTest v3, CAPTCHA gambar dan grid, plus CaptchaFox (beta), Friendly Captcha (beta), dan Lemin (beta).


Mulai dari satu request yang tertangkap

Ambil API key di captchaai.com, jalankan kelas di atas terhadap halaman staging Anda sendiri, lalu lihat CAPTCHA apa yang benar-benar muncul di driver.requests. Tiga panduan lanjutannya:

Komentar dinonaktifkan untuk artikel ini.