CAPSOLVER
Blog
Mengintegrasikan Katana dengan CapSolver: Penyelesaian CAPTCHA Otomatis untuk Penjelajahan Web

Mengintegrasikan Katana dengan CapSolver: Penyelesaian CAPTCHA Otomatis untuk Penjelajahan Web

Logo of CapSolver

Adélia Cruz

Neural Network Developer

12-Jan-2026

Cara Menyelesaikan Captcha dengan Katana Menggunakan CapSolver

Pengambilan data web adalah teknik penting bagi peneliti keamanan, penester penetrasi, dan analis data. Namun, situs web modern semakin menggunakan CAPTCHA untuk melindungi dari akses otomatis. Panduan ini menunjukkan cara mengintegrasikan Katana, kerangka pengambilan data web canggih dari ProjectDiscovery, dengan CapSolver, layanan penyelesaian CAPTCHA terkemuka, untuk menciptakan solusi pengambilan data yang kuat yang secara otomatis menangani tantangan CAPTCHA.

Apa yang Akan Anda Pelajari

  • Mengatur Katana dalam mode browser tanpa antarmuka
  • Mengintegrasikan API CapSolver untuk penyelesaian CAPTCHA otomatis
  • Menangani reCAPTCHA v2 dan Cloudflare Turnstile
  • Contoh kode lengkap untuk setiap jenis CAPTCHA
  • Praktik terbaik untuk pengambilan data yang efisien dan bertanggung jawab

Apa itu Katana?

Katana adalah kerangka pengambilan data web generasi berikutnya yang dikembangkan oleh ProjectDiscovery. Dirancang untuk kecepatan dan fleksibilitas, membuatnya ideal untuk penelitian keamanan dan pipa otomasi.

Fitur Utama

  • Dua Mode Pengambilan Data: Pengambilan data berbasis HTTP dan otomasi browser tanpa antarmuka
  • Dukungan JavaScript: Parsing dan pengambilan konten yang dirender JavaScript
  • Konfigurasi Fleksibel: Header kustom, cookie, pengisian formulir, dan kontrol skop
  • Format Output Banyak: Teks biasa, JSON, atau JSONL

Instalasi

bash Copy
# Memerlukan Go 1.24+
CGO_ENABLED=1 go install github.com/projectdiscovery/katana/cmd/katana@latest

Penggunaan Dasar

bash Copy
katana -u https://example.com -headless

Apa itu CapSolver?

CapSolver adalah layanan penyelesaian CAPTCHA berbasis AI yang menyediakan solusi cepat dan andal untuk berbagai jenis CAPTCHA.

Jenis CAPTCHA yang Didukung

  • reCAPTCHA: Versi v2 dan Enterprise
  • Cloudflare: Turnstile dan Challenge
  • AWS WAF: Bypass perlindungan WAF
  • Dan Lainnya

Alur Kerja API

CapSolver menggunakan model API berbasis tugas:

  1. Buat Tugas: Kirim parameter CAPTCHA (jenis, siteKey, URL)
  2. Dapatkan ID Tugas: Terima identifikasi tugas unik
  3. Poll Hasil: Periksa status tugas hingga solusi siap
  4. Terima Token: Dapatkan token CAPTCHA yang telah diselesaikan

Prasyarat

Sebelum memulai, pastikan Anda memiliki:

  1. Go 1.24+ terinstal
  2. Kunci API CapSolver - Daftar di sini
  3. Browser Chrome (untuk mode tanpa antarmuka)

Atur kunci API sebagai variabel lingkungan:

bash Copy
export CAPSOLVER_API_KEY="KUNCI_API_ANDA"

Arsitektur Integrasi

Copy
┌─────────────────────────┐
│   Aplikasi Go           │
│   (browser go-rod)      │
└───────────┬─────────────┘
            │
            ▼
┌─────────────────────────┐
│   Situs Target          │
│   (dengan CAPTCHA)      │
└───────────┬─────────────┘
            │
    CAPTCHA Terdeteksi
            │
            ▼
┌─────────────────────────┐
│   Ekstrak Parameter     │
│   (siteKey, URL, jenis) │
└───────────┬─────────────┘
            │
            ▼
┌─────────────────────────┐
│   API CapSolver         │
│   createTask()          │
└───────────┬─────────────┘
            │
            ▼
┌─────────────────────────┐
│   Poll Hasil            │
│   getTaskResult()       │
└───────────┬─────────────┘
            │
            ▼
┌─────────────────────────┐
│   Masukkan Token        │
│   ke Halaman            │
└───────────┬─────────────┘
            │
            ▼
┌─────────────────────────┐
│   Lanjutkan Pengambilan   │
└─────────────────────────┘

Menyelesaikan reCAPTCHA v2 dengan CapSolver

reCAPTCHA v2 adalah jenis CAPTCHA yang paling umum, menampilkan kotak centang "Saya bukan robot" atau tantangan gambar. Berikut adalah skrip lengkap yang dapat dijalankan untuk menyelesaikan reCAPTCHA v2:

go Copy
// Penyelesaian reCAPTCHA v2 - Contoh Lengkap
// Penggunaan: go run main.go
// Memerlukan: variabel lingkungan CAPSOLVER_API_KEY

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/go-rod/rod"
	"github.com/go-rod/rod/lib/launcher"
)

// Konfigurasi
var (
	CAPSOLVER_API_KEY = os.Getenv("CAPSOLVER_API_KEY")
	CAPSOLVER_API     = "https://api.capsolver.com"
)

// Struktur respons API
type CreateTaskResponse struct {
	ErrorID          int    `json:"errorId"`
	ErrorCode        string `json:"errorCode"`
	ErrorDescription string `json:"errorDescription"`
	TaskID           string `json:"taskId"`
}

type GetTaskResultResponse struct {
	ErrorID          int    `json:"errorId"`
	ErrorCode        string `json:"errorCode"`
	ErrorDescription string `json:"errorDescription"`
	Status           string `json:"status"`
	Solution         struct {
		GRecaptchaResponse string `json:"gRecaptchaResponse"`
	} `json:"solution"`
}

type BalanceResponse struct {
	ErrorID int     `json:"errorId"`
	Balance float64 `json:"balance"`
}

// CapsolverClient menangani komunikasi API
type CapsolverClient struct {
	APIKey string
	Client *http.Client
}

// NewCapsolverClient membuat klien CapSolver baru
func NewCapsolverClient(apiKey string) *CapsolverClient {
	return &CapsolverClient{
		APIKey: apiKey,
		Client: &http.Client{Timeout: 120 * time.Second},
	}
}

// GetBalance mengambil saldo akun
func (c *CapsolverClient) GetBalance() (float64, error) {
	payload := map[string]string{"clientKey": c.APIKey}
	jsonData, _ := json.Marshal(payload)

	resp, err := c.Client.Post(CAPSOLVER_API+"/getBalance", "application/json", bytes.NewBuffer(jsonData))
	if err != nil {
		return 0, err
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	var result BalanceResponse
	json.Unmarshal(body, &result)

	if result.ErrorID != 0 {
		return 0, fmt.Errorf("pemeriksaan saldo gagal")
	}

	return result.Balance, nil
}

// SolveRecaptchaV2 menyelesaikan tantangan reCAPTCHA v2
func (c *CapsolverClient) SolveRecaptchaV2(websiteURL, siteKey string) (string, error) {
	log.Printf("Membuat tugas reCAPTCHA v2 untuk %s", websiteURL)

	// Buat tugas
	task := map[string]interface{}{
		"type":       "ReCaptchaV2TaskProxyLess",
		"websiteURL": websiteURL,
		"websiteKey": siteKey,
	}

	payload := map[string]interface{}{
		"clientKey": c.APIKey,
		"task":      task,
	}

	jsonData, _ := json.Marshal(payload)
	resp, err := c.Client.Post(CAPSOLVER_API+"/createTask", "application/json", bytes.NewBuffer(jsonData))
	if err != nil {
		return "", fmt.Errorf("gagal membuat tugas: %w", err)
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	var createResult CreateTaskResponse
	json.Unmarshal(body, &createResult)

	if createResult.ErrorID != 0 {
		return "", fmt.Errorf("kesalahan API: %s - %s", createResult.ErrorCode, createResult.ErrorDescription)
	}

	log.Printf("Tugas dibuat: %s", createResult.TaskID)

	// Poll hasil
	for i := 0; i < 120; i++ {
		result, err := c.getTaskResult(createResult.TaskID)
		if err != nil {
			return "", err
		}

		if result.Status == "ready" {
			log.Printf("CAPTCHA berhasil diselesaikan!")
			return result.Solution.GRecaptchaResponse, nil
		}

		if result.Status == "failed" {
			return "", fmt.Errorf("tugas gagal: %s", result.ErrorDescription)
		}

		if i%10 == 0 {
			log.Printf("Menunggu solusi... (%ds)", i)
		}
		time.Sleep(1 * time.Second)
	}

	return "", fmt.Errorf("waktu habis menunggu solusi")
}

func (c *CapsolverClient) getTaskResult(taskID string) (*GetTaskResultResponse, error) {
	payload := map[string]string{
		"clientKey": c.APIKey,
		"taskId":    taskID,
	}

	jsonData, _ := json.Marshal(payload)
	resp, err := c.Client.Post(CAPSOLVER_API+"/getTaskResult", "application/json", bytes.NewBuffer(jsonData))
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	var result GetTaskResultResponse
	json.Unmarshal(body, &result)

	return &result, nil
}

// extractSiteKey mengekstrak kunci site reCAPTCHA dari HTML halaman
func extractSiteKey(html string) string {
	// Cari atribut data-sitekey
	patterns := []string{
		`data-sitekey="`,
		`data-sitekey='`,
		`"sitekey":"`,
		`'sitekey':'`,
	}

	for _, pattern := range patterns {
		if idx := strings.Index(html, pattern); idx != -1 {
			start := idx + len(pattern)
			end := start
			for end < len(html) && html[end] != '"' && html[end] != '\'' {
				end++
			}
			if end > start {
				return html[start:end]
			}
		}
	}
	return ""
}

// injectRecaptchaToken menyisipkan token yang telah diselesaikan ke halaman
func injectRecaptchaToken(page *rod.Page, token string) error {
	js := fmt.Sprintf(`
		(function() {
			// Atur bidang respons
			var responseField = document.getElementById('g-recaptcha-response');
			if (responseField) {
				responseField.style.display = 'block';
				responseField.value = '%s';
			}

			// Juga atur text area tersembunyi
			var textareas = document.querySelectorAll('textarea[name="g-recaptcha-response"]');
			for (var i = 0; i < textareas.length; i++) {
				textareas[i].value = '%s';
			}

			// Panggil callback jika ada
			if (typeof ___grecaptcha_cfg !== 'undefined') {
				var clients = ___grecaptcha_cfg.clients;
				for (var key in clients) {
					var client = clients[key];
					if (client) {
						// Coba temukan dan panggil callback
						try {
							var callback = client.callback ||
								(client.Q && client.Q.callback) ||
								(client.S && client.S.callback);
							if (typeof callback === 'function') {
								callback('%s');
							}
						} catch(e) {}
					}
				}
			}

			return true;
		})();
	`, token, token, token)

	_, err := page.Eval(js)
	return err
}

func main() {
	// Periksa kunci API
	if CAPSOLVER_API_KEY == "" {
		log.Fatal("Variabel lingkungan CAPSOLVER_API_KEY diperlukan")
	}

	// URL target - halaman demo reCAPTCHA Google
	targetURL := "https://www.google.com/recaptcha/api2/demo"

	log.Println("==============================================")
	log.Println("Katana + Capsolver - Demo reCAPTCHA v2")
	log.Println("==============================================")

	// Inisialisasi klien Capsolver
	client := NewCapsolverClient(CAPSOLVER_API_KEY)

	// Periksa saldo
	balance, err := client.GetBalance()
	if err != nil {
		log.Printf("Peringatan: Tidak dapat memeriksa saldo: %v", err)
	} else {
		log.Printf("Saldo Capsolver: $%.2f", balance)
	}

	// Jalankan browser
	log.Println("Menjalankan browser...")
	path, _ := launcher.LookPath()
	u := launcher.New().Bin(path).Headless(true).MustLaunch()
	browser := rod.New().ControlURL(u).MustConnect()
	defer browser.MustClose()

	// Navigasi ke target
	log.Printf("Mengarahkan ke: %s", targetURL)
	page := browser.MustPage(targetURL)
	page.MustWaitLoad()
	time.Sleep(2 * time.Second)

	// Dapatkan HTML halaman dan ekstrak kunci site
	html := page.MustHTML()

	// Periksa reCAPTCHA
	if !strings.Contains(html, "g-recaptcha") && !strings.Contains(html, "grecaptcha") {
		log.Fatal("Tidak ditemukan reCAPTCHA di halaman")
	}

	log.Println("reCAPTCHA terdeteksi!")

	// Ekstrak kunci site
	siteKey := extractSiteKey(html)
	if siteKey == "" {
		log.Fatal("Tidak dapat mengekstrak kunci site")
	}
	log.Printf("Kunci site: %s", siteKey)

	// Selesaikan CAPTCHA
	log.Println("Menyelesaikan CAPTCHA dengan Capsolver...")
	token, err := client.SolveRecaptchaV2(targetURL, siteKey)
	if err != nil {
		log.Fatalf("Gagal menyelesaikan CAPTCHA: %v", err)
	}

	log.Printf("Token diterima: %s...", token[:50])

	// Sisipkan token
	log.Println("Menyisipkan token ke halaman...")
	err = injectRecaptchaToken(page, token)
	if err != nil {
		log.Fatalf("Gagal menyisipkan token: %v", err)
	}

	// Kirim formulir
	log.Println("Mengirim formulir...")
	submitBtn := page.MustElement("#recaptcha-demo-submit")
	submitBtn.MustClick()

	// Tunggu hasil
	time.Sleep(3 * time.Second)

	// Periksa hasil
	newHTML := page.MustHTML()
	if strings.Contains(newHTML, "Verification Success") || strings.Contains(newHTML, "success") {
		log.Println("==============================================")
		log.Println("BERHASIL! reCAPTCHA diselesaikan dan diverifikasi!")
		log.Println("==============================================")
	} else {
		log.Println("Formulir dikirim - periksa halaman untuk hasilnya")
	}

	// Dapatkan judul halaman
	title := page.MustEval(`document.title`).String()
	log.Printf("Judul halaman akhir: %s", title)
}

Setup dan Jalankan

bash Copy
# Buat proyek
mkdir katana-recaptcha-v2
cd katana-recaptcha-v2
go mod init katana-recaptcha-v2

# Instal dependensi
go get github.com/go-rod/rod@latest

# Atur kunci API
export CAPSOLVER_API_KEY="KUNCI_API_ANDA"

# Jalankan
go run main.go

Menyelesaikan Cloudflare Turnstile dengan CapSolver

Cloudflare Turnstile adalah alternatif CAPTCHA yang fokus pada privasi. Berikut adalah skrip lengkap:

go Copy
// Penyelesaian Cloudflare Turnstile - Contoh Lengkap
// Penggunaan: go run main.go
// Memerlukan: variabel lingkungan CAPSOLVER_API_KEY

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"regexp"
	"strings"
	"time"

	"github.com/go-rod/rod"
	"github.com/go-rod/rod/lib/launcher"
)

// Konfigurasi
var (
	CAPSOLVER_API_KEY = os.Getenv("CAPSOLVER_API_KEY")
	CAPSOLVER_API     = "https://api.capsolver.com"
)

// Struktur respons API
type CreateTaskResponse struct {
	ErrorID          int    `json:"errorId"`
	ErrorCode        string `json:"errorCode"`
	ErrorDescription string `json:"errorDescription"`
	TaskID           string `json:"taskId"`
}

type GetTaskResultResponse struct {
	ErrorID          int    `json:"errorId"`
	ErrorCode        string `json:"errorCode"`
	ErrorDescription string `json:"errorDescription"`
	Status           string `json:"status"`
	Solution         struct {
		Token string `json:"token"`
	} `json:"solution"`
}

type BalanceResponse struct {
	ErrorID int     `json:"errorId"`
	Balance float64 `json:"balance"`
}

// CapsolverClient menangani komunikasi API
type CapsolverClient struct {
	APIKey string
	Client *http.Client
}

// NewCapsolverClient membuat klien CapSolver baru
func NewCapsolverClient(apiKey string) *CapsolverClient {
	return &CapsolverClient{
		APIKey: apiKey,
		Client: &http.Client{Timeout: 120 * time.Second},
	}
}

// GetBalance mengambil saldo akun
func (c *CapsolverClient) GetBalance() (float64, error) {
	payload := map[string]string{"clientKey": c.APIKey}
	jsonData, _ := json.Marshal(payload)

	resp, err := c.Client.Post(CAPSOLVER_API+"/getBalance", "application/json", bytes.NewBuffer(jsonData))
	if err != nil {
		return 0, err
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	var result BalanceResponse
	json.Unmarshal(body, &result)

	return result.Balance, nil
}

// SolveTurnstile menyelesaikan tantangan Cloudflare Turnstile
func (c *CapsolverClient) SolveTurnstile(websiteURL, siteKey string) (string, error) {
	log.Printf("Membuat tugas Turnstile untuk %s", websiteURL)

	// Buat tugas
	task := map[string]interface{}{
		"type":       "AntiTurnstileTaskProxyLess",
		"websiteURL": websiteURL,
	```
"websiteKey": siteKey,
	}

	payload := map[string]interface{}{
		"clientKey": c.APIKey,
		"task":      task,
	}

	jsonData, _ := json.Marshal(payload)
	resp, err := c.Client.Post(CAPSOLVER_API+"/createTask", "application/json", bytes.NewBuffer(jsonData))
	if err != nil {
		return "", fmt.Errorf("gagal membuat tugas: %w", err)
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	var createResult CreateTaskResponse
	json.Unmarshal(body, &createResult)

	if createResult.ErrorID != 0 {
		return "", fmt.Errorf("kesalahan API: %s - %s", createResult.ErrorCode, createResult.ErrorDescription)
	}

	log.Printf("Tugas dibuat: %s", createResult.TaskID)

	// Memantau hasil
	for i := 0; i < 120; i++ {
		result, err := c.getTaskResult(createResult.TaskID)
		if err != nil {
			return "", err
		}

		if result.Status == "ready" {
			log.Printf("Turnstile berhasil diselesaikan!")
			return result.Solution.Token, nil
		}

		if result.Status == "failed" {
			return "", fmt.Errorf("tugas gagal: %s", result.ErrorDescription)
		}

		if i%10 == 0 {
			log.Printf("Menunggu solusi... (%ds)", i)
		}
		time.Sleep(1 * time.Second)
	}

	return "", fmt.Errorf("waktu habis menunggu solusi")
}

func (c *CapsolverClient) getTaskResult(taskID string) (*GetTaskResultResponse, error) {
	payload := map[string]string{
		"clientKey": c.APIKey,
		"taskId":    taskID,
	}

	jsonData, _ := json.Marshal(payload)
	resp, err := c.Client.Post(CAPSOLVER_API+"/getTaskResult", "application/json", bytes.NewBuffer(jsonData))
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	var result GetTaskResultResponse
	json.Unmarshal(body, &result)

	return &result, nil
}

// extractTurnstileSiteKey mengekstrak kunci situs Turnstile dari HTML halaman
func extractTurnstileSiteKey(html string) string {
	// Pola 1: atribut data-sitekey pada div cf-turnstile
	patterns := []string{
		`cf-turnstile[^>]*data-sitekey=['"]([^'"]+)['"]`,
		`data-sitekey=['"]([^'"]+)['"][^>]*class=['"][^'"]*cf-turnstile`,
		`turnstile\.render\s*\([^,]+,\s*\{[^}]*sitekey['":\s]+['"]([^'"]+)['"]`,
		`sitekey['":\s]+['"]([0-9a-zA-Z_-]+)['"]`,
	}

	for _, pattern := range patterns {
		re := regexp.MustCompile(pattern)
		matches := re.FindStringSubmatch(html)
		if len(matches) > 1 {
			return matches[1]
		}
	}

	return ""
}

// injectTurnstileToken menyisipkan token yang dise diselesaikan ke halaman
func injectTurnstileToken(page *rod.Page, token string) error {
	js := fmt.Sprintf(`
		(function() {
			// Atur bidang cf-turnstile-response
			var responseField = document.querySelector('[name="cf-turnstile-response"]');
			if (responseField) {
				responseField.value = '%s';
			}

			// Coba cari berdasarkan ID
			var byId = document.getElementById('cf-turnstile-response');
			if (byId) {
				byId.value = '%s';
			}

			// Buat input tersembunyi jika diperlukan
			if (!responseField && !byId) {
				var input = document.createElement('input');
				input.type = 'hidden';
				input.name = 'cf-turnstile-response';
				input.value = '%s';
				var form = document.querySelector('form');
				if (form) {
					form.appendChild(input);
				}
			}

			// Coba memicu callback
			if (window.turnstile && window.turnstileCallback) {
				window.turnstileCallback('%s');
			}

			return true;
		})();
	`, token, token, token, token)

	_, err := page.Eval(js)
	return err
}

func main() {
	// Periksa kunci API
	if CAPSOLVER_API_KEY == "" {
		log.Fatal("Variabel lingkungan CAPSOLVER_API_KEY diperlukan")
	}

	// URL target - Ganti dengan situs yang menggunakan Cloudflare Turnstile
	targetURL := "https://example.com"

	log.Println("==============================================")
	log.Println("Katana + Capsolver - Demo Turnstile")
	log.Println("==============================================")

	// Inisialisasi klien Capsolver
	client := NewCapsolverClient(CAPSOLVER_API_KEY)

	// Periksa saldo
	balance, err := client.GetBalance()
	if err != nil {
		log.Printf("Peringatan: Tidak dapat memeriksa saldo: %v", err)
	} else {
		log.Printf("Saldo Capsolver: $%.2f", balance)
	}

	// Mulai browser
	log.Println("Menginisialisasi browser...")
	path, _ := launcher.LookPath()
	u := launcher.New().Bin(path).Headless(true).MustLaunch()
	browser := rod.New().ControlURL(u).MustConnect()
	defer browser.MustClose()

	// Navigasi ke target
	log.Printf("Mengunj

"websiteKey": siteKey,
	}

	payload := map[string]interface{}{
		"clientKey": c.APIKey,
		"task":      task,
	}

	jsonData, _ := json.Marshal(payload)
	resp, err := c.Client.Post(CAPSOLVER_API+"/createTask", "application/json", bytes.NewBuffer(jsonData))
	if err != nil {
		return "", fmt.Errorf("gagal membuat tugas: %w", err)
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	var createResult CreateTaskResponse
	json.Unmarshal(body, &createResult)

	if createResult.ErrorID != 0 {
		return "", fmt.Errorf("kesalahan API: %s - %s", createResult.ErrorCode, createResult.ErrorDescription)
	}

	log.Printf("Tugas dibuat: %s", createResult.TaskID)

	// Memantau hasil
	for i := 0; i < 120; i++ {
		result, err := c.getTaskResult(createResult.TaskID)
		if err != nil {
			return "", err
		}

		if result.Status == "ready" {
			log.Printf("Turnstile berhasil diselesaikan!")
			return result.Solution.Token, nil
		}

		if result.Status == "failed" {
			return "", fmt.Errorf("tugas gagal: %s", result.ErrorDescription)
		}

		if i%10 == 0 {
			log.Printf("Menunggu solusi... (%ds)", i)
		}
		time.Sleep(1 * time.Second)
	}

	return "", fmt.Errorf("waktu habis menunggu solusi")
}

func (c *CapsolverClient) getTaskResult(taskID string) (*GetTaskResultResponse, error) {
	payload := map[string]string{
		"clientKey": c.APIKey,
		"taskId":    taskID,
	}

	jsonData, _ := json.Marshal(payload)
	resp, err := c.Client.Post(CAPSOLVER_API+"/getTaskResult", "application/json", bytes.NewBuffer(jsonData))
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	var result GetTaskResultResponse
	json.Unmarshal(body, &result)

	return &result, nil
}

// extractTurnstileSiteKey mengekstrak kunci situs Turnstile dari HTML halaman
func extractTurnstileSiteKey(html string) string {
	// Pola 1: atribut data-sitekey pada div cf-turnstile
	patterns := []string{
		`cf-turnstile[^>]*data-sitekey=['"]([^'"]+)['"]`,
		`data-sitekey=['"]([^'"]+)['"][^>]*class=['"][^'"]*cf-turnstile`,
		`turnstile\.render\s*\([^,]+,\s*\{[^}]*sitekey['":\s]+['"]([^'"]+)['"]`,
		`sitekey['":\s]+['"]([0-9a-zA-Z_-]+)['"]`,
	}

	for _, pattern := range patterns {
		re := regexp.MustCompile(pattern)
		matches := re.FindStringSubmatch(html)
		if len(matches) > 1 {
			return matches[1]
		}
	}

	return ""
}

// injectTurnstileToken menyisipkan token yang telah diselesaikan ke halaman
func injectTurnstileToken(page *rod.Page, token string) error {
	js := fmt.Sprintf(`
		(function() {
			// Atur bidang cf-turnstile-response
			var responseField = document.querySelector('[name="cf-turnstile-response"]');
			if (responseField) {
				responseField.value = '%s';
			}

			// Coba cari berdasarkan ID
			var byId = document.getElementById('cf-turnstile-response');
			if (byId) {
				byId.value = '%s';
			}

			// Buat input tersembunyi jika diperlukan
			if (!responseField && !byId) {
				var input = document.createElement('input');
				input.type = 'hidden';
				input.name = 'cf-turnstile-response';
				input.value = '%s';
				var form = document.querySelector('form');
				if (form) {
					form.appendChild(input);
				}
			}

			// Coba memicu callback
			if (window.turnstile && window.turnstileCallback) {
				window.turnstileCallback('%s');
			}

			return true;
		})();
	`, token, token, token, token)

	_, err := page.Eval(js)
	return err
}

func main() {
	// Periksa kunci API
	if CAPSOLVER_API_KEY == "" {
		log.Fatal("Variabel lingkungan CAPSOLVER_API_KEY diperlukan")
	}

	// URL target - Ganti dengan situs yang menggunakan Cloudflare Turnstile
	targetURL := "https://example.com"

	log.Println("==============================================")
	log.Println("Katana + Capsolver - Demo Turnstile")
	log.Println("==============================================")

	// Inisialisasi klien Capsolver
	client := NewCapsolverClient(CAPSOLVER_API_KEY)

	// Periksa saldo
	balance, err := client.GetBalance()
	if err != nil {
		log.Printf("Peringatan: Tidak dapat memeriksa saldo: %v", err)
	} else {
		log.Printf("Saldo Capsolver: $%.2f", balance)
	}

	// Mulai browser
	log.Println("Menginisialisasi browser...")
	path, _ := launcher.LookPath()
	u := launcher.New().Bin(path).Headless(true).MustLaunch()
	browser := rod.New().ControlURL(u).MustConnect()
	defer browser.MustClose()

	// Navigasi ke target
	log.Printf("Mengarahkan ke: %s", targetURL)
	page := browser.MustPage(targetURL)
	page.MustWaitLoad()
	time.Sleep(2 * time.Second)

	// Dapatkan HTML halaman
	html := page.MustHTML()

	// Periksa apakah ada Turnstile
	if !strings.Contains(html, "cf-turnstile") && !strings.Contains(html, "turnstile") {
		log.Println("Tidak ditemukan Turnstile di halaman")
		log.Println("Pet

"websiteKey": siteKey,
	}

	payload := map[string]interface{}{
		"clientKey": c.APIKey,
		"task":      task,
	}

	jsonData, _ := json.Marshal(payload)
	resp, err := c.Client.Post(CAPSOLVER_API+"/createTask", "application/json", bytes.NewBuffer(jsonData))
	if err != nil {
		return "", fmt.Errorf("gagal membuat tugas: %w", err)
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	var createResult CreateTaskResponse
	json.Unmarshal(body, &createResult)

	if createResult.ErrorID != 0 {
		return "", fmt.Errorf("kesalahan API: %s - %s", createResult.ErrorCode, createResult.ErrorDescription)
	}

	log.Printf("Tugas dibuat: %s", createResult.TaskID)

	// Memantau hasil
	for i := 0; i < 120; i++ {
		result, err := c.getTaskResult(createResult.TaskID)
		if err != nil {
			return "", err
		}

		if result.Status == "ready" {
			log.Printf("Turnstile berhasil diselesaikan!")
			return result.Solution.Token, nil
		}

		if result.Status == "failed" {
			return "", fmt.Errorf("tugas gagal: %s", result.ErrorDescription)
		}

		if i%10 == 0 {
			log.Printf("Menunggu solusi... (%ds)", i)
		}
		time.Sleep(1 * time.Second)
	}

	return "", fmt.Errorf("waktu habis menunggu solusi")
}

func (c *CapsolverClient) getTaskResult(taskID string) (*GetTaskResultResponse, error) {
	payload := map[string]string{
		"clientKey": c.APIKey,
		"taskId":    taskID,
	}

	jsonData, _ := json.Marshal(payload)
	resp, err := c.Client.Post(CAPSOLVER_API+"/getTaskResult", "application/json", bytes.NewBuffer(jsonData))
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	var result GetTaskResultResponse
	json.Unmarshal(body, &result)

	return &result, nil
}

// extractTurnstileSiteKey mengekstrak kunci situs Turnstile dari HTML halaman
func extractTurnstileSiteKey(html string) string {
	// Pola 1: atribut data-sitekey pada div cf-turnstile
	patterns := []string{
		`cf-turnstile[^>]*data-sitekey=['"]([^'"]+)['"]`,
		`data-sitekey=['"]([^'"]+)['"][^>]*class=['"][^'"]*cf-turnstile`,
		`turnstile\.render\s*\([^,]+,\s*\{[^}]*sitekey['":\s]+['"]([^'"]+)['"]`,
		`sitekey['":\s]+['"]([0-9a-zA-Z_-]+)['"]`,
	}

	for _, pattern := range patterns {
		re := regexp.MustCompile(pattern)
		matches := re.FindStringSubmatch(html)
		if len(matches) > 1 {
			return matches[1]
		}
	}

	return ""
}

// injectTurnstileToken menyisipkan token yang telah diselesaikan ke halaman
func injectTurnstileToken(page *rod.Page, token string) error {
	js := fmt.Sprintf(`
		(function() {
			// Atur bidang cf-turnstile-response
			var responseField = document.querySelector('[name="cf-turnstile-response"]');
			if (responseField) {
				responseField.value = '%s';
			}

			// Coba cari berdasarkan ID
			var byId = document.getElementById('cf-turnstile-response');
			if (byId) {
				byId.value = '%s';
			}

			// Buat input tersembunyi jika diperlukan
			if (!responseField && !byId) {
				var input = document.createElement('input');
				input.type = 'hidden';
				input.name = 'cf-turnstile-response';
				input.value = '%s';
				var form = document.querySelector('form');
				if (form) {
					form.appendChild(input);
				}
			}

			// Coba memicu callback
			if (window.turnstile && window.turnstileCallback) {
				window.turnstileCallback('%s');
			}

			return true;
		})();
	`, token, token, token, token)

	_, err := page.Eval(js)
	return err
}

func main() {
	// Periksa kunci API
	if CAPSOLVER_API_KEY == "" {
		log.Fatal("Variabel lingkungan CAPSOLVER_API_KEY diperlukan")
	}

	// URL target - Ganti dengan situs yang menggunakan Cloudflare Turnstile
	targetURL := "https://example.com"

	log.Println("==============================================")
	log.Println("Katana + Capsolver - Demo Turnstile")
	log.Println("==============================================")

	// Inisialisasi klien Capsolver
	client := NewCapsolverClient(CAPSOLVER_API_KEY)

	// Periksa saldo
	balance, err := client.GetBalance()
	if err != nil {
		log.Printf("Peringatan: Tidak dapat memeriksa saldo: %v", err)
	} else {
		log.Printf("Saldo Capsolver: $%.2f", balance)
	}

	// Mulai browser
	log.Println("Menginisialisasi browser...")
	path, _ := launcher.LookPath()
	u := launcher.New().Bin(path).Headless(true).MustLaunch()
	browser := rod.New().ControlURL(u).MustConnect()
	defer browser.MustClose()

	// Navigasi ke target
	log.Printf("Mengarahkan ke: %s", targetURL)
	page := browser.MustPage(targetURL)
	page.MustWaitLoad()
	time.Sleep(2 * time.Second)

	// Dapatkan HTML halaman
	html := page.MustHTML()

	// Periksa apakah ada Turnstile
	if !strings.Contains(html, "cf-turnstile") && !strings.Contains(html, "turnstile") {
		log.Println("Tidak ditemukan Turnstile di halaman")
		log.Println("Petunjuk: Ganti targetURL dengan situs yang menggunakan Cloudflare Turnstile")
		return
	}

	log.Println("Cloudflare Turnstile ditemukan!")

	// Ekstrak kunci situs
	siteKey := extractTurnstileSiteKey(html)
	if siteKey == "" {
		log.Fatal("Tidak dapat mengekstrak kunci situs")
	}
	log.Printf("Kunci situs: %s", siteKey)

	// Selesaikan Turnstile
	log.Println("Menyelesaikan Turnstile dengan Capsolver...")
	token, err := client.SolveTurnstile(targetURL, siteKey)
	if err != nil {
		log.Fatalf("Gagal menyelesaikan Turnstile: %v", err)
	}

	log.Printf("Token diterima: %s...", token[:min(50, len(token))])

	// Sisipkan token
	log.Println("Menyisipkan token ke halaman...")
	err = injectTurnstileToken(page, token)
	if err != nil {
		log.Fatalf("Gagal menyisipkan token: %v", err)
	}

	log.Println("==============================================")
	log.Println("BERHASIL! Token Turnstile disisipkan!")
	log.Println("==============================================")

	// Dapatkan judul halaman
	title := page.MustEval(`document.title`).String()
	log.Printf("Judul halaman: %s", title)
}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

Poin-Poin Turnstile

  1. Jenis Tugas: Gunakan AntiTurnstileTaskProxyLess
  2. Bidang Respons: Turnstile menggunakan cf-turnstile-response alih-alih g-recaptcha-response
  3. Pemecahan Lebih Cepat: Turnstile biasanya lebih cepat dibandingkan reCAPTCHA (1-10 detik)
  4. Bidang Token: Solusi ada di solution.token alih-alih solution.gRecaptchaResponse

Pengumpul CAPTCHA Universal

Berikut adalah crawler lengkap dan modular yang menangani semua jenis CAPTCHA secara otomatis:

go Copy
// Pengumpul CAPTCHA Universal - Contoh Lengkap
// Menangani secara otomatis reCAPTCHA v2 dan Turnstile
// Penggunaan: go run main.go -url "https://example.com"
// Membutuhkan: variabel lingkungan CAPSOLVER_API_KEY

package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"regexp"
	"strings"
	"time"

	"github.com/go-rod/rod"
	"github.com/go-rod/rod/lib/launcher"
)

// ============================================
// Konfigurasi
// ============================================

var (
	CAPSOLVER_API_KEY = os.Getenv("CAPSOLVER_API_KEY")
	CAPSOLVER_API     = "https://api.capsolver.com"
)

// JenisCAPTCHA merepresentasikan berbagai jenis CAPTCHA
type JenisCAPTCHA string

const (
	ReCAPTCHAv2 JenisCAPTCHA = "recaptcha_v2"
	Turnstile   JenisCAPTCHA = "turnstile"
	Unknown     JenisCAPTCHA = "unknown"
)

// InformasiCAPTCHA berisi parameter CAPTCHA yang diekstrak
type InformasiCAPTCHA struct {
	Jenis    JenisCAPTCHA
	SiteKey  string
}

// ============================================
// Tipe API
// ============================================

type CreateTaskResponse struct {
	ErrorID          int    `json:"errorId"`
	ErrorCode        string `json:"errorCode"`
	ErrorDescription string `json:"errorDescription"`
	TaskID           string `json:"taskId"`
}

type GetTaskResultResponse struct {
	ErrorID          int    `json:"errorId"`
	ErrorCode        string `json:"errorCode"`
	ErrorDescription string `json:"errorDescription"`
	Status           string `json:"status"`
	Solution         struct {
		GRecaptchaResponse string `json:"gRecaptchaResponse"`
		Token              string `json:"token"`
	} `json:"solution"`
}

type BalanceResponse struct {
	ErrorID int     `json:"errorId"`
	Balance float64 `json:"balance"`
}

// ============================================
// Klien Capsolver
// ============================================

type CapsolverClient struct {
	APIKey string
	Client *http.Client
}

func NewCapsolverClient(apiKey string) *CapsolverClient {
	return &CapsolverClient{
		APIKey: apiKey,
		Client: &http.Client{Timeout: 120 * time.Second},
	}
}

func (c *CapsolverClient) GetBalance() (float64, error) {
	payload := map[string]string{"clientKey": c.APIKey}
	jsonData, _ := json.Marshal(payload)

	resp, err := c.Client.Post(CAPSOLVER_API+"/getBalance", "application/json", bytes.NewBuffer(jsonData))
	if err != nil {
		return 0, err
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	var result BalanceResponse
	json.Unmarshal(body, &result)

	return result.Balance, nil
}

func (c *CapsolverClient) Solve(info *InformasiCAPTCHA, websiteURL string) (string, error) {
	switch info.Jenis {
	case ReCAPTCHAv2:
		return c.solveReCAPTCHAv2(websiteURL, info.SiteKey)
	case Turnstile:
		return c.solveTurnstile(websiteURL, info.SiteKey)
	default:
		return "", fmt.Errorf("jenis CAPTCHA tidak didukung: %s", info.Jenis)
	}
}

func (c *CapsolverClient) solveReCAPTCHAv2(websiteURL, siteKey string) (string, error) {
	task := map[string]interface{}{
		"type":       "ReCaptchaV2TaskProxyLess",
		"websiteURL": websiteURL,
		"websiteKey": siteKey,
	}
	return c.solveTask(task, "recaptcha")
}

func (c *CapsolverClient) solveTurnstile(websiteURL, siteKey string) (string, error) {
	task := map[string]interface{}{
		"type":       "AntiTurnstileTaskProxyLess",
		"websiteURL": websiteURL,
		"websiteKey": siteKey,
	}
	return c.solveTask(task, "turnstile")
}

func (c *CapsolverClient) solveTask(task map[string]interface{}, tokenType string) (string, error) {
	// Membuat tugas
	payload := map[string]interface{}{
		"clientKey": c.APIKey,
		"task":      task,
	}

	jsonData, _ := json.Marshal(payload)
	resp, err := c.Client.Post(CAPSOLVER_API+"/createTask", "application/json", bytes.NewBuffer(jsonData))
	if err != nil {
		return "", fmt.Errorf("gagal membuat tugas: %w", err)
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	var createResult CreateTaskResponse
	json.Unmarshal(body, &createResult)

	if createResult.ErrorID != 0 {
		return "", fmt.Errorf("kesalahan API: %s - %s", createResult.ErrorCode, createResult.ErrorDescription)
	}

	log.Printf("Tugas dibuat: %s", createResult.TaskID)

	// Memantau hasil
	for i := 0; i < 120; i++ {
		getPayload := map[string]string{
			"clientKey": c.APIKey,
			"taskId":    createResult.TaskID,
		}

		jsonData, _ := json.Marshal(getPayload)
		resp, err := c.Client.Post(CAPSOLVER_API+"/getTaskResult", "application/json", bytes.NewBuffer(jsonData))
		if err != nil {
			return "", err
		}

		body, _ := io.ReadAll(resp.Body)
		resp.Body.Close()

		var result GetTaskResultResponse
		json.Unmarshal(body, &result)

		if result.Status == "ready" {
			if tokenType == "turnstile" {
				return result.Solution.Token, nil
			}
			return result.Solution.GRecaptchaResponse, nil
		}

		if result.Status == "failed" {
			return "", fmt.Errorf("tugas gagal: %s", result.ErrorDescription)
		}

		if i%10 == 0 {
			log.Printf("Menunggu solusi... (%ds)", i)
		}
		time.Sleep(1 * time.Second)
	}

	return "", fmt.Errorf("waktu habis menunggu solusi")
}

// ============================================
// Deteksi CAPTCHA
// ============================================

func DeteksiCAPTCHA(html string) *InformasiCAPTCHA {
	// Periksa re reCAPTCHA v2 (checkbox)
	if strings.Contains(html, "g-recaptcha") {
		siteKey := ekstrakDataSiteKey(html, "g-recaptcha")
		if siteKey != "" {
			return &InformasiCAPTCHA{
				Jenis:    ReCAPTCHAv2,
				SiteKey:  siteKey,
			}
		}
	}

	// Periksa untuk Cloudflare Turnstile
	if strings.Contains(html, "cf-turnstile") || strings.Contains(html, "challenges.cloudflare.com/turnstile") {
		siteKey := ekstrakDataSiteKey(html, "cf-turnstile")
		if siteKey != "" {
			return &InformasiCAPTCHA{
				Jenis:    Turnstile,
				SiteKey:  siteKey,
			}
		}
	}

	return nil
}

func ekstrakDataSiteKey(html, className string) string {
	pattern := fmt.Sprintf(`class=['"][^'"]*%s[^'"]*['"][^>]*data-sitekey=['"]([^'"]+)['"]`, className)
	re := regexp.MustCompile(pattern)
	matches := re.FindStringSubmatch(html)
	if len(matches) > 1 {
		return matches[1]
	}

	// Pola alternatif
	pattern = fmt.Sprintf(`data-sitekey=['"]([^'"]+)['"][^>]*class=['"][^'"]*%s`, className)
	re = regexp.MustCompile(pattern)
	matches = re.FindStringSubmatch(html)
	if len(matches) > 1 {
		return matches[1]
	}

	// Pola umum untuk sitekey
	re = regexp.MustCompile(`data-sitekey=['"]([^'"]+)['"]`)
	matches = re.FindStringSubmatch(html)
	if len(matches) > 1 {
		return matches[1]
	}

	return ""
}

// ============================================
// Penyisipan Token
// ============================================

func SisipkanToken(page *rod.Page, token string, captchaType JenisCAPTCHA) error {
	var js string

	switch captchaType {
	case ReCAPTCHAv2:
		js = fmt.Sprintf(`
			(function() {
				var responseField = document.getElementById('g-recaptcha-response');
				if (responseField) {
					responseField.style.display = 'block';
					responseField.value = '%s';
				}

				var textareas = document.querySelectorAll('textarea[name="g-recaptcha-response"]');
				for (var i = 0; i < textareas.length; i++) {
					textareas[i].value = '%s';
				}

				if (typeof ___grecaptcha_cfg !== 'undefined') {
					var clients = ___grecaptcha_cfg.clients;
					for (var key in clients) {
						var client = clients[key];
						if (client) {
							try {
								var callback = client.callback ||
									(client.Q && client.Q.callback) ||
									(client.S && client.S.callback);
								if (typeof callback === 'function') {
									callback('%s');
								}
							} catch(e) {}
						}
					}
				}
				return true;
			})();
		`, token, token, token)

	case Turnstile:
		js = fmt.Sprintf(`
			(function() {
				var responseField = document.querySelector('[name="cf-turnstile-response"]');
				if (responseField) {
					responseField.value = '%s';
				}

				if (!responseField) {
					var input = document.createElement('input');
					input.type = 'hidden';
					input.name = 'cf-turnstile-response';
					input.value = '%s';
					var form = document.querySelector('form');
					if (form) form.appendChild(input);
				}

				if (window.turnstile && window.turnstileCallback) {
					window.turnstileCallback('%s');
				}
				return true;
			})();
		`, token, token, token)

	default:
		return fmt.Errorf("jenis CAPTCHA tidak didukung: %s", captchaType)
	}

	_, err := page.Eval(js)
	return err
}

// ============================================
// Crawler
// ============================================

type HasilCrawl struct {
	URL           string
	Judul         string
	Keberhasilan  bool
	CAPTCHAFound  bool
	CAPTCHAType   JenisCAPTCHA
	CAPTCHASolved bool
	Kesalahan     string
}

func Crawl(browser *rod.Browser, client *CapsolverClient, targetURL string) *HasilCrawl {
	result := &HasilCrawl{
		URL:     targetURL,

Berhasil: false,
}

Copy
// Navigasi ke target
log.Printf("Navigasi ke: %s", targetURL)
page := browser.MustPage(targetURL)
defer page.MustClose()

page.MustWaitLoad()
time.Sleep(2 * time detik)

// Dapatkan HTML halaman
html := page.MustHTML()

// Deteksi CAPTCHA
captchaInfo := DetectCaptcha(html)

if captchaInfo != nil && captchaInfo.Type != Unknown {
	result.CaptchaFound = true
	result.CaptchaType = captchaInfo.Type

	log.Printf("CAPTCHA terdeteksi: %s (siteKey: %s)", captchaInfo.Type, captchaInfo.SiteKey)

	// Selesaikan CAPTCHA
	log.Println("Menyelesaikan CAPTCHA dengan Capsolver...")
	token, err := client.Solve(captchaInfo, targetURL)
	if err != nil {
		result.Error = fmt.Sprintf("gagal menyelesaikan CAPTCHA: %v", err)
		log.Printf("Kesalahan: %s", result.Error)
		return result
	}

	log.Printf("Token diterima: %s...", token[:min(50, len(token))])

	// Sisipkan token
	log.Println("Menyisipkan token...")
	err = InjectToken(page, token, captchaInfo.Type)
	if err != nil {
		result.Error = fmt.Sprintf("gagal menyisipkan token: %v", err)
		log.Printf("Kesalahan: %s", result.Error)
		return result
	}

	result.CaptchaSolved = true
	log.Println("Token berhasil disisipkan!")

	// Coba kirim formulir
	submitForm(page)
	time.Sleep(3 * time detik)
} else {
	log.Println("Tidak ada CAPTCHA yang terdeteksi di halaman")
}

// Dapatkan informasi halaman akhir
result.Title = page.MustEval(`document.title`).String()
result.Success = true

return result

}

func submitForm(page *rod.Page) {
selectors := []string{
"button[type='submit']",
"input[type='submit']",
"#recaptcha-demo-submit",
".submit-button",
}

Copy
for _, selector := range selectors {
	js := fmt.Sprintf(`
		(function() {
			var btn = document.querySelector('%s');
			if (btn && btn.offsetParent !== null) {
				btn.click();
				return true;
			}
			return false;
		})();
	`, selector)

	result := page.MustEval(js)
	if result.Bool() {
		log.Printf("Klik tombol submit: %s", selector)
		return
	}
}

}

func min(a, b int) int {
if a < b {
return a
}
return b
}

// ============================================
// Utama
// ============================================

func main() {
// Parsing flag
targetURL := flag.String("url", "https://www.google.com/recaptcha/api2/demo", "URL target untuk dijelajahi")
headless := flag.Bool("headless", true, "Jalankan browser dalam mode headless")
checkBalance := flag.Bool("balance", false, "Hanya periksa saldo akun")
flag.Parse()

Copy
// Periksa kunci API
if CAPSOLVER_API_KEY == "" {
	log.Fatal("Variabel lingkungan CAPSOLVER_API_KEY diperlukan")
}

log.Println("==============================================")
log.Println("Katana + Capsolver - Pengejelajah CAPTCHA Universal")
log.Println("==============================================")

// Inisialisasi klien
client := NewCapsolverClient(CAPSOLVER_API_KEY)

// Periksa saldo
balance, err := client.GetBalance()
if err != nil {
	log.Printf("Peringatan: Tidak dapat memeriksa saldo: %v", err)
} else {
	log.Printf("Saldo Capsolver: $%.2f", balance)
}

if *checkBalance {
	return
}

// Jalankan browser
log.Println("Menginisialisasi browser...")
path, _ := launcher.LookPath()
u := launcher.New().Bin(path).Headless(*headless).MustLaunch()
browser := rod.New().ControlURL(u).MustConnect()
defer browser.MustClose()

// Jelajahi
result := Crawl(browser, client, *targetURL)

// Output hasil
log.Println("==============================================")
log.Println("HASIL PENJELAJAHAN")
log.Println("==============================================")
log.Printf("URL: %s", result.URL)
log.Printf("Judul: %s", result.Title)
log.Printf("Berhasil: %v", result.Success)
log.Printf("CAPTCHA Ditemukan: %v", result.CaptchaFound)
if result.CaptchaFound {
	log.Printf("Jenis CAPTCHA: %s", result.CaptchaType)
	log.Printf("CAPTCHA Diselesaikan: %v", result.CaptchaSolved)
}
if result.Error != "" {
	log.Printf("Kesalahan: %s", result.Error)
}
log.Println("==============================================")

}

Copy
### Penggunaan

```bash
# Buat proyek
mkdir katana-universal-crawler
cd katana-universal-crawler
go mod init katana-universal-crawler

# Instal dependensi
go get github.com/go-rod/rod@latest

# Atur kunci API
export CAPSOLVER_API_KEY="KUNCI_API_ANDA"

# Jalankan dengan default (demo reCAPTCHA v2)
go run main.go

# Jalankan dengan URL kustom
go run main.go -url "https://example.com"

# Periksa saldo saja
go run main.go -balance

# Jalankan dengan browser yang terlihat
go run main.go -headless=false

Praktik Terbaik

1. Optimisasi Kinerja

  • Gunakan Tipe Tugas ProxyLess: ReCaptchaV2TaskProxyLess menggunakan proxy internal Capsolver untuk penyelesaian yang lebih cepat
  • Pemrosesan Paralel: Mulai penyelesaian CAPTCHA saat elemen halaman lain dimuat
  • Penyimpanan Token: Token reCAPTCHA berlaku selama ~2 menit; simpan jika memungkinkan

2. Pengelolaan Biaya

  • Deteksi Sebelum Menyelesaikan: Hanya panggil Capsolver ketika CAPTCHA benar-benar hadir
  • Validasi Site Keys: Pastikan kunci yang diekstrak valid sebelum panggilan API
  • Pantau Penggunaan: Lacak panggilan API untuk mengelola biaya secara efektif

3. Penanganan Kesalahan

go Copy
func SolveWithRetry(client *CapsolverClient, info *CaptchaInfo, url string, maxRetries int) (string, error) {
    var lastErr error

    for i := 0; i < maxRetries; i++ {
        token, err := client.Solve(info, url)
        if err == nil {
            return token, nil
        }

        lastErr = err
        log.Printf("Percobaan %d gagal: %v", i+1, err)

        // Backoff eksponensial
        time.Sleep(time.Duration(i+1) * time detik)
    }

    return "", fmt.Errorf("gagal setelah %d percobaan: %w", maxRetries, lastErr)
}

4. Pembatasan Kecepatan

Implementasikan penundaan yang sesuai antara permintaan untuk menghindari deteksi:

go Copy
type RateLimiter struct {
    requests    int
    interval    time.Duration
    lastRequest time.Time
    mu          sync.Mutex
}

func (r *RateLimiter) Wait() {
    r.mu.Lock()
    defer r.mu.Unlock()

    elapsed := time.Since(r.lastRequest)
    if elapsed < r.interval {
        time.Sleep(r.interval - elapsed)
    }
    r.lastRequest = time.Now()
}

Pemecahan Masalah

Kesalahan Umum

Kesalahan Penyebab Solusi
ERROR_ZERO_BALANCE Kredit tidak cukup Isi ulang akun Capsolver
ERROR_CAPTCHA_UNSOLVABLE Kunci situs tidak valid Periksa logika ekstraksi
ERROR_INVALID_TASK_DATA Parameter hilang Periksa struktur tugas
context deadline exceeded Waktu habis Tingkatkan waktu tunggu atau periksa jaringan

Tips Pemecahan Masalah

  1. Aktifkan Browser yang Terlihat: Setel Headless(false) untuk melihat apa yang terjadi
  2. Lacak Lalu Lintas Jaringan: Pantau permintaan untuk mengidentifikasi masalah
  3. Simpan Tangkapan Layar: Tangkap status halaman untuk debugging
  4. Validasi Token: Catat format token sebelum penyisipan

FAQ

T: Bisakah saya menggunakan Katana tanpa mode headless untuk halaman CAPTCHA?
J: Tidak, halaman CAPTCHA memerlukan rendering JavaScript yang hanya bekerja dalam mode headless.

T: Berapa lama token CAPTCHA berlaku?
J: Token reCAPTCHA: ~2 menit. Turnstile: tergantung konfigurasi.

T: Berapa waktu rata-rata penyelesaian?
J: reCAPTCHA v2: 5-15 detik, Turnstile: 1-10 detik.

T: Bisakah saya menggunakan proxy sendiri?
J: Ya, gunakan tipe tugas tanpa kata "ProxyLess" dan berikan konfigurasi proxy.


Kesimpulan

Mengintegrasikan Capsolver dengan Katana memungkinkan penanganan CAPTCHA yang andal untuk kebutuhan penjelajahan web Anda. Skrip lengkap di atas dapat dicopy langsung dan digunakan dengan proyek Go Anda.

Siap mulai? Daftar di Capsolver dan tingkatkan crawler Anda!

💡 Bonus Eksklusif untuk Pengguna Integrasi Katana:
Untuk merayakan integrasi ini, kami menawarkan kode bonus 6% — Katana untuk semua pengguna Capsolver yang mendaftar melalui tutorial ini.
Cukup masukkan kode saat recharge di Dashboard untuk menerima tambahan 6% kredit secara instan.


12. Dokumentasi

Pernyataan Kepatuhan: Informasi yang diberikan di blog ini hanya untuk tujuan informasi. CapSolver berkomitmen untuk mematuhi semua hukum dan peraturan yang berlaku. Penggunaan jaringan CapSolver untuk kegiatan ilegal, penipuan, atau penyalahgunaan sangat dilarang dan akan diselidiki. Solusi penyelesaian captcha kami meningkatkan pengalaman pengguna sambil memastikan kepatuhan 100% dalam membantu menyelesaikan kesulitan captcha selama pengambilan data publik. Kami mendorong penggunaan layanan kami secara bertanggung jawab. Untuk informasi lebih lanjut, silakan kunjungi Syarat Layanan dan Kebijakan Privasi.

Lebih lanjut

Agno dengan Integrasi CapSolver
Cara Menyelesaikan Captcha di Agno dengan Integrasi CapSolver

Pelajari cara mengintegrasikan CapSolver dengan Agno untuk menyelesaikan tantangan reCAPTCHA v2/v3, Cloudflare Turnstile, dan WAF dalam agen AI otonom. Termasuk contoh Python nyata untuk pengambilan data web dan otomatisasi.

web scraping
Logo of CapSolver

Emma Foster

13-Jan-2026

Cara menyelesaikan Captcha dengan Katana Menggunakan CapSolver
Mengintegrasikan Katana dengan CapSolver: Penyelesaian CAPTCHA Otomatis untuk Penjelajahan Web

Pelajari cara mengintegrasikan Katana dengan Capsolver untuk secara otomatis menyelesaikan reCAPTCHA v2 dan Cloudflare Turnstile dalam crawling headless.

web scraping
Logo of CapSolver

Adélia Cruz

12-Jan-2026

Perpustakaan Web Scraping Python Teratas 2026
Pustaka Scraping Web Python Teratas 2026

Jelajahi perpustakaan web scraping Python terbaik untuk 2026. Bandingkan fitur, kemudahan penggunaan, dan kinerja untuk kebutuhan ekstraksi data Anda. Termasuk wawasan ahli dan FAQ.

web scraping
Logo of CapSolver

Anh Tuan

12-Jan-2026

Cara Menyelesaikan Captcha dengan Crawlab Menggunakan CapSolver
Mengintegrasikan Crawlab dengan CapSolver: Penyelesaian CAPTCHA Otomatis untuk Penjelajahan Terdistribusi

Pelajari cara mengintegrasikan CapSolver dengan Crawlab untuk menyelesaikan reCAPTCHA dan Cloudflare Turnstile secara skala.

web scraping
Logo of CapSolver

Ethan Collins

09-Jan-2026

Alat Scraping Kecerdasan Buatan Terbaik yang Harus Anda Ketahui pada 2026
Alat Scraping AI Terbaik yang Harus Anda Ketahui pada 2026

Temukan pilihan alat scraping AI terbaik untuk 2026. Kami membandingkan alat scraping web AI terbaik, termasuk Bright Data, Crawl4AI, dan Browse AI, dengan harga spesifik untuk membantu Anda menguasai ekstraksi data otomatis dan penyelesaian tantangan keamanan.

web scraping
Logo of CapSolver

Emma Foster

07-Jan-2026

Penyedia Data Alternatif Terbaik
Penyedia Data Alternatif Terbaik pada 2026 (Platform Terbaik Dibandingkan)

Temukan Penyedia Data Alternatif Terbaik pada 2026. Panduan kami membandingkan platform teratas (YipitData, FactSet, Preqin) dengan kelebihan, kekurangan, dan wawasan harga untuk kepatuhan dan penghasilan alpha.

web scraping
Logo of CapSolver

Emma Foster

06-Jan-2026