Begitu target scraping Anda menyisipkan reCAPTCHA v2 atau Cloudflare Turnstile, collector Colly yang tadinya lancar langsung berhenti di halaman challenge — isi respons cuma berupa widget CAPTCHA, bukan data yang Anda cari. Solusinya bukan mengganti framework, tapi menyambungkan Colly ke API CaptchaAI: satu client Go berbasis net/http yang mengirim task, polling hasil, lalu menyuntikkan token ke request POST berikutnya.
CaptchaAI menagih per thread yang sedang dipakai, bukan per CAPTCHA yang diselesaikan — jadi berapa pun volume halaman yang di-crawl Colly Anda dalam sebulan, biayanya mengikuti paket thread yang Anda pilih, bukan jumlah solve. Untuk collector yang berjalan concurrent dengan banyak goroutine, ini membuat proyeksi biaya jauh lebih mudah dihitung dibanding skema per-solve.
Alur kerja: dari deteksi CAPTCHA sampai token siap dipakai
Semua contoh di bawah memakai pola empat langkah yang sama: kirim task ke endpoint in.php, simpan taskID dari respons, polling res.php sampai status bernilai 1, lalu pakai token yang dikembalikan untuk melengkapi request berikutnya. Client di bawah ini murni package standar Go — net/http, encoding/json, net/url — tidak ada dependency eksternal maupun SDK yang perlu Anda pasang.
Client CaptchaAI di Go
Struct Client menyimpan API key dan satu http.Client dengan timeout 30 detik. Method SolveRecaptchaV2 melakukan submit lewat PostForm, menunggu 15 detik sebelum polling pertama — jeda ini memberi CaptchaAI waktu memproses task sebelum dicek — lalu mencoba hingga 24 kali dengan jeda 5 detik antar-percobaan. Loop berhenti begitu Status bernilai 1, atau begitu Request bukan lagi CAPCHA_NOT_READY — artinya terjadi error nyata, bukan sekadar belum selesai.
package captchaai
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"strings"
"time"
)
type Client struct {
APIKey string
HTTPClient *http.Client
}
type apiResponse struct {
Status int `json:"status"`
Request string `json:"request"`
}
func NewClient(apiKey string) *Client {
return &Client{
APIKey: apiKey,
HTTPClient: &http.Client{Timeout: 30 * time.Second},
}
}
func (c *Client) SolveRecaptchaV2(sitekey, pageurl string) (string, error) {
// Submit task
data := url.Values{
"key": {c.APIKey},
"method": {"userrecaptcha"},
"googlekey": {sitekey},
"pageurl": {pageurl},
"json": {"1"},
}
resp, err := c.HTTPClient.PostForm("https://ocr.captchaai.com/in.php", data)
if err != nil {
return "", fmt.Errorf("submit error: %w", err)
}
defer resp.Body.Close()
var result apiResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return "", fmt.Errorf("decode error: %w", err)
}
if result.Status != 1 {
return "", fmt.Errorf("submit failed: %s", result.Request)
}
taskID := result.Request
// Poll for result
time.Sleep(15 * time.Second)
for i := 0; i < 24; i++ {
pollURL := fmt.Sprintf(
"https://ocr.captchaai.com/res.php?key=%s&action=get&id=%s&json=1",
c.APIKey, taskID,
)
resp, err := c.HTTPClient.Get(pollURL)
if err != nil {
time.Sleep(5 * time.Second)
continue
}
var pollResult apiResponse
json.NewDecoder(resp.Body).Decode(&pollResult)
resp.Body.Close()
if pollResult.Status == 1 {
return pollResult.Request, nil
}
if pollResult.Request != "CAPCHA_NOT_READY" {
return "", fmt.Errorf("solve error: %s", pollResult.Request)
}
time.Sleep(5 * time.Second)
}
return "", errors.New("solve timeout")
}
Simpan file ini sebagai package captchaai terpisah agar bisa di-import oleh collector Colly mana pun di proyek Anda.
Menyambungkan client ke collector Colly
Bagian ini menyatukan Colly dengan client di atas. colly.NewCollector dibatasi ke satu domain dengan kedalaman crawl maksimal 2, lalu callback OnHTML("[data-sitekey]") bertugas mendeteksi widget CAPTCHA di setiap halaman yang dikunjungi. Begitu sitekey ditemukan, SolveRecaptchaV2 dipanggil; token yang kembali dipakai pada c.Post lewat field g-recaptcha-response — nama field yang memang diharapkan Google, bukan nama custom. Callback OnHTML("table tr") di bawahnya menunjukkan pola ekstraksi data yang lazim dipakai berdampingan dengan penanganan CAPTCHA: solve dulu, baru scrape.
package main
import (
"fmt"
"log"
"os"
"strings"
"github.com/gocolly/colly/v2"
)
func main() {
apiKey := os.Getenv("CAPTCHAAI_API_KEY")
solver := captchaai.NewClient(apiKey)
c := colly.NewCollector(
colly.AllowedDomains("example.com"),
colly.MaxDepth(2),
)
// Detect CAPTCHA pages
c.OnHTML("[data-sitekey]", func(e *colly.HTMLElement) {
sitekey := e.Attr("data-sitekey")
pageURL := e.Request.URL.String()
log.Printf("CAPTCHA detected on %s, solving...", pageURL)
token, err := solver.SolveRecaptchaV2(sitekey, pageURL)
if err != nil {
log.Printf("Solve failed: %v", err)
return
}
log.Printf("CAPTCHA solved, token length: %d", len(token))
// Post form with token
err = c.Post(pageURL, map[string]string{
"g-recaptcha-response": token,
})
if err != nil {
log.Printf("Form submit failed: %v", err)
}
})
// Extract data
c.OnHTML("table tr", func(e *colly.HTMLElement) {
cols := []string{}
e.ForEach("td", func(_ int, td *colly.HTMLElement) {
cols = append(cols, strings.TrimSpace(td.Text))
})
if len(cols) > 0 {
fmt.Printf("Row: %s\n", strings.Join(cols, " | "))
}
})
c.OnError(func(r *colly.Response, err error) {
log.Printf("Error %s: %v", r.Request.URL, err)
})
c.Visit("https://example.com/data")
}
Rate limiting saat Colly menjalankan CAPTCHA solving
colly.LimitRule mengatur jeda antar-permintaan per domain — pada contoh ini 1 permintaan tiap 3 detik ditambah random delay hingga 2 detik, supaya pola traffic tidak terlihat seperti burst mendadak. Rate limiting ini independen dari thread CaptchaAI: Parallelism: 1 pada Colly hanya membatasi berapa banyak request Colly yang berjalan bersamaan, sementara jumlah thread CaptchaAI Anda menentukan berapa banyak CAPTCHA yang bisa diselesaikan paralel. Kalau Anda menjalankan beberapa collector sekaligus di goroutine terpisah, jumlah CAPTCHA yang solve bersamaan bisa melebihi Parallelism per collector — di titik inilah ukuran paket thread mulai relevan.
package main
import (
"time"
"github.com/gocolly/colly/v2"
)
func main() {
c := colly.NewCollector()
// Rate limit: 1 request per 3 seconds per domain
c.Limit(&colly.LimitRule{
DomainGlob: "*",
Parallelism: 1,
Delay: 3 * time.Second,
RandomDelay: 2 * time.Second,
})
// ... CAPTCHA handling as above ...
c.Visit("https://example.com")
}
Contoh skenario: agensi price-monitoring di Jakarta yang menjalankan puluhan goroutine Colly paralel dari region AWS ap-southeast-3 (Jakarta) atau ap-southeast-1 (Singapura) biasanya mulai dari paket BASIC ($15/bulan, 5 thread) untuk uji coba scraper, lalu naik ke ADVANCE ($90/bulan, 50 thread) begitu volume produksi harian stabil. Karena tagihan mengikuti thread yang dipakai, bukan jumlah CAPTCHA, menambah goroutine tidak otomatis menaikkan biaya selama thread yang aktif bersamaan masih di bawah kuota paket. Saat scraping menyentuh data pribadi, ingat UU Pelindungan Data Pribadi (UU 27/2022) — proses hanya data yang memang berwenang Anda olah.
Solve Cloudflare Turnstile langsung dari Go
Turnstile memakai method turnstile dan parameter sitekey, bukan googlekey seperti pada reCAPTCHA. Pola polling-nya sama persis dengan SolveRecaptchaV2, hanya jeda awalnya lebih pendek — 5 detik, bukan 15 — karena task Turnstile umumnya lebih cepat diproses di sisi CaptchaAI. Panggil fungsi ini dari callback OnHTML yang sama seperti client reCAPTCHA di atas; cukup ganti method sesuai widget yang terdeteksi di halaman.
func (c *Client) SolveTurnstile(sitekey, pageurl string) (string, error) {
data := url.Values{
"key": {c.APIKey},
"method": {"turnstile"},
"sitekey": {sitekey},
"pageurl": {pageurl},
"json": {"1"},
}
resp, err := c.HTTPClient.PostForm("https://ocr.captchaai.com/in.php", data)
if err != nil {
return "", fmt.Errorf("submit error: %w", err)
}
defer resp.Body.Close()
var result apiResponse
json.NewDecoder(resp.Body).Decode(&result)
if result.Status != 1 {
return "", fmt.Errorf("submit failed: %s", result.Request)
}
// Poll (same as reCAPTCHA)
time.Sleep(5 * time.Second)
for i := 0; i < 20; i++ {
pollURL := fmt.Sprintf(
"https://ocr.captchaai.com/res.php?key=%s&action=get&id=%s&json=1",
c.APIKey, result.Request,
)
resp, err := c.HTTPClient.Get(pollURL)
if err != nil {
time.Sleep(3 * time.Second)
continue
}
var pr apiResponse
json.NewDecoder(resp.Body).Decode(&pr)
resp.Body.Close()
if pr.Status == 1 {
return pr.Request, nil
}
if pr.Request != "CAPCHA_NOT_READY" {
return "", fmt.Errorf("error: %s", pr.Request)
}
time.Sleep(3 * time.Second)
}
return "", errors.New("timeout")
}
Colly atau headless browser: kapan pakai yang mana
Colly cepat karena tidak menjalankan browser sungguhan — ia mem-parsing HTML mentah lewat net/http. Itu ideal untuk halaman statis dan CAPTCHA yang bisa diselesaikan lewat API murni seperti reCAPTCHA v2 dan Turnstile. Begitu target memuat konten lewat JavaScript setelah page load, Colly sendirian tidak cukup — pasangkan dengan chromedp atau rod untuk merender halaman, baru serahkan sitekey yang ditemukan ke client CaptchaAI yang sama.
| Kebutuhan | Colly saja | Colly + chromedp/rod |
|---|---|---|
| Halaman statis, CAPTCHA server-side | Cukup | Berlebihan |
| Konten dimuat via JavaScript | Tidak cukup | Diperlukan |
| Kecepatan crawl per goroutine | Tinggi | Lebih lambat (render penuh) |
| Kompleksitas setup | Rendah | Lebih tinggi (perlu Chromium) |
Pemecahan masalah umum
| Gejala | Penyebab | Solusi |
|---|---|---|
Respons CAPCHA_NOT_READY terus sampai loop habis |
Task belum selesai diproses, jeda polling terlalu pendek | Naikkan jumlah percobaan atau jeda time.Sleep |
submit failed langsung di percobaan pertama |
API key salah atau sitekey tidak terbaca dari DOM | Cek data-sitekey lewat curl manual sebelum debug kode Go |
| Form tetap menolak walau token terisi | Callback reCAPTCHA di halaman tidak ikut terpicu | Pastikan nama field token persis sama dengan yang diharapkan target |
Colly berhenti di OnError tanpa pesan jelas |
Domain redirect di luar AllowedDomains |
Tambahkan domain tujuan ke daftar AllowedDomains |
| Rate limit Colly membuat solving terasa lambat | Parallelism terlalu rendah dibanding kuota thread yang tersedia |
Naikkan Parallelism sebanding kuota thread paket Anda |
Pertanyaan yang sering diajukan
Apakah hCaptcha dan FunCaptcha tersedia di CaptchaAI untuk scraper Colly?
Belum. hCaptcha dan FunCaptcha (Arkose Labs) tidak didukung saat ini, jadi client di atas tidak menyediakan method untuk keduanya. CaptchaAI menangani reCAPTCHA v2/v3, Cloudflare Turnstile dan Challenge, GeeTest v3, serta CAPTCHA gambar — GeeTest v4 masih berstatus segera hadir.
Berapa thread yang dibutuhkan untuk menjalankan banyak goroutine Colly sekaligus?
Satu thread menangani satu solve yang sedang berjalan; begitu solve selesai, thread itu langsung bebas dipakai goroutine berikutnya. Kalau 10 goroutine Colly berpotensi menghadapi CAPTCHA bersamaan, sekitar 10 thread — misalnya paket STANDARD ($30/bulan, 15 thread) — sudah cukup longgar.
Bisakah Colly dipadukan dengan headless browser untuk halaman yang butuh JavaScript?
Bisa. Pakai chromedp atau rod untuk merender halaman dinamis, ambil sitekey dari DOM hasil render, lalu kirim ke client CaptchaAI yang sama seperti pada contoh di atas — Colly tetap dipakai untuk bagian scraping yang statis.
Apakah API CaptchaAI kompatibel dengan Go tanpa SDK tambahan?
Ya. Endpoint in.php dan res.php adalah HTTP standar yang langsung bisa dipanggil lewat package net/http bawaan Go, seperti pada seluruh contoh di artikel ini. Tidak ada SDK resmi yang wajib dipasang.
Kenapa SolveRecaptchaV2 bisa mengembalikan error meski task berhasil dikirim?
Kalau pollResult.Request bukan CAPCHA_NOT_READY sebelum Status bernilai 1, itu berarti CaptchaAI mengembalikan pesan error sungguhan — misalnya sitekey tidak valid atau task kedaluwarsa — dan fungsi langsung menghentikan loop polling alih-alih menghabiskan sisa 24 percobaan.
Ringkasan
Colly + CaptchaAI menutup satu-satunya celah yang biasanya menghentikan scraper Go: halaman yang menyisipkan CAPTCHA di antara request Anda dan data yang dicari. Empat cuplikan kode di atas — client, integrasi collector, rate limiting, dan solver Turnstile — cukup untuk mulai berjalan di produksi hari ini.
Panduan Terkait
- Bandingkan dengan integrasi Crawlee + CaptchaAI
- Panduan lengkap solve reCAPTCHA v2 lewat API CaptchaAI
Sambungkan CaptchaAI ke scraper Colly Anda — ambil API key.