Resuelve tareas de CAPTCHA de forma concurrente en Go

Por solvethree4 min de lectura

Usa un único cliente compartido del SDK de Go y un grupo fijo de workers. Asigna a cada trabajo su propio contexto y envía cada solución en cuanto esté lista, en lugar de acumular tokens para un lote posterior.

En esta página

Prepara el cliente y el destino

  • Usa Go 1.22 o posterior y el módulo oficial solvethree.com/sdk en la versión v0.1.0. Los detalles de instalación están en la documentación de integración con Go.
  • Configura SOLVETHREE_API_KEY y SOLVETHREE_PROXY en el entorno del servidor. No incluyas la clave de API en el código del navegador.
  • Sustituye accounts.example.com y login por el nombre de host y la acción de destino. El espacio de trabajo necesita un perfil activo para ese sitio; enviar el nombre de host no crea uno. Consulta la configuración del perfil del sitio.

El ejemplo solicita reCAPTCHA Enterprise, la opción predeterminada. Proporciona un proxy y deja intencionadamente Type y URL sin definir: las solicitudes de reCAPTCHA usan el nombre de host del sitio y la acción, no esos campos.

Limita el trabajo, no el número de clientes

El cliente admite el uso concurrente de forma segura. Este ejemplo inicia cuatro workers y les distribuye ocho trabajos mediante un canal sin búfer. El productor espera cuando todos los workers están ocupados, en lugar de lanzar una goroutine por cada trabajo. Cuatro es un valor ilustrativo de configuración de la aplicación, no un límite del servicio solvethree.

Go · cuatro procesos de trabajo y gestión inmediata de cada trabajo
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()
}

Cada worker crea un contexto independiente de 180 segundos para cada trabajo. Solve crea la tarea una sola vez y espera su resultado. Como el ejemplo omite RequestID, el SDK genera uno para esa creación. Las llamadas fallidas se registran y continue hace que solo ese worker pase al siguiente trabajo; no detiene el lote.

Mantén la cancelación dentro del bucle, como se muestra, para que cada trabajo libere los recursos de su contexto sin demora. La cancelación detiene la espera local; no cancela el trabajo remoto.

Procesa la solución antes de tomar otro trabajo

El ejemplo imprime el ID de la tarea lista; no envía nada al destino. Sustituye el comentario por tu solicitud al destino y luego informa del resultado del envío de ese trabajo antes de que el worker tome otro. No añadas tokens a un slice para usarlos después de workers.Wait.

  • Envía result.Solution.Token, no result.ID.
  • Usa SuccessfulProxy con la tarea original y el resultado listo para identificar el proxy que funcionó. Realiza el envío a través de ese proxy con la identidad del navegador devuelta en UserAgent y SecChUA.
  • Mantén el envío dentro del contexto del trabajo, antes de cancel(). Así, el plazo de 180 segundos abarca tanto la resolución como el envío; usa un contexto de envío independiente si necesitas un margen de tiempo separado.

Informa por separado de la resolución y del envío al destino. Una tarea lista proporciona una solución utilizable, pero solo la respuesta del destino indica si aceptó la solicitud de la aplicación.

Recupera un trabajo interrumpido sin reiniciar el lote

Registrar un error basta para este pequeño ejemplo, pero una integración que permita la recuperación debe conservar los datos de entrada exactos de la tarea de cada trabajo y los ID de solicitud y tarea disponibles. Un tiempo de espera agotado no demuestra que la creación de la tarea haya fallado.

  • Extrae *solvethree.Error para obtener RequestID y TaskID. Si conoces el ID de la tarea, reanuda la consulta con GetTask usando un contexto nuevo.
  • Si no sabes si la creación se completó y tienes el ID de solicitud, vuelve a enviar los datos de entrada originales con ese mismo RequestID. La misma solicitud devuelve la tarea existente; cambiar los datos de entrada y reutilizar el ID provoca un conflicto.
  • Para *TaskFailedError, registra ErrorCode y recupera la tarea fallida si necesitas su texto de Error. Deja de consultar esa tarea y corrige la causa, en lugar de volver a enviar a ciegas los mismos datos de entrada no válidos.

Distingue los errores HTTP de los fallos terminales de las tareas. errors.As puede extraer un *APIError anidado con StatusCode y RetryAfter. Solve ya reintenta las respuestas temporales de consulta con HTTP 429, 502, 503 o 504, respetando Retry-After dentro del plazo del contexto. La creación de tareas nunca se reintenta automáticamente.

Ejecuta tu primera tarea.

Empieza con $5 de crédito en tu espacio de trabajo.

Crear un espacio de trabajo
Resuelve tareas de CAPTCHA de forma concurrente en Go · solvethree