Scraper Python yang mendadak kena reCAPTCHA atau Turnstile bukan tanda proyeknya gagal — cukup tambahkan satu panggilan API solver sebelum submit form, pakai kombinasi requests + BeautifulSoup + CaptchaAI, tanpa browser penuh.
Pola ini favorit tim price-monitoring dan freelancer scraping Indonesia di Upwork atau Fastwork — jauh lebih murah dibanding Selenium tiap halaman.
Kenapa Pola Ini Lebih Hemat dari Selenium
Untuk banyak kasus scraping, kombinasi CaptchaAI + requests menang dari sisi biaya dan resource dibanding menjalankan Selenium atau Playwright penuh di setiap request:
- Tidak perlu instalasi browser driver (ChromeDriver, GeckoDriver) atau flag
--headless— cukup HTTP client biasa. - Pemakaian RAM dan CPU jauh lebih ringan, jadi satu VPS kecil bisa menjalankan ratusan task paralel selama thread mencukupi.
- reCAPTCHA v3 biasanya selesai di bawah 4 detik dan Cloudflare Turnstile di bawah 10 detik, jadi latensi tambahan per request tetap kecil.
- Cocok dideploy di region Asia Tenggara seperti AWS
ap-southeast-1(Singapura) atauap-southeast-3(Jakarta), maupun GCPasia-southeast2(Jakarta) — latensi ke API CaptchaAI tetap singkat.
Yang Perlu Disiapkan
Sebelum menulis kode, pastikan lingkungan berikut sudah siap:
| Persyaratan | Detail |
|---|---|
| Python 3.7+ | Dengan pip |
requests |
pip install requests |
beautifulsoup4 |
pip install beautifulsoup4 |
| Kunci API CaptchaAI | Dari captchaai.com |
Yang tidak Anda perlukan untuk pola ini:
- Instalasi Chrome, Chromium, atau browser lain
- Driver seperti ChromeDriver atau GeckoDriver
- Paket
seleniumatauplaywright
Bangun Kelas CaptchaSolver yang Bisa Dipakai Ulang
Daripada menulis ulang logika submit-dan-polling tiap script, bungkus sekali dalam kelas berikut:
import requests
import time
class CaptchaSolver:
def __init__(self, api_key):
self.api_key = api_key
self.base = "https://ocr.captchaai.com"
def _submit(self, params):
params["key"] = self.api_key
resp = requests.get(f"{self.base}/in.php", params=params)
if not resp.text.startswith("OK|"):
raise Exception(f"Submit error: {resp.text}")
return resp.text.split("|")[1]
def _poll(self, task_id, timeout=300):
deadline = time.time() + timeout
while time.time() < deadline:
time.sleep(5)
resp = requests.get(f"{self.base}/res.php", params={
"key": self.api_key,
"action": "get",
"id": task_id
})
if resp.text == "CAPCHA_NOT_READY":
continue
if resp.text.startswith("OK|"):
return resp.text.split("|")[1]
raise Exception(f"Solve error: {resp.text}")
raise TimeoutError("Solve timed out")
def solve_recaptcha_v2(self, site_key, page_url):
task_id = self._submit({
"method": "userrecaptcha",
"googlekey": site_key,
"pageurl": page_url
})
return self._poll(task_id)
def solve_recaptcha_v3(self, site_key, page_url, action="verify"):
task_id = self._submit({
"method": "userrecaptcha",
"googlekey": site_key,
"pageurl": page_url,
"version": "v3",
"action": action
})
return self._poll(task_id)
def solve_turnstile(self, site_key, page_url):
task_id = self._submit({
"method": "turnstile",
"sitekey": site_key,
"pageurl": page_url
})
return self._poll(task_id)
def solve_image(self, image_base64):
task_id = self._submit({
"method": "base64",
"body": image_base64
})
return self._poll(task_id)
Empat method menutup reCAPTCHA v2, v3, Turnstile, dan CAPTCHA gambar — pola kirim → simpan task ID → polling → pakai token, konsisten di seluruh API. Simpan kelas ini di modul terpisah, misalnya captcha_solver.py, supaya bisa di-import ke semua script scraping tanpa duplikasi kode.
Scraping Form ber-reCAPTCHA v2
Contoh berikut mengasumsikan form target memuat widget reCAPTCHA v2 standar (div.g-recaptcha) dan menerima submit lewat HTTP POST biasa — pola paling umum di form pencarian maupun form kontak:
from bs4 import BeautifulSoup
import requests
solver = CaptchaSolver("YOUR_API_KEY")
session = requests.Session()
session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
})
# Step 1: Load the page
url = "https://example.com/search"
page = session.get(url)
soup = BeautifulSoup(page.text, "html.parser")
# Step 2: Extract the site key
recaptcha_div = soup.find("div", class_="g-recaptcha")
site_key = recaptcha_div["data-sitekey"]
# Step 3: Solve the CAPTCHA
token = solver.solve_recaptcha_v2(site_key, url)
# Step 4: Submit the form with the token
form_data = {
"q": "search term",
"g-recaptcha-response": token
}
result = session.post(url, data=form_data)
# Step 5: Parse the results
result_soup = BeautifulSoup(result.text, "html.parser")
items = result_soup.find_all("div", class_="result-item")
for item in items:
print(item.text.strip())
Jika situs mengembalikan halaman CAPTCHA lagi setelah submit, penyebab paling umum adalah nama field token yang salah atau token yang sudah kedaluwarsa — cek bagian troubleshooting di bawah untuk daftar lengkapnya.
Scraping Banyak Halaman di Balik CAPTCHA
def scrape_all_pages(base_url, site_key, max_pages=10):
solver = CaptchaSolver("YOUR_API_KEY")
session = requests.Session()
session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
})
all_results = []
for page_num in range(1, max_pages + 1):
page_url = f"{base_url}?page={page_num}"
# Solve CAPTCHA for each page if needed
token = solver.solve_recaptcha_v2(site_key, page_url)
resp = session.get(page_url, params={
"g-recaptcha-response": token,
"page": page_num
})
soup = BeautifulSoup(resp.text, "html.parser")
items = soup.find_all("div", class_="item")
if not items:
break
all_results.extend([item.text.strip() for item in items])
print(f"Page {page_num}: {len(items)} items")
time.sleep(2) # Polite delay
return all_results
Jika CAPTCHA muncul di tiap halaman, solve_recaptcha_v2() dipanggil ulang per halaman seperti contoh di atas. Untuk ratusan halaman, kombinasikan dengan rotasi proxy untuk scraping ber-CAPTCHA agar tidak diblokir karena volume.
Tips praktis untuk scraping multi-halaman dalam skala besar:
- Gunakan
sessionyang sama di seluruh halaman supaya cookie tidak hilang antar request. - Tambahkan jeda (
time.sleep) antar halaman agar traffic tidak terlihat abnormal. - Pantau saldo dan pemakaian thread — task solve paralel yang berlebihan bisa melampaui kuota thread pada paket Anda.
Menangani CAPTCHA Gambar (OCR)
Beberapa situs lokal — misalnya form pendaftaran atau portal pembanding harga — masih memakai CAPTCHA gambar klasik alih-alih reCAPTCHA. Alurnya beda karena tidak ada site key: gambar diunduh lalu di-encode ke base64 sebelum dikirim ke endpoint OCR CaptchaAI:
import base64
def scrape_with_image_captcha(url):
solver = CaptchaSolver("YOUR_API_KEY")
session = requests.Session()
page = session.get(url)
soup = BeautifulSoup(page.text, "html.parser")
# Find the CAPTCHA image
captcha_img = soup.find("img", {"id": "captcha-image"})
captcha_url = captcha_img["src"]
# Download and encode the image
img_resp = session.get(captcha_url)
img_base64 = base64.b64encode(img_resp.content).decode()
# Solve
captcha_text = solver.solve_image(img_base64)
# Submit
form_data = {
"captcha": captcha_text,
"username": "user"
}
result = session.post(url, data=form_data)
return result.text
Retry dan Error Handling untuk Scraper Produksi
Scraper yang jalan tanpa pengawasan butuh logika percobaan ulang, bukan sekadar try-except sekali jalan. Kegagalan paling sering datang dari koneksi jaringan yang putus sesaat, task solve yang timeout, atau saldo yang habis di tengah batch:
def solve_with_retry(solver, site_key, page_url, max_retries=3):
for attempt in range(max_retries):
try:
return solver.solve_recaptcha_v2(site_key, page_url)
except Exception as e:
if attempt == max_retries - 1:
raise
print(f"Attempt {attempt + 1} failed: {e}. Retrying...")
time.sleep(2)
Untuk scraping volume besar, naikkan max_retries menjadi 5 dan gabungkan dengan exponential backoff (jeda yang meningkat eksponensial) pada bagian time.sleep supaya CaptchaAI maupun server target sama-sama tidak dibanjiri request saat terjadi gangguan sementara.
Troubleshooting: Error yang Sering Muncul
| Masalah | Penyebab | Solusi |
|---|---|---|
ERROR_WRONG_USER_KEY |
API key tidak valid | Verifikasi key dari dashboard |
ERROR_ZERO_BALANCE |
Saldo habis | Isi ulang akun |
| Submit form mengembalikan halaman CAPTCHA lagi | Token kedaluwarsa atau nama field salah | Gunakan token segera; periksa nama field form |
ConnectionError |
Masalah jaringan | Tambahkan logika retry dengan exponential backoff |
| Hasil kosong setelah submit | Situs memerlukan cookie/session | Gunakan requests.Session() untuk mempertahankan cookie |
Tip: sebelum debugging kode, cek dashboard CaptchaAI Anda terlebih dahulu — saldo dan status task sering jadi penyebab error yang sekilas terlihat seperti bug di script.
Pertanyaan yang Sering Diajukan
Apakah scraping dengan Python selalu butuh Selenium kalau ada CAPTCHA?
Tidak selalu. Kalau form bisa disubmit lewat HTTP POST biasa, requests + CaptchaAI lebih ringan. Selenium baru perlu jika situs merender JavaScript dulu.
Apakah CaptchaAI mendukung reCAPTCHA v3 dan Turnstile lewat script Python tanpa browser?
Ya, lewat method sama pada kelas CaptchaSolver di atas — tanpa browser, cukup panggil API dari script.
Bagaimana menjalankan penyelesaian CAPTCHA secara asynchronous untuk scraping ribuan halaman?
Gunakan aiohttp bersama API CaptchaAI agar banyak task solve berjalan bersamaan. Lihat integrasi aiohttp dengan CaptchaAI untuk polanya.
Berapa biaya solver CAPTCHA untuk proyek scraping skala freelance?
Paket BASIC ($15/bulan, 5 thread) biasanya cukup — solve per thread tidak dibatasi jumlahnya, tinggal naik ke STANDARD ($30/bulan, 15 thread) bila perlu.
Apakah scraping dengan CaptchaAI melanggar UU PDP?
Sepanjang Anda hanya mengambil data publik yang memang berhak Anda proses, ini tidak melanggar UU Pelindungan Data Pribadi (UU 27/2022) maupun UU ITE.
Panduan Terkait
- Menangani CAPTCHA di Selenium dengan Python
- Scraping CAPTCHA dengan Node.js