Scarnox · Blog

Solver captcha in Go: esempio funzionante con net/http (2026)

Un solver captcha in Go non richiede pacchetti di terze parti. net/http e encoding/json della libreria standard coprono l'intero flusso: una POST all'API di Scarnox, una risposta JSON e un invio del form al sito. Questa guida costruisce quel flusso per PopularCaptcha, che Scarnox risolve oggi in modalità proxyless.

Inizia con crediti di prova gratuiti, senza carta di credito ed esegui il programma su una pagina che controlli prima di usarlo altrove.

Requisiti

Il programma completo

Questo file singolo risolve il captcha, riprova gli errori temporanei e invia il token al form. Salvalo come 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"

// La chiamata di solve blocca finché il token non è pronto, quindi il timeout è lungo.
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 riprova gli errori temporanei. Le risoluzioni fallite non vengono addebitate.
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)
	}

	// Un cookie jar mantiene la sessione a cui il sito lega il 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)
}

Eseguilo:

export SCARNOX_API_KEY=scarnox_YOURAPIKEY
go run main.go

Come si incastrano i pezzi

La richiesta. solveCaptcha serializza una struct nel corpo JSON che l'API si aspetta e imposta l'header Authorization. Il prefisso Bearer è obbligatorio.

Il timeout. L'API si blocca finché il token non è pronto, quindi il timeout del client è di 185 secondi. Non impostarlo più basso, o annullerai risoluzioni che stavano per finire.

Il controllo degli errori. L'API restituisce errorId uguale a 0 in caso di successo. Qualsiasi altro valore arriva con errorCode e errorDescription, e la funzione li restituisce come errore. Il token è in solution.token.

L'invio. Il token va nel campo h-captcha-response, e g-recaptcha-response riceve lo stesso valore. La maggior parte dei siti legge uno dei due.

Molte pagine di registrazione e ricerca legano il captcha a una sessione. Il cookiejar sul client del sito mantiene quei cookie tra la GET che mostra il form e la POST che lo invia. Se lo ometti, il sito potrebbe rifiutare un token altrimenti valido.

Tempistiche

Il token scade in circa due minuti, quindi risolvi subito prima di inviare. Non risolvere all'inizio di un job lungo e poi aspettare. Scadenza del token captcha spiega come dimensionare la finestra.

Widget invisibili

Se il widget della pagina è invisibile, imposta l'argomento invisible a true. La richiesta include allora is_invisible: true. Alcune pagine richiedono anche una funzione di callback chiamata con il token. Controlla lo script della pagina per trovarla. La guida sul captcha invisibile elenca i pattern.

Concorrenza

In Go è facile risolvere molti captcha insieme con le goroutine. Mantieni il numero di risoluzioni concorrenti entro ciò che il tuo account e il sito di destinazione possono sostenere. Un singolo dominio non dovrebbe ricevere una raffica di richieste, anche se il tuo solver riesce a tenere il passo. Rispetta robots.txt e i termini del sito, e limita lo scraping a dati pubblici che sei autorizzato a raccogliere.

Prezzi

Scarnox addebita per ogni risoluzione riuscita. Le risoluzioni fallite sono gratuite. Consulta i prezzi attuali per le tariffe. Per provare l'API, inizia con crediti di prova gratuiti, senza carta di credito.

Prossimi passi