Scarnox · Blog

Solveur de captcha en Go : exemple complet avec net/http (2026)

Un solveur de captcha en Go n'a besoin d'aucun paquet tiers. net/http et encoding/json de la bibliothèque standard couvrent tout le flux : un POST vers l'API Scarnox, une réponse JSON, puis une soumission de formulaire vers le site. Ce guide construit ce flux pour PopularCaptcha, que Scarnox résout aujourd'hui sans proxy.

Démarrez avec un crédit d'essai gratuit, sans carte bancaire et exécutez le programme sur une page que vous contrôlez avant de l'utiliser ailleurs.

Prérequis

Le programme complet

Ce fichier unique résout le captcha, relance les échecs transitoires et soumet le token au formulaire. Enregistrez-le sous 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"

// L'appel de résolution bloque jusqu'à ce que le token soit prêt, d'où un délai long.
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 relance les échecs transitoires. Les résolutions échouées ne sont pas facturées.
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 pot de cookies conserve la session à laquelle le site rattache le 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)
}

Exécutez-le :

export SCARNOX_API_KEY=scarnox_YOURAPIKEY
go run main.go

Comment les éléments s'articulent

La requête. solveCaptcha sérialise une structure dans le corps JSON attendu par l'API et définit l'en-tête Authorization. Le préfixe Bearer est obligatoire.

Le délai d'attente. L'API bloque jusqu'à ce que le token soit prêt, donc le délai client est de 185 secondes. Ne le réglez pas plus bas, sinon vous annulerez des résolutions sur le point de se terminer.

La vérification d'erreur. L'API renvoie errorId à 0 en cas de succès. Toute autre valeur s'accompagne de errorCode et errorDescription, et la fonction les renvoie sous forme d'erreur. Le token se trouve dans solution.token.

La soumission. Le token va dans le champ h-captcha-response, et g-recaptcha-response reçoit la même valeur. La plupart des sites lisent l'un ou l'autre.

Utiliser un pot de cookies

Beaucoup de pages d'inscription et de recherche rattachent le captcha à une session. Le cookiejar du client du site conserve ces cookies entre le GET qui affiche le formulaire et le POST qui le soumet. Si vous l'omettez, le site peut rejeter un token par ailleurs valide.

Timing

Le token expire en environ deux minutes, résolvez donc juste avant de soumettre. Ne résolvez pas au début d'un long job pour attendre ensuite. L'expiration des tokens de captcha explique comment dimensionner la fenêtre.

Widgets invisibles

Si le widget de la page est invisible, passez l'argument invisible à true. La requête inclut alors is_invisible: true. Certaines pages nécessitent aussi une fonction de callback appelée avec le token. Vérifiez le script de la page pour la trouver. Le guide sur le captcha invisible présente les schémas à suivre.

Concurrence

Go permet de résoudre facilement plusieurs captchas en même temps avec des goroutines. Gardez le nombre de résolutions simultanées dans les limites de ce que votre compte et le site cible peuvent supporter. Un seul domaine ne doit pas recevoir une rafale de requêtes, même si votre solveur peut suivre. Respectez le robots.txt et les conditions du site, et limitez le scraping aux données publiques que vous avez le droit de collecter.

Tarifs

Scarnox facture par résolution réussie. Les résolutions échouées sont gratuites. Consultez les tarifs actuels pour les montants. Pour essayer l'API, démarrez avec un crédit d'essai gratuit, sans carte bancaire.

Étapes suivantes