Kalau solver sudah mengembalikan token tetapi form target tetap menolak submit, halaman itu biasanya memakai reCAPTCHA v2 invisible, bukan checkbox. Di API CaptchaAI bedanya hanya satu parameter, invisible=1; di browser bedanya besar, karena tidak ada widget yang bisa diklik dan token harus Anda serahkan sendiri ke callback halaman.
Urutannya di bawah mengikuti alur kerja nyata: kenali markup, deteksi dari skrip, kirim task, pasang token.
Bentuk reCAPTCHA v2 checkbox dan invisible di HTML
Format sitekey keduanya identik, jadi hanya markup halaman yang jujur menunjukkan varian mana yang dipasang.
Varian checkbox
<!-- Standard checkbox widget -->
<div class="g-recaptcha"
data-sitekey="6Le-wvkSAAAAAPBMRTvw..."
data-callback="onSubmit">
</div>
<!-- Widget renders as: -->
<!-- [✓] I'm not a robot reCAPTCHA logo -->
Pengguna melihat dan mengklik kotaknya sendiri. Bila Google curiga, kisi gambar muncul sebaris di bawah widget.
Varian invisible
<!-- Pattern 1: Invisible widget on a button -->
<button class="g-recaptcha"
data-sitekey="6Le-wvkSAAAAAPBMRTvw..."
data-callback="onSubmit"
data-size="invisible">
Submit
</button>
<!-- Pattern 2: Invisible div (programmatic trigger) -->
<div class="g-recaptcha"
data-sitekey="6Le-wvkSAAAAAPBMRTvw..."
data-size="invisible"
data-callback="onSubmit">
</div>
<!-- Pattern 3: Programmatic render -->
<script>
grecaptcha.render('submit-btn', {
sitekey: '6Le-wvkSAAAAAPBMRTvw...',
callback: onSubmit,
size: 'invisible'
});
</script>
Tidak ada elemen yang bisa diklik. reCAPTCHA aktif saat pengguna menyentuh elemen pemicu atau saat grecaptcha.execute() dijalankan kode halaman.
Deteksi varian reCAPTCHA langsung dari skrip
Untuk pipeline scraping tanpa browser, ambil HTML-nya lalu baca atributnya. Fungsi berikut mengembalikan tiga hal sekaligus: varian, sitekey, dan nama callback.
Versi Python
import requests
from bs4 import BeautifulSoup
import re
def detect_recaptcha_variant(url):
resp = requests.get(url)
soup = BeautifulSoup(resp.text, "html.parser")
# Check for invisible indicators
invisible_widget = soup.find(attrs={"data-size": "invisible", "class": "g-recaptcha"})
if invisible_widget:
return {
"variant": "invisible",
"sitekey": invisible_widget.get("data-sitekey"),
"callback": invisible_widget.get("data-callback")
}
# Check for programmatic invisible in scripts
for script in soup.find_all("script"):
if script.string and "invisible" in str(script.string):
key_match = re.search(r"sitekey['\"]?\s*[:=]\s*['\"]([^'\"]+)", script.string)
if key_match:
return {
"variant": "invisible-programmatic",
"sitekey": key_match.group(1),
"callback": "check grecaptcha.render() call"
}
# Check for standard checkbox
checkbox_widget = soup.find(class_="g-recaptcha")
if checkbox_widget:
return {
"variant": "checkbox",
"sitekey": checkbox_widget.get("data-sitekey"),
"callback": checkbox_widget.get("data-callback")
}
return None
result = detect_recaptcha_variant("https://staging.example.com/qa-login")
print(result)
Versi Node.js
Pola yang sama untuk worker berbasis Axios dan Cheerio:
const axios = require("axios");
const cheerio = require("cheerio");
async function detectRecaptchaVariant(url) {
const { data } = await axios.get(url);
const $ = cheerio.load(data);
// Check for invisible
const invisible = $(".g-recaptcha[data-size='invisible']");
if (invisible.length) {
return {
variant: "invisible",
sitekey: invisible.attr("data-sitekey"),
callback: invisible.attr("data-callback"),
};
}
// Check scripts for programmatic invisible
const scripts = $("script")
.map((_, el) => $(el).html())
.get()
.join("\n");
if (scripts.includes("invisible")) {
const keyMatch = scripts.match(/sitekey['"]?\s*[:=]\s*['"]([^'"]+)/);
if (keyMatch) {
return {
variant: "invisible-programmatic",
sitekey: keyMatch[1],
callback: "check render call",
};
}
}
// Check for standard checkbox
const checkbox = $(".g-recaptcha");
if (checkbox.length) {
return {
variant: "checkbox",
sitekey: checkbox.attr("data-sitekey"),
callback: checkbox.attr("data-callback"),
};
}
return null;
}
Cek cepat saat menelusuri halaman
Saat memeriksa halaman secara manual, satu baris di console browser sudah cukup:
const el = document.querySelector('[data-size="invisible"]');
console.log(el ? "Invisible reCAPTCHA" : "Checkbox reCAPTCHA");
Sebagian halaman merender widget lewat grecaptcha.render(), bukan lewat atribut HTML; blok pencocokan isi skrip di atas menutup celah itu.
Perbandingan singkat kedua varian
| Fitur | v2 checkbox | v2 invisible |
|---|---|---|
| Widget terlihat | Ya — checkbox "I'm not a robot" | Tidak — tersembunyi dari pengguna |
| Pemicu | Pengguna mengklik checkbox | Klik tombol, submit form, atau load halaman |
| Tantangan gambar | Muncul saat Google mencurigai bot | Popup di pojok kanan bawah bila mencurigakan |
| Field token | g-recaptcha-response |
g-recaptcha-response |
| Fungsi callback | Opsional | Hampir selalu wajib |
| Metode CaptchaAI | method=userrecaptcha |
method=userrecaptcha + invisible=1 |
| Masa berlaku token | 120 detik | 120 detik |
| Waktu penyelesaian | 10–30 detik | 10–25 detik |
Masa berlaku 120 detik itu yang paling sering menjegal worker di AWS ap-southeast-1 (Singapura) atau ap-southeast-3 (Jakarta) saat halaman target lambat: token selesai di detik ke-20, form baru siap semenit kemudian, token telanjur kedaluwarsa. Ambil token sedekat mungkin dengan momen submit.
Kirim task ke CaptchaAI
Alurnya sama: kirim ke in.php → simpan task ID → polling res.php → pakai token.
Task untuk varian checkbox
import requests
import time
resp = requests.get("https://ocr.captchaai.com/in.php", params={
"key": "YOUR_API_KEY",
"method": "userrecaptcha",
"googlekey": "6Le-wvkSAAAA...",
"pageurl": "https://example.com/form"
})
task_id = resp.text.split("|")[1]
for _ in range(60):
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": "YOUR_API_KEY", "action": "get", "id": task_id
})
if result.text.startswith("OK|"):
token = result.text.split("|")[1]
break
Task untuk varian invisible
import requests
import time
resp = requests.get("https://ocr.captchaai.com/in.php", params={
"key": "YOUR_API_KEY",
"method": "userrecaptcha",
"googlekey": "6Le-wvkSAAAA...",
"pageurl": "https://example.com/form",
"invisible": 1 # Only parameter difference
})
task_id = resp.text.split("|")[1]
for _ in range(60):
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": "YOUR_API_KEY", "action": "get", "id": task_id
})
if result.text.startswith("OK|"):
token = result.text.split("|")[1]
break
Satu-satunya baris yang berubah adalah "invisible": 1; tarif tidak berbeda antar-tipe karena paket CaptchaAI dihitung per thread, bukan per solve. BASIC ($15/bulan, 5 thread) cukup untuk satu worker QA, sedangkan STANDARD ($30/bulan, 15 thread) atau ADVANCE ($90/bulan, 50 thread) relevan saat banyak job paralel. Bagi pekerja lepas otomatisasi di Indonesia, biaya bulanan tetap juga lebih mudah ditagihkan ke klien.
Injeksi token: di sinilah keduanya benar-benar berpisah
Token-nya sama, cara memasangnya berbeda — dan di sinilah kegagalan paling sering muncul.
Checkbox: field tersembunyi biasanya sudah cukup
# Selenium — inject into hidden field
driver.execute_script(
f'document.getElementById("g-recaptcha-response").value = "{token}";'
)
# If the page uses a callback, also call it
callback = driver.find_element("css selector", ".g-recaptcha").get_attribute("data-callback")
if callback:
driver.execute_script(f'{callback}("{token}");')
Invisible: callback hampir selalu wajib dipanggil
# Selenium — inject AND call the callback
driver.execute_script(
f'document.getElementById("g-recaptcha-response").value = "{token}";'
)
# CRITICAL: Invisible reCAPTCHA almost always requires calling the callback
callback_name = driver.find_element(
"css selector", ".g-recaptcha[data-size='invisible']"
).get_attribute("data-callback")
driver.execute_script(f'{callback_name}("{token}");')
Versi Puppeteer untuk worker Node.js:
// Puppeteer — invisible callback injection
await page.evaluate((tok) => {
// Set the hidden field
document.getElementById("g-recaptcha-response").value = tok;
// Find and call the callback function
const widget = document.querySelector("[data-size='invisible']");
const cbName = widget?.getAttribute("data-callback");
if (cbName && typeof window[cbName] === "function") {
window[cbName](tok);
}
}, token);
Intinya: pada checkbox, klik pengguna sudah mendaftarkan callback sehingga mengisi field tersembunyi sering cukup. Pada invisible klik itu tidak pernah terjadi — panggil callback eksplisit, sebelum form auto-submit.
Satu solver untuk kedua varian
Karena bedanya hanya satu parameter, gabungkan deteksi dan solve. Kelas berikut menentukan varian, mengirim task, lalu mengembalikan token dan nama callback-nya.
import requests
import time
from bs4 import BeautifulSoup
class RecaptchaV2UniversalSolver:
def __init__(self, api_key):
self.api_key = api_key
def detect_and_solve(self, page_url, page_html=None):
if not page_html:
page_html = requests.get(page_url).text
soup = BeautifulSoup(page_html, "html.parser")
# Detect variant
invisible = soup.find(attrs={"data-size": "invisible", "class": "g-recaptcha"})
widget = invisible or soup.find(class_="g-recaptcha")
if not widget:
raise Exception("No reCAPTCHA widget found")
sitekey = widget.get("data-sitekey")
is_invisible = invisible is not None
callback = widget.get("data-callback")
params = {
"key": self.api_key,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": page_url
}
if is_invisible:
params["invisible"] = 1
resp = requests.get("https://ocr.captchaai.com/in.php", params=params)
if not resp.text.startswith("OK|"):
raise Exception(f"Submit failed: {resp.text}")
task_id = resp.text.split("|")[1]
for _ in range(60):
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": self.api_key, "action": "get", "id": task_id
})
if result.text.startswith("OK|"):
return {
"token": result.text.split("|")[1],
"variant": "invisible" if is_invisible else "checkbox",
"callback": callback,
"sitekey": sitekey
}
if result.text != "CAPCHA_NOT_READY":
raise Exception(f"Solve error: {result.text}")
raise Exception("Timed out")
# Usage
solver = RecaptchaV2UniversalSolver("YOUR_API_KEY")
result = solver.detect_and_solve("https://staging.example.com/qa-login")
print(f"Variant: {result['variant']}, Callback: {result['callback']}")
Pola ini awet untuk agensi price-monitoring: satu worker menangani puluhan halaman klien tanpa tahu di muka varian mana yang dipasang. Rincian versi checkbox ada di cara solve reCAPTCHA v2 lewat API.
Ketika token diterima solver tetapi ditolak halaman
| Gejala | Perbaikan di checkbox | Perbaikan di invisible |
|---|---|---|
| Token diterima solver, form menolak | Isi field g-recaptcha-response |
Isi field dan panggil fungsi data-callback |
| Widget tidak ditemukan skrip | Cari class .g-recaptcha |
Cek data-size="invisible" atau panggilan render di skrip |
| Form terkirim tetapi gagal validasi | Periksa apakah halaman mengharapkan callback | Callback hampir pasti wajib — cari namanya lalu jalankan |
| Token ditolak karena kedaluwarsa | Solve ulang; masa berlaku 120 detik | Sama — ambil token tepat sebelum submit |
Catatan kepatuhan lokal: otomatisasi dijalankan hanya pada halaman yang Anda miliki atau boleh proses, dan hindari data pribadi — UU Pelindungan Data Pribadi (UU 27/2022) mengatur datanya, bukan hanya caranya.
Pertanyaan yang sering muncul
Callback sudah dipanggil tetapi form tetap ditolak. Apa yang salah?
Biasanya urutannya. Di banyak halaman invisible, tombol submit ikut terpicu begitu callback selesai, jadi callback harus jalan sebelum skrip Anda menekan submit sendiri. Pastikan juga nama dari data-callback terdaftar sebagai fungsi di window.
Apakah hCaptcha atau FunCaptcha juga tercakup di alur ini?
Belum. hCaptcha dan FunCaptcha (Arkose Labs) tidak didukung, dan GeeTest v4 masih berstatus segera hadir. Yang tersedia: reCAPTCHA v2 dan v3, Cloudflare Turnstile dan Challenge, GeeTest v3, CAPTCHA gambar dan grid, plus CaptchaFox (beta), Friendly Captcha (beta), dan Lemin (beta).
Berapa thread yang saya butuhkan untuk solve invisible secara paralel?
Satu thread menampung satu solve yang sedang berjalan. Dengan waktu penyelesaian 10–25 detik, 5 thread pada BASIC ($15/bulan) berarti sekitar satu solve rampung tiap 2–5 detik. Naikkan paket saat antrean menumpuk, bukan berdasarkan volume bulanan — solve per thread tidak dibatasi.
Panduan terkait
- Cara solve reCAPTCHA v2 lewat API — versi checkbox
- Cara kerja reCAPTCHA invisible — cara kerjanya di balik layar
- Cara solve reCAPTCHA invisible lewat API — tutorial invisible
- Error umum reCAPTCHA invisible — error yang paling sering muncul
- Perbedaan reCAPTCHA v2 dan v3 — bila skor v3 ikut terlibat