Scarnox · Blog

Solver captcha w Go: działający przykład z net/http (2026)

Solver captcha w Go nie potrzebuje pakietów zewnętrznych. Standardowe net/http i encoding/json obejmują cały przepływ: jedno żądanie POST do API Scarnox, jedną odpowiedź JSON i jedno wysłanie formularza na stronę. Ten przewodnik buduje ten przepływ dla PopularCaptcha, które Scarnox dziś rozwiązuje bez proxy.

Zacznij od darmowego kredytu próbnego, bez karty i uruchom program na stronie, którą kontrolujesz, zanim użyjesz go gdziekolwiek indziej.

Wymagania

Kompletny program

Ten pojedynczy plik rozwiązuje captcha, ponawia przejściowe błędy i wysyła token do formularza. Zapisz go jako main.go:

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"os"
	"time"
)

const apiURL = "https://api.scarnox.com/api/tasks/create"

// Wywołanie blokuje się, aż token będzie gotowy, dlatego timeout jest długi.
var apiClient = &http.Client{Timeout: 185 * time.Second}

type createRequest struct {
	CaptchaType string `json:"captcha_type"`
	SiteKey     string `json:"site_key"`
	SiteURL     string `json:"site_url"`
	IsInvisible bool   `json:"is_invisible,omitempty"`
}

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

func solveCaptcha(apiKey, sitekey, pageURL string, invisible bool) (string, error) {
	body, err := json.Marshal(createRequest{
		CaptchaType: "PopularCaptchaTaskProxyless",
		SiteKey:     sitekey,
		SiteURL:     pageURL,
		IsInvisible: invisible,
	})
	if err != nil {
		return "", err
	}

	req, err := http.NewRequest(http.MethodPost, apiURL, bytes.NewReader(body))
	if err != nil {
		return "", err
	}
	req.Header.Set("Authorization", "Bearer "+apiKey)
	req.Header.Set("Content-Type", "application/json")

	resp, err := apiClient.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	raw, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	var out createResponse
	if err := json.Unmarshal(raw, &out); err != nil {
		return "", fmt.Errorf("HTTP %d, response was not JSON: %s", resp.StatusCode, raw)
	}
	if out.ErrorID != 0 {
		return "", fmt.Errorf("%s: %s", out.ErrorCode, out.ErrorDescription)
	}
	return out.Solution.Token, nil
}

// solveWithRetry ponawia przejściowe błędy. Nieudane rozwiązania nie są naliczane.
func solveWithRetry(apiKey, sitekey, pageURL string, attempts int) (string, error) {
	var lastErr error
	for i := 1; i <= attempts; i++ {
		token, err := solveCaptcha(apiKey, sitekey, pageURL, false)
		if err == nil {
			return token, nil
		}
		lastErr = err
		log.Printf("attempt %d failed: %v", i, err)
		time.Sleep(time.Duration(i) * time.Second)
	}
	return "", lastErr
}

func main() {
	apiKey := os.Getenv("SCARNOX_API_KEY")
	if apiKey == "" {
		log.Fatal("set SCARNOX_API_KEY")
	}

	const pageURL = "https://example.com/signup"
	const sitekey = "YOUR-SITEKEY-UUID"

	token, err := solveWithRetry(apiKey, sitekey, pageURL, 3)
	if err != nil {
		log.Fatal(err)
	}

	// Cookie jar zachowuje sesję, z którą serwis wiąże captcha.
	jar, err := cookiejar.New(nil)
	if err != nil {
		log.Fatal(err)
	}
	site := &http.Client{Jar: jar, Timeout: 60 * time.Second}

	form := url.Values{}
	form.Set("email", "you@example.com")
	form.Set("h-captcha-response", token)
	form.Set("g-recaptcha-response", token)

	resp, err := site.PostForm(pageURL, form)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()
	fmt.Println("Status:", resp.Status)
}

Uruchom:

export SCARNOX_API_KEY=scarnox_YOURAPIKEY
go run main.go

Jak elementy się łączą

Żądanie. solveCaptcha serializuje strukturę do treści JSON, której oczekuje API, i ustawia nagłówek Authorization. Prefiks Bearer jest wymagany.

Timeout. API blokuje się, aż token będzie gotowy, więc timeout klienta wynosi 185 sekund. Nie ustawiaj go niżej, bo anulujesz rozwiązania, które właśnie miały się zakończyć.

Sprawdzenie błędu. API zwraca errorId równe 0 przy sukcesie. Każda inna wartość przychodzi z errorCode i errorDescription, a funkcja zwraca je jako błąd. Token znajduje się w solution.token.

Wysłanie. Token trafia do pola h-captcha-response, a g-recaptcha-response dostaje tę samą wartość. Większość serwisów czyta jedno z nich.

Wiele stron rejestracji i wyszukiwania wiąże captcha z sesją. cookiejar na kliencie strony zachowuje te ciasteczka między GET, które pokazuje formularz, a POST, który go wysyła. Jeśli to pominiesz, serwis może odrzucić token, który w innym wypadku byłby poprawny.

Czas

Token wygasa po około dwóch minutach, więc rozwiązuj tuż przed wysłaniem. Nie rozwiązuj na początku długiego zadania, a potem nie czekaj. Wygasanie tokenu captcha wyjaśnia, jak dobrać okno czasowe.

Niewidoczne widżety

Jeśli widżet na stronie jest niewidoczny, ustaw argument invisible na true. Żądanie zawiera wtedy is_invisible: true. Niektóre strony wymagają też funkcji zwrotnej wywoływanej z tokenem. Sprawdź skrypt strony, czy ją zawiera. Wzorce opisuje przewodnik o niewidocznej captcha.

Równoległość

Go ułatwia rozwiązywanie wielu captcha naraz za pomocą goroutine. Utrzymuj liczbę równoległych rozwiązań w granicach tego, co obsłuży Twoje konto i docelowy serwis. Jedna domena nie powinna otrzymywać serii żądań, nawet jeśli solver nadąża. Szanuj robots.txt i regulamin serwisu oraz ogranicz scrapowanie do danych publicznych, które wolno Ci zbierać.

Cennik

Scarnox nalicza opłatę za każde udane rozwiązanie. Nieudane rozwiązania są bezpłatne. Stawki znajdziesz na stronie cennika. Aby wypróbować API, zacznij od darmowego kredytu próbnego, bez karty.

Następne kroki