Résoudre des tâches CAPTCHA en parallèle en Go

Par solvethree4 min de lecture

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/sdk en version v0.1.0. Les détails d’installation figurent dans la documentation d’intégration Go.
  • Définissez SOLVETHREE_API_KEY et SOLVETHREE_PROXY dans l’environnement du serveur. Ne placez pas la clé API dans le code du navigateur.
  • Remplacez accounts.example.com et login par 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.

Go · quatre workers et traitement immédiat de chaque tâche
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, pas result.ID.
  • Utilisez SuccessfulProxy avec 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 dans UserAgent et SecChUA.
  • 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.Error pour obtenir RequestID et TaskID. Si l’ID de tâche est connu, reprenez la récupération avec GetTask dans 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, enregistrez ErrorCode et récupérez la tâche en échec si vous avez besoin du texte de son champ Error. 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.

Lancez votre première tâche.

Démarrez avec $5 de crédit dans votre espace de travail.

Créer un espace de travail
Résoudre des tâches CAPTCHA en parallèle en Go · solvethree