Lingkup aman: Panduan ini ditujukan untuk scraping data publik yang boleh Anda proses, atau lingkungan QA dan staging milik sendiri. Jangan pakai polanya untuk mengambil data pribadi maupun data di balik login milik orang lain.
Scraper yang mati di tengah jalan karena CAPTCHA hampir selalu bisa diperbaiki tanpa mengganti seluruh arsitektur: cukup deteksi jenis tantangan yang muncul, kirim parameternya ke API CaptchaAI, ambil token hasilnya, lalu lanjutkan request di sesi yang sama. Pola empat langkah itu — kirim → simpan task ID → polling → pakai token — adalah inti seluruh artikel ini.
Yang membedakan scraper rapuh dari yang berumur panjang bukan trik eksotis, melainkan tiga keputusan: jenis tantangan apa yang Anda hadapi, kapan Anda menyelesaikannya, dan seberapa konsisten sesi Anda antar-request. Ketiganya dibahas berurutan di bawah, dengan kode Python yang bisa langsung dipakai.
Kenali dulu jenis tantangan yang Anda hadapi
Langkah pertama bukan menulis kode, melainkan membaca HTML halaman yang gagal. Setiap penyedia meninggalkan penanda khas, dan penanda itulah yang menentukan parameter method yang Anda kirim ke in.php.
| Tantangan | Penanda di halaman | Method CaptchaAI |
|---|---|---|
| reCAPTCHA v2 | div.g-recaptcha pada form login atau pencarian |
method=userrecaptcha |
| reCAPTCHA v3 | skor latar di hampir semua halaman, tanpa widget | method=userrecaptcha&version=v3 |
| Cloudflare Turnstile | div.cf-turnstile di situs di balik Cloudflare |
method=turnstile |
| Cloudflare Challenge | halaman interstisial penuh dengan penanda cf-chl |
method=cloudflare_challenge |
| Gambar/OCR CAPTCHA | <img> CAPTCHA di situs lawas |
method=base64 |
| GeeTest v3 | widget geser dengan gt dan challenge |
method=geetest |
| hCaptcha | div.h-captcha |
Belum didukung CaptchaAI |
Dua catatan sebelum lanjut. hCaptcha dan FunCaptcha (Arkose Labs) belum didukung — jika target Anda memakainya, Anda perlu layanan lain untuk bagian itu. GeeTest v4 pun masih berstatus segera hadir. Menguji satu halaman sampel lebih dulu jauh lebih murah daripada menemukannya setelah scraper berjalan seminggu.
Kalau Anda belum yakin penanda mana yang muncul, penjelasan deteksi CAPTCHA saat scraping menguraikan sinyal apa saja yang dinilai penyedia sebelum tantangan ditampilkan.
Strategi 1: deteksi saat berjalan, selesaikan hanya saat perlu
Ini pendekatan default yang paling hemat: scraper berjalan normal, dan hanya berhenti untuk menyelesaikan tantangan ketika respons benar-benar mengandung penandanya. Untuk pekerjaan borongan ala freelancer scraping keuntungannya nyata — mayoritas halaman biasanya lolos tanpa tantangan, jadi Anda tidak membakar thread untuk halaman yang tidak membutuhkannya.
import requests
import time
from bs4 import BeautifulSoup
API_KEY = "YOUR_API_KEY"
class ProtectedScraper:
def __init__(self):
self.session = requests.Session()
self.session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
})
def scrape(self, url):
resp = self.session.get(url)
# Check for CAPTCHA
if self._has_captcha(resp.text):
resp = self._handle_captcha(resp.text, url)
return resp.text
def _has_captcha(self, html):
indicators = ["g-recaptcha", "cf-turnstile", "h-captcha", "captcha"]
return any(ind in html.lower() for ind in indicators)
def _handle_captcha(self, html, url):
soup = BeautifulSoup(html, "html.parser")
# reCAPTCHA v2
rc = soup.find("div", class_="g-recaptcha")
if rc:
token = self._solve_recaptcha(rc["data-sitekey"], url)
return self.session.post(url, data={"g-recaptcha-response": token})
# Cloudflare Turnstile
ts = soup.find("div", class_="cf-turnstile")
if ts:
token = self._solve_turnstile(ts["data-sitekey"], url)
return self.session.post(url, data={"cf-turnstile-response": token})
raise Exception("Unknown CAPTCHA type")
def _solve_recaptcha(self, site_key, page_url):
resp = requests.get("https://ocr.captchaai.com/in.php", params={
"key": API_KEY, "method": "userrecaptcha",
"googlekey": site_key, "pageurl": page_url
})
return self._poll(resp.text.split("|")[1])
def _solve_turnstile(self, site_key, page_url):
resp = requests.get("https://ocr.captchaai.com/in.php", params={
"key": API_KEY, "method": "turnstile",
"sitekey": site_key, "pageurl": page_url
})
return self._poll(resp.text.split("|")[1])
def _poll(self, task_id):
for _ in range(60):
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get", "id": task_id
})
if result.text == "CAPCHA_NOT_READY": continue
if result.text.startswith("OK|"): return result.text.split("|")[1]
raise Exception(result.text)
raise TimeoutError()
# Usage
scraper = ProtectedScraper()
html = scraper.scrape("https://example.com/data")
Perhatikan bahwa requests.Session() yang sama dipakai untuk request ulang. Token terikat pada pasangan sitekey dan halaman, tetapi cookie sesi atau User-Agent yang berubah di tengah jalan tetap bisa membuat token ditolak.
Strategi 2: selesaikan lebih dulu untuk halaman yang pasti bertantangan
Jika Anda sudah tahu halaman mana yang selalu memunculkan widget — misalnya form pencarian atau endpoint login QA milik sendiri — memuat halaman itu lebih dulu hanya membuang waktu. Ambil sitekey sekali, simpan, lalu kirim POST langsung bersama token.
def scrape_known_captcha_page(url, site_key):
# Solve before even loading the page
token = solve_recaptcha(site_key, url)
# Submit directly with token
resp = requests.post(url, data={
"g-recaptcha-response": token,
"query": "search term"
})
return resp.text
Pola ini memangkas satu round-trip HTTP per halaman. Selisihnya terasa jika worker Anda jalan dari AWS ap-southeast-1 (Singapura) atau GCP asia-southeast2 (Jakarta) dan target servernya berada jauh di luar kawasan.
Strategi 3: situs di balik Cloudflare
Cloudflare Challenge berbeda dari Turnstile. Yang Anda terima bukan token untuk ditempel di form, melainkan cookie validasi plus User-Agent yang harus dipakai apa adanya di request berikutnya. Ini juga satu-satunya bagian di panduan ini yang mewajibkan parameter proxy: hasilnya terikat pada jalur keluar jaringan yang sama.
def get_cloudflare_clearance(url, proxy):
resp = requests.get("https://ocr.captchaai.com/in.php", params={
"key": API_KEY,
"method": "cloudflare_challenge",
"pageurl": url,
"proxy": proxy,
"proxytype": "HTTP"
})
task_id = resp.text.split("|")[1]
for _ in range(60):
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get", "id": task_id
})
if result.text == "CAPCHA_NOT_READY": continue
if "qa_validation_cookie" in result.text:
# Parse qa_validation_cookie and user_agent from response
return result.text
raise TimeoutError()
Cloudflare Challenge umumnya tuntas di bawah 15 detik, Turnstile di bawah 10 detik. Angka itu batas atas — pakai untuk menyetel batas waktu worker, bukan sebagai rata-rata yang Anda janjikan ke klien.
Menjalankan banyak halaman tanpa merusak sesi
Setelah satu halaman berhasil, sisanya soal disiplin: satu sesi, jeda yang wajar, dan kegagalan yang tidak menjatuhkan seluruh proses.
def scrape_multiple_pages(base_url, site_key, pages):
scraper = ProtectedScraper()
results = []
for page in pages:
url = f"{base_url}?page={page}"
try:
html = scraper.scrape(url)
soup = BeautifulSoup(html, "html.parser")
items = soup.find_all("div", class_="item")
results.extend([item.text.strip() for item in items])
print(f"Page {page}: {len(items)} items")
except Exception as e:
print(f"Page {page} failed: {e}")
time.sleep(random.uniform(2, 5))
return results
Blok try/except per halaman itu bukan hiasan: satu halaman yang berubah struktur tidak boleh membatalkan ribuan halaman yang sudah berhasil. Catat kegagalannya, lanjutkan, lalu jalankan ulang daftar itu sebagai batch terpisah.
Berapa thread yang Anda butuhkan
CaptchaAI menagih per thread yang berjalan bersamaan, bukan per penyelesaian, dan setiap paket memberi penyelesaian tanpa batas selama bulan berjalan. Untuk pekerjaan scraping, ini berarti biaya Anda ditentukan oleh seberapa banyak tantangan yang Anda proses serentak, bukan seberapa banyak halaman yang Anda kunjungi.
| Paket | Harga/bulan | Threads |
|---|---|---|
| BASIC | $15 | 5 |
| STANDARD | $30 | 15 |
| ADVANCE | $90 | 50 |
| PREMIUM | $170 | 100 |
Contoh kasar: crawler harga produk dengan 10 worker paralel, di mana kira-kira satu dari sepuluh halaman memunculkan tantangan, jarang butuh lebih dari 5 thread aktif — BASIC ($15/bulan, 5 threads) sudah cukup. Naikkan tingkat saat paralelisme Anda menyentuh 50 worker. Untuk agensi price-monitoring dan pekerja lepas yang menagih per proyek, biaya bulanan tetap lebih mudah dimasukkan ke penawaran daripada tarif per penyelesaian yang melonjak bersama volume.
Satu catatan kepatuhan yang relevan di Indonesia: UU Pelindungan Data Pribadi (UU 27/2022) membuat "hanya ambil data yang boleh Anda proses, hindari data pribadi" bukan sekadar etika, melainkan pertimbangan hukum saat memilih target scraping.
Pemecahan masalah cepat
| Gejala | Penyebab yang paling mungkin | Perbaikan |
|---|---|---|
| Tantangan muncul di setiap halaman | laju request terlalu tinggi | Perbesar jeda antar-request dan kurangi paralelisme |
| Token ditolak padahal solver sukses | token kedaluwarsa | Pakai token dalam 120 detik sejak diterima |
| Cloudflare tetap memblokir setelah validasi | jalur keluar atau User-Agent berubah | Gunakan proxy dan User-Agent yang sama untuk seluruh sesi |
| Halaman berbeda muncul setelah solve | ada pengalihan atau cookie tambahan | Ikuti redirect dan simpan seluruh cookie di satu Session |
CAPCHA_NOT_READY terus-menerus |
polling terlalu cepat | Beri jeda 5 detik antar-polling dan batasi jumlah percobaan |
Pertanyaan umum
Bagaimana jika target saya memakai hCaptcha?
hCaptcha belum didukung CaptchaAI, begitu pula FunCaptcha (Arkose Labs); GeeTest v4 baru berstatus segera hadir. Periksa jenis tantangan di target Anda sebelum merancang scraper.
Berapa lama waktu penyelesaian yang wajar saya anggarkan?
Sebagai batas atas: di bawah 10 detik untuk Cloudflare Turnstile, di bawah 15 detik untuk Cloudflare Challenge, di bawah 30 detik untuk reCAPTCHA v2 Invisible, dan di bawah 60 detik untuk reCAPTCHA v2. Setel batas waktu worker Anda di atas angka tersebut, bukan tepat di angkanya.
Berapa thread yang sebaiknya saya ambil untuk crawler menengah?
Hitung dari jumlah tantangan yang berjalan serentak, bukan dari jumlah halaman. Sepuluh worker dengan rasio tantangan rendah umumnya cukup dengan BASIC ($15/bulan, 5 threads); naikkan ke STANDARD ($30/bulan, 15 threads) saat Anda menambah target.
Bisakah saya scraping halaman yang butuh login?
Bisa, selama akun dan datanya memang milik Anda atau Anda punya otorisasi. Selesaikan tantangan pada form login, pertahankan cookie sesi di satu objek Session, lalu lanjutkan ke halaman terautentikasi.
Bagaimana menangani halaman yang isinya dirender JavaScript?
Render dulu dengan Selenium, Puppeteer, atau Playwright, ambil sitekey dari DOM yang sudah jadi, lalu kirim ke API dan suntikkan token kembali ke form. Contoh lengkapnya ada di panduan penanganan CAPTCHA dengan Selenium.