Résoudre des tâches CAPTCHA en parallèle en Go
Utilisez un seul client partagé du SDK Go et un pool fixe de workers. Attribuez à chaque travail son propre contexte, puis soumettez chaque solution dès qu’elle est prête au lieu de collecter les jetons pour un traitement groupé ultérieur.
Sur cette page
Préparer le client et la cible
- Utilisez Go 1.22 ou une version ultérieure et le module officiel
solvethree.com/sdken versionv0.1.0. Les détails d’installation figurent dans la documentation d’intégration Go. - Définissez
SOLVETHREE_API_KEYetSOLVETHREE_PROXYdans l’environnement du serveur. Ne placez pas la clé API dans le code du navigateur. - Remplacez
accounts.example.cometloginpar le nom d’hôte et l’action de votre cible. L’espace de travail doit disposer d’un profil actif pour ce site ; l’envoi du nom d’hôte n’en crée pas. Consultez la configuration du profil de site.
L’exemple demande reCAPTCHA Enterprise, le type par défaut. Il fournit un seul proxy et laisse volontairement Type et URL non définis : les requêtes reCAPTCHA utilisent le nom d’hôte du site et l’action, pas ces champs.
Limiter le travail, pas le nombre de clients
Le client peut être utilisé simultanément en toute sécurité. Cet exemple démarre quatre workers et leur transmet huit travaux via un canal non bufferisé. Le producteur attend lorsque tous les workers sont occupés, au lieu de lancer une goroutine pour chaque travail. Le nombre quatre est un paramètre d’application donné à titre d’exemple, pas une limite du service solvethree.
package main
import (
"context"
"fmt"
"log"
"os"
"sync"
"time"
solvethree "solvethree.com/sdk"
)
func main() {
client, err := solvethree.NewClient(os.Getenv("SOLVETHREE_API_KEY"), nil)
if err != nil { log.Fatal(err) }
jobs := make(chan int)
var workers sync.WaitGroup
for worker := 0; worker < 4; worker++ {
workers.Add(1)
go func() {
defer workers.Done()
for job := range jobs {
ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second)
input := solvethree.TaskRequest{Site: "accounts.example.com", Action: "login", Proxy: os.Getenv("SOLVETHREE_PROXY")}
result, err := client.Solve(ctx, input, solvethree.SolveOptions{})
if err != nil { cancel(); log.Printf("Job %d: %v", job, err); continue }
fmt.Println("Ready task:", result.ID)
// Submit this solution now, with its proxy and browser identity.
cancel()
}
}()
}
for job := 0; job < 8; job++ { jobs <- job }
close(jobs)
workers.Wait()
}Chaque worker crée un contexte distinct de 180 secondes pour chaque travail. Solve crée la tâche une seule fois et attend son résultat. Comme l’exemple omet RequestID, le SDK en génère un pour cette création. Tout échec d’appel est journalisé et continue fait passer uniquement le worker concerné au travail suivant ; le lot n’est pas arrêté.
Conservez l’annulation dans la boucle, comme indiqué, pour que chaque travail libère rapidement les ressources de son contexte. L’annulation arrête l’attente locale ; elle n’annule pas le travail distant.
Traiter la solution avant de prendre un autre travail
L’exemple affiche l’ID de la tâche prête ; il ne soumet rien à la cible. Remplacez le commentaire par votre requête à la cible, puis indiquez le résultat de la soumission de ce travail avant que le worker n’en prenne un autre. N’ajoutez pas les jetons à une slice pour les utiliser après workers.Wait.
- Soumettez
result.Solution.Token, pasresult.ID. - Utilisez
SuccessfulProxyavec la tâche d’origine et le résultat prêt pour identifier le proxy qui a réussi. Effectuez la soumission via ce proxy avec l’identité de navigateur renvoyée dansUserAgentetSecChUA. - Effectuez la soumission dans le contexte du travail, avant
cancel(). Le délai de 180 secondes couvre alors à la fois la résolution et la soumission ; utilisez un contexte de soumission distinct si vous avez besoin d’un budget de temps séparé.
Indiquez séparément les résultats de la résolution et de la soumission à la cible. Une tâche prête fournit une solution utilisable, mais seule la réponse de la cible vous indique si elle a accepté la requête applicative.
Reprendre un travail interrompu sans redémarrer le lot
Journaliser une erreur suffit pour ce petit exemple, mais une intégration permettant la reprise doit conserver les données exactes de la tâche pour chaque travail, ainsi que les ID de requête et de tâche disponibles. Un dépassement de délai ne prouve pas que la création de la tâche a échoué.
- Extrayez
*solvethree.Errorpour obtenirRequestIDetTaskID. Si l’ID de tâche est connu, reprenez la récupération avecGetTaskdans un nouveau contexte. - Si la création est incertaine et que vous disposez de l’ID de requête, renvoyez les données d’origine avec ce même
RequestID. La même requête renvoie la tâche existante ; modifier les données tout en réutilisant l’ID provoque un conflit. - Pour
*TaskFailedError, enregistrezErrorCodeet récupérez la tâche en échec si vous avez besoin du texte de son champError. Arrêtez d’interroger cette tâche et corrigez la cause au lieu de soumettre à nouveau aveuglément les mêmes données invalides.
Distinguez les erreurs HTTP des échecs définitifs de tâche. errors.As peut extraire une erreur *APIError imbriquée contenant StatusCode et RetryAfter. Solve retente déjà les interrogations qui reçoivent des réponses temporaires HTTP 429, 502, 503 ou 504, en respectant Retry-After dans la limite du délai du contexte. La création d’une tâche n’est jamais retentée automatiquement.
Sources et référence de l’API
Lancez votre première tâche.
Démarrez avec $5 de crédit dans votre espace de travail.