Pemecahan Masalah

Menangani reCAPTCHA v2 dan Cloudflare Turnstile di Situs yang Sama

Beberapa situs menggunakan reCAPTCHA di halaman loginnya dan Turnstile di halaman checkoutnya. Tes A/B lainnya antar penyedia — URL yang sama menampilkan reCAPTCHA untuk satu pengunjung dan Turnstile untuk pengunjung lainnya. Melakukan hardcoding pada metode pemecah tunggal berarti otomatisasi Anda rusak setiap kali menemukan jenis lainnya.

Mengapa Situs Menggunakan Beberapa Penyedia CAPTCHA

Skenario Bagaimana Kelihatannya
Beda halaman, beda penyedia Masuk = reCAPTCHA, checkout = Turnstile
Penyedia pengujian A/B Halaman yang sama secara acak menampilkan jenis mana pun
Migrasi sedang berlangsung Halaman lama memiliki reCAPTCHA, halaman baru memiliki Turnstile
Penggantian pada kegagalan Penyedia primer gagal → kembali ke sekunder
Variasi regional reCAPTCHA untuk pengunjung AS, Turnstile untuk UE (GDPR)

Python: Deteksi dan Solve Otomatis

import requests
import time
import re
from dataclasses import dataclass

API_KEY = "YOUR_API_KEY"
SUBMIT_URL = "https://ocr.captchaai.com/in.php"
RESULT_URL = "https://ocr.captchaai.com/res.php"


@dataclass
class CaptchaInfo:
    provider: str     # "recaptcha" or "turnstile"
    method: str       # API method name
    sitekey: str
    pageurl: str
    response_field: str  # Form field name for the token


def detect_captcha_type(html, pageurl):
    """
    Detect which CAPTCHA provider is on the page.
    Returns CaptchaInfo or None.
    """
    # Check for Turnstile
    turnstile_match = re.search(
        r'class=["\'][^"\']*cf-turnstile[^"\']*["\'][^>]*data-sitekey=["\']([^"\']+)["\']',
        html,
    )
    if not turnstile_match:
        turnstile_match = re.search(
            r'data-sitekey=["\']([^"\']+)["\'][^>]*class=["\'][^"\']*cf-turnstile',
            html,
        )

    if turnstile_match:
        return CaptchaInfo(
            provider="turnstile",
            method="turnstile",
            sitekey=turnstile_match.group(1),
            pageurl=pageurl,
            response_field="cf-turnstile-response",
        )

    # Check for reCAPTCHA
    recaptcha_match = re.search(
        r'class=["\'][^"\']*g-recaptcha[^"\']*["\'][^>]*data-sitekey=["\']([^"\']+)["\']',
        html,
    )
    if not recaptcha_match:
        recaptcha_match = re.search(
            r'data-sitekey=["\']([^"\']+)["\'][^>]*class=["\'][^"\']*g-recaptcha',
            html,
        )

    # Also check for script-rendered reCAPTCHA
    if not recaptcha_match:
        recaptcha_match = re.search(
            r'grecaptcha\.render\([^,]+,\s*\{[^}]*["\']sitekey["\']\s*:\s*["\']([^"\']+)["\']',
            html,
        )

    if recaptcha_match:
        return CaptchaInfo(
            provider="recaptcha",
            method="userrecaptcha",
            sitekey=recaptcha_match.group(1),
            pageurl=pageurl,
            response_field="g-recaptcha-response",
        )

    return None


def solve_captcha(info):
    """Solve any detected CAPTCHA type via CaptchaAI."""
    params = {
        "key": API_KEY,
        "method": info.method,
        "json": 1,
    }

    if info.method == "userrecaptcha":
        params["googlekey"] = info.sitekey
        params["pageurl"] = info.pageurl
    elif info.method == "turnstile":
        params["sitekey"] = info.sitekey
        params["pageurl"] = info.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 process_page(session, url):
    """Fetch page, detect CAPTCHA type, solve, and return form-ready data."""
    response = session.get(url)
    captcha_info = detect_captcha_type(response.text, url)

    if not captcha_info:
        print(f"No CAPTCHA detected on {url}")
        return None

    print(f"Detected {captcha_info.provider} on {url}")
    print(f"  Sitekey: {captcha_info.sitekey[:30]}...")

    token = solve_captcha(captcha_info)
    print(f"  Solved: {token[:30]}...")

    return {
        "provider": captcha_info.provider,
        "response_field": captcha_info.response_field,
        "token": token,
    }


# Usage: Handle multiple pages with different providers
session = requests.Session()

pages = [
    "https://staging.example.com/qa-login",      # Might have reCAPTCHA
    "https://example.com/checkout",   # Might have Turnstile
]

for url in pages:
    result = process_page(session, url)
    if result:
        form_data = {result["response_field"]: result["token"]}
        # Add other form fields...
        # session.post(url, data=form_data)

JavaScript: Deteksi CAPTCHA Dinamis

const API_KEY = "YOUR_API_KEY";
const SUBMIT_URL = "https://ocr.captchaai.com/in.php";
const RESULT_URL = "https://ocr.captchaai.com/res.php";

function detectCaptchaType(html, pageurl) {
  // Turnstile
  const turnstileMatch = html.match(/cf-turnstile[^>]*data-sitekey=["']([^"']+)["']/);
  if (turnstileMatch) {
    return { provider: "turnstile", method: "turnstile", sitekey: turnstileMatch[1], pageurl, field: "cf-turnstile-response" };
  }

  // reCAPTCHA
  const recaptchaMatch = html.match(/g-recaptcha[^>]*data-sitekey=["']([^"']+)["']/);
  if (recaptchaMatch) {
    return { provider: "recaptcha", method: "userrecaptcha", sitekey: recaptchaMatch[1], pageurl, field: "g-recaptcha-response" };
  }

  // Script-rendered reCAPTCHA
  const scriptMatch = html.match(/sitekey["']\s*:\s*["']([^"']+)["']/);
  if (scriptMatch) {
    return { provider: "recaptcha", method: "userrecaptcha", sitekey: scriptMatch[1], pageurl, field: "g-recaptcha-response" };
  }

  return null;
}

async function solveCaptcha(info) {
  const body = new URLSearchParams({ key: API_KEY, method: info.method, json: "1" });
  if (info.method === "userrecaptcha") { body.set("googlekey", info.sitekey); body.set("pageurl", info.pageurl); }
  else if (info.method === "turnstile") { body.set("sitekey", info.sitekey); body.set("pageurl", info.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 processPage(url) {
  const response = await fetch(url);
  const html = await response.text();
  const info = detectCaptchaType(html, url);

  if (!info) { console.log(`No CAPTCHA on ${url}`); return null; }
  console.log(`${info.provider} detected on ${url}`);

  const token = await solveCaptcha(info);
  return { provider: info.provider, field: info.field, token };
}

// Usage
const pages = ["https://staging.example.com/qa-login", "https://example.com/checkout"];
for (const url of pages) {
  const result = await processPage(url);
  if (result) {
    console.log(`Solved ${result.provider}: ${result.token.substring(0, 30)}...`);
  }
}

Referensi Deteksi Penyedia

Penyedia Penanda HTML URL skrip Bidang Respon
reCAPTCHA v2 class="g-recaptcha" google.com/recaptcha/api.js g-recaptcha-response
Cloudflare Turnstile class="cf-turnstile" challenges.cloudflare.com/turnstile cf-turnstile-response
hCaptcha class="h-captcha" js.hcaptcha.com/1/api.js h-captcha-response

Pemecahan Masalah

Masalah Penyebab Solusi
Jenis CAPTCHA yang salah terdeteksi Regex cocok dengan elemen yang salah Periksa nama kelas khusus penyedia terlebih dahulu, bukan hanya data-sitekey
Token ditolak setelah solve yang benar Menggunakan nama bidang respons yang salah Cocokkan nama bidang dengan penyedia: g-recaptcha-response vs cf-turnstile-response
Jenis CAPTCHA berubah antar kunjungan Pengujian A/B atau seleksi berbasis geografis Selalu deteksi secara dinamis; jangan pernah melakukan hardcode pada penyedia
Kedua penyedia terdeteksi pada satu halaman Satu mungkin disembunyikan/inactive Periksa visibilitas elemen — selesaikan hanya CAPTCHA yang terlihat
Deteksi gagal untuk CAPTCHA yang dirender dengan skrip Tidak ada penanda HTML di sumber Periksa panggilan grecaptcha.render() atau turnstile.render() dalam skrip

Pertanyaan Umum

Bisakah suatu halaman menggunakan reCAPTCHA dan Turnstile secara bersamaan?

Jarang dalam bentuk yang sama, namun terjadi di berbagai bagian situs. Jika keduanya muncul di satu halaman, biasanya hanya satu yang aktif — periksa widget mana yang terlihat dan memiliki data-sitekey yang tidak kosong.

Apakah CaptchaAI menggunakan API yang sama untuk kedua penyedia?

Titik akhir yang sama (in.php / res.php) tetapi nilai method berbeda. reCAPTCHA menggunakan method=userrecaptcha dengan googlekey, sedangkan Turnstile menggunakan method=turnstile dengan sitekey. Pola deteksi otomatis menangani pemetaan ini.

Bagaimana cara menangani failover penyedia?

Jika situs beralih dari reCAPTCHA ke Turnstile di tengah sesi (misalnya, setelah reCAPTCHA gagal dimuat), deteksi ulang jenis CAPTCHA pada setiap permintaan halaman daripada menyimpan cache penyedia dari kunjungan pertama.

Artikel Terkait

Langkah Selanjutnya

Menangani situs dengan beberapa penyedia CAPTCHA —dapatkan kunci API CaptchaAI Andadan menerapkan deteksi otomatis.

Panduan terkait:

Komentar dinonaktifkan untuk artikel ini.