Formulir web yang dipasangi CAPTCHA tidak akan menerima kiriman dari skrip Anda sampai tantangannya diselesaikan lebih dulu. Alurnya sebenarnya pendek: deteksi jenis CAPTCHA di halaman, kirim ke CaptchaAI untuk diselesaikan, suntikkan token hasilnya ke formulir, lalu klik submit. Panduan ini merangkai alur itu dengan Selenium dan Python sehingga satu skrip bisa menangani reCAPTCHA v2, Cloudflare Turnstile, maupun CAPTCHA gambar sekaligus.
Kenapa formulir ber-CAPTCHA menghentikan skrip
CAPTCHA memang dipasang untuk menyaring lalu lintas otomatis. Baik Anda menguji formulir kontak, menjalankan regresi QA di alur pendaftaran, atau memproses form pada situs milik sendiri, langkah "submit" akan selalu gagal selama field respons CAPTCHA masih kosong. Selenium bisa mengisi setiap kolom dan menekan tombol kirim, tetapi ia tidak menyelesaikan tantangan visual atau skor risiko di baliknya.
Di sinilah solver eksternal masuk ke dalam alur. CaptchaAI menerima parameter CAPTCHA (misalnya sitekey dan URL halaman), menyelesaikannya, lalu mengembalikan token yang bisa Anda tempelkan ke formulir persis seperti yang dilakukan pengguna asli. Skrip Anda tetap memegang kendali penuh atas pengisian field dan pengiriman; solver hanya mengurus satu bagian yang tidak bisa ditiru oleh automation biasa.
Alur kerja dari muat form sampai kirim
Empat langkah berikut adalah kerangka yang konsisten untuk hampir semua formulir:
┌────────────┐ ┌──────────────┐ ┌────────────┐ ┌──────────────┐
│ Load Form │────▶│ Fill Fields │────▶│ Detect & │────▶│ Submit Form │
│ (Selenium) │ │ │ │ Solve │ │ │
│ │ │ │ │ CAPTCHA │ │ │
└────────────┘ └──────────────┘ └────────────┘ └──────────────┘
Di dalam tahap "Detect & Solve" berlaku pola empat langkah standar CaptchaAI: kirim task ke in.php, simpan task ID yang dikembalikan, lakukan polling ke res.php sampai hasil siap, lalu pakai token yang dikembalikan. Menyisipkan token sebagai langkah terakhir sebelum submit itu penting — token reCAPTCHA dan Turnstile punya masa berlaku, jadi semakin cepat formulir dikirim setelah token didapat, semakin kecil peluang ditolak.
Membangun komponen inti
Kita pisahkan tanggung jawab menjadi tiga kelas: satu untuk berbicara dengan API solver, satu untuk mengenali jenis CAPTCHA di halaman, dan satu untuk mengorkestrasi pengisian serta pengiriman formulir.
Solver CAPTCHA
Kelas ini membungkus pola kirim-lalu-polling. Ia mengirim parameter ke in.php, lalu menunggu hasil di res.php dengan jeda yang wajar hingga token siap:
import time
import requests
class FormCaptchaSolver:
BASE = "https://ocr.captchaai.com"
def __init__(self, api_key):
self.api_key = api_key
def solve(self, params, initial_wait=10):
params["key"] = self.api_key
params["json"] = 1
resp = requests.post(f"{self.BASE}/in.php", data=params).json()
if resp["status"] != 1:
raise Exception(f"Submit error: {resp['request']}")
task_id = resp["request"]
time.sleep(initial_wait)
for _ in range(60):
result = requests.get(
f"{self.BASE}/res.php",
params={"key": self.api_key, "action": "get", "id": task_id, "json": 1},
).json()
if result["request"] == "CAPCHA_NOT_READY":
time.sleep(5)
continue
if result["status"] == 1:
return result["request"]
raise Exception(f"Solve error: {result['request']}")
raise TimeoutError("CAPTCHA solve timed out")
Detektor jenis CAPTCHA
Sebelum bisa menyelesaikan, skrip harus tahu CAPTCHA apa yang sedang dihadapi. Detektor ini memeriksa halaman untuk penanda Turnstile, reCAPTCHA, lalu CAPTCHA gambar — dengan urutan yang sengaja dibuat spesifik-ke-umum agar Turnstile tidak salah dikenali sebagai reCAPTCHA (keduanya sama-sama memakai atribut data-sitekey):
import re
from selenium.webdriver.common.by import By
class CaptchaDetector:
def __init__(self, driver):
self.driver = driver
def detect(self):
"""Detect CAPTCHA type on current page."""
html = self.driver.page_source
# Turnstile
turnstile = self.driver.find_elements(By.CSS_SELECTOR, ".cf-turnstile, [data-sitekey]")
for el in turnstile:
if "cf-turnstile" in (el.get_attribute("class") or ""):
return "turnstile", el.get_attribute("data-sitekey")
# reCAPTCHA
recaptcha = self.driver.find_elements(By.CSS_SELECTOR, "[data-sitekey]")
if recaptcha:
sitekey = recaptcha[0].get_attribute("data-sitekey")
if "recaptcha" in html.lower():
return "recaptcha_v2", sitekey
# Image CAPTCHA
img = self.driver.find_elements(By.CSS_SELECTOR, "img[src*='captcha'], img.captcha")
if img:
return "image", img[0].get_attribute("src")
return "none", None
Automator formulir
Kelas terakhir menyatukan semuanya: mengisi field, memanggil solver sesuai jenis CAPTCHA yang terdeteksi, menyuntikkan token, lalu menekan tombol submit. Perhatikan bahwa setiap cabang memakai field respons yang benar — g-recaptcha-response untuk reCAPTCHA dan cf-turnstile-response untuk Turnstile:
import base64
import requests as req
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
class FormAutomator:
def __init__(self, api_key):
self.solver = FormCaptchaSolver(api_key)
self.driver = webdriver.Chrome()
self.detector = CaptchaDetector(self.driver)
def fill_field(self, selector, value):
field = WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, selector))
)
field.clear()
field.send_keys(value)
def select_option(self, selector, value):
from selenium.webdriver.support.ui import Select
select = Select(self.driver.find_element(By.CSS_SELECTOR, selector))
select.select_by_value(value)
def solve_captcha(self):
captcha_type, data = self.detector.detect()
page_url = self.driver.current_url
if captcha_type == "recaptcha_v2":
token = self.solver.solve({
"method": "userrecaptcha",
"googlekey": data,
"pageurl": page_url,
})
self.driver.execute_script(
f'document.querySelector("[name=g-recaptcha-response]").value = "{token}";'
)
return True
if captcha_type == "turnstile":
token = self.solver.solve({
"method": "turnstile",
"sitekey": data,
"pageurl": page_url,
})
self.driver.execute_script(
f'document.querySelector("[name=cf-turnstile-response]").value = "{token}";'
)
return True
if captcha_type == "image":
img_data = req.get(data).content
img_b64 = base64.b64encode(img_data).decode()
text = self.solver.solve({"method": "base64", "body": img_b64})
captcha_input = self.driver.find_element(
By.CSS_SELECTOR, "input[name*='captcha']"
)
captcha_input.clear()
captcha_input.send_keys(text)
return True
return False # No CAPTCHA detected
def submit_form(self, url, fields, submit_selector="button[type='submit']"):
"""
fields: list of (selector, value) tuples
"""
self.driver.get(url)
for selector, value in fields:
self.fill_field(selector, value)
self.solve_captcha()
submit = self.driver.find_element(By.CSS_SELECTOR, submit_selector)
submit.click()
return self.driver.current_url
def close(self):
self.driver.quit()
Contoh lengkap: formulir kontak
Dengan tiga kelas di atas, memproses satu formulir kontak jadi ringkas. Berikan daftar pasangan (selektor, nilai), tentukan selektor tombol kirim, dan biarkan automator mengurus deteksi serta penyelesaian CAPTCHA:
automator = FormAutomator("YOUR_API_KEY")
try:
result_url = automator.submit_form(
url="https://example.com/contact",
fields=[
("#name", "John Doe"),
("#email", "john@example.com"),
("#subject", "Sales inquiry"),
("#message", "I'd like to learn more about your services."),
],
submit_selector="#submit-btn",
)
print(f"Form submitted. Redirected to: {result_url}")
finally:
automator.close()
Menyesuaikan untuk jenis formulir lain
Pola yang sama berlaku untuk formulir login, pendaftaran, hingga pencarian — yang berubah hanya daftar field dan selektor tombolnya.
Formulir login
result = automator.submit_form(
url="https://staging.example.com/qa-login",
fields=[
("#username", "testuser"),
("#password", "testpass123"),
],
submit_selector="#login-btn",
)
Formulir pendaftaran
result = automator.submit_form(
url="https://example.com/register",
fields=[
("#first-name", "Jane"),
("#last-name", "Smith"),
("#email", "jane@example.com"),
("#password", "SecurePass!123"),
("#confirm-password", "SecurePass!123"),
],
submit_selector="#register-btn",
)
Formulir pencarian ber-CAPTCHA
result = automator.submit_form(
url="https://example.com/search",
fields=[
("#query", "python developer"),
("#location", "San Francisco"),
],
submit_selector="#search-btn",
)
Skenario nyata: tim QA yang menjalankan ratusan submit per hari
Bayangkan sebuah agensi price-monitoring atau tim data freelance di Jakarta yang menjalankan pengujian formulir dalam jumlah besar setiap hari — misalnya memverifikasi bahwa alur pendaftaran dan kontak di properti milik klien tetap berfungsi setelah setiap rilis. Di skala itu, model harga berbasis thread jauh lebih mudah diprediksi daripada tarif per-solve. Paket BASIC ($15/bulan, 5 thread) cukup untuk alur QA kecil, sedangkan ADVANCE ($90/bulan, 50 thread) menampung pengujian paralel yang jauh lebih padat — semuanya dengan solve tak terbatas per thread, tanpa biaya tambahan per jenis CAPTCHA.
Kalau worker Selenium Anda berjalan di region seperti AWS ap-southeast-3 (Jakarta), latensi jaringan ke endpoint solve umumnya kecil dibanding waktu penyelesaian CAPTCHA itu sendiri, jadi throughput lebih ditentukan oleh alokasi thread daripada oleh jarak. Satu catatan kepatuhan: otomatiskan hanya formulir yang Anda miliki atau yang izinnya sudah Anda kantongi, dan hindari memproses data pribadi tanpa dasar yang sah — relevan dengan UU Pelindungan Data Pribadi (UU 27/2022). Ini bukan nasihat hukum, hanya batas yang sehat untuk alur otomatisasi.
Mengatasi masalah umum
| Masalah | Penyebab | Solusi |
|---|---|---|
| Token ditolak | Token kedaluwarsa sebelum formulir terkirim | Selesaikan CAPTCHA sebagai langkah terakhir, langsung submit |
| Field tidak ditemukan | Halaman memuat kontennya secara dinamis | Tambahkan explicit wait pada elemen target |
| Jenis CAPTCHA salah terdeteksi | Ada beberapa elemen CAPTCHA di halaman | Periksa kembali urutan deteksi di detektor |
| Formulir memuat ulang setelah submit | Validasi sisi server gagal | Pastikan semua field wajib sudah terisi benar |
| Callback reCAPTCHA tidak terpicu | Fungsi callback perlu dipanggil manual | Jalankan grecaptcha.execute() setelah token disuntik |
Pertanyaan umum
Bisakah formulir dikirim tanpa membuka browser?
Untuk reCAPTCHA dan Turnstile, token bisa diselesaikan lewat HTTP POST langsung tanpa menjalankan browser. Tetapi jika formulir mengandalkan validasi JavaScript sebelum submit, Anda tetap butuh browser seperti pada contoh Selenium di atas.
Jenis CAPTCHA apa saja yang bisa ditangani skrip ini?
Contoh di sini menangani reCAPTCHA v2, Cloudflare Turnstile, dan CAPTCHA gambar/OCR. Di luar itu CaptchaAI juga menyelesaikan reCAPTCHA v3, Cloudflare Challenge, dan GeeTest v3. hCaptcha dan FunCaptcha belum didukung, jadi jangan mengandalkannya untuk formulir yang memakai keduanya.
Berapa biaya untuk menyelesaikan CAPTCHA form bervolume tinggi?
Biaya tidak dihitung per CAPTCHA. CaptchaAI menagih per thread bersamaan dengan solve tak terbatas per thread, sehingga tagihan bulanan tetap berapa pun jumlah formulir yang diproses. Mulai dari BASIC ($15/bulan, 5 thread); untuk pengujian paralel yang padat, naik ke ADVANCE ($90/bulan, 50 thread) atau lebih tinggi.
Bagaimana menangani formulir yang mengirim via AJAX?
Untuk pengiriman AJAX, jangan mengisi kolom tersembunyi. Intersepsi permintaan XHR-nya lalu sisipkan token CAPTCHA ke dalam payload permintaan sebelum dikirim.
Apakah otomatisasi formulir seperti ini sesuai aturan?
Selama formulirnya milik Anda atau pengujiannya sudah diizinkan pemilik situs, ini adalah alur QA dan testing yang wajar. Hindari memproses data pribadi orang lain tanpa dasar yang sah dan jangan menargetkan situs yang tidak Anda kelola.
Panduan terkait
Otomatiskan formulir apa pun — selesaikan CAPTCHA dengan CaptchaAI.