Параллельное решение задач CAPTCHA в Go
Используйте один общий клиент Go SDK и пул воркеров фиксированного размера. Создавайте отдельный контекст для каждого задания и отправляйте каждое готовое решение сразу, а не накапливайте токены для последующей пакетной отправки.
На этой странице
Подготовьте клиент и целевой сайт
- Используйте Go 1.22 или новее и официальный модуль
solvethree.com/sdkверсииv0.1.0. Подробности установки приведены в документации по интеграции с Go. - Задайте
SOLVETHREE_API_KEYиSOLVETHREE_PROXYв окружении сервера. Не размещайте API-ключ в браузерном коде. - Замените
accounts.example.comиloginна имя целевого хоста и действие. В рабочем пространстве должен быть активный профиль этого сайта; передача имени хоста не создаёт профиль. См. настройку профиля сайта.
В примере запрашивается reCAPTCHA Enterprise — вариант по умолчанию. Передаётся один прокси, а поля Type и URL намеренно не задаются: запросы reCAPTCHA используют имя хоста сайта и действие, а не эти поля.
Ограничивайте объём параллельной работы, а не число клиентов
Клиент безопасен для конкурентного использования. В этом примере запускаются четыре воркера, которым через небуферизованный канал передаются восемь заданий. Когда все воркеры заняты, отправитель ждёт, а не запускает горутину для каждого задания. Четыре — это пример настройки приложения, а не ограничение сервиса 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()
}Каждый воркер создаёт отдельный контекст на 180 секунд для каждого задания. Solve создаёт задачу один раз и ждёт её результата. Поскольку в примере не указан RequestID, SDK генерирует его для этого создания. При неудачном вызове ошибка записывается в журнал, а continue переводит только этот воркер к следующему заданию; обработка пакета не останавливается.
Вызывайте отмену внутри цикла, как показано в примере, чтобы каждое задание своевременно освобождало ресурсы своего контекста. Отмена прекращает локальное ожидание, но не отменяет удалённую работу.
Обработайте решение, прежде чем брать следующее задание
Пример выводит ID готовой задачи, но ничего не отправляет на целевой сайт. Замените комментарий запросом к целевому сайту, затем сообщите результат отправки для этого задания, прежде чем воркер возьмёт следующее. Не добавляйте токены в срез для использования после workers.Wait.
- Отправляйте
result.Solution.Token, а неresult.ID. - Используйте
SuccessfulProxyс исходной задачей и готовым результатом, чтобы определить успешный прокси. Отправляйте запрос через этот прокси с возвращёнными данными браузераUserAgentиSecChUA. - Выполняйте отправку в контексте задания, до
cancel(). Тогда лимит в 180 секунд охватывает и решение, и отправку; если для отправки нужен отдельный лимит времени, используйте отдельный контекст.
Сообщайте результаты решения и отправки на целевой сайт отдельно. Готовая задача предоставляет пригодное к использованию решение, но только ответ целевого сайта показывает, принят ли запрос приложения.
Восстановите прерванное задание без перезапуска всего пакета
Для этого небольшого примера достаточно записать ошибку в журнал, но интеграция с поддержкой восстановления должна сохранять точные входные данные каждого задания и доступные ID запроса и задачи. Тайм-аут не доказывает, что создать задачу не удалось.
- Извлеките
*solvethree.Error, чтобы получитьRequestIDиTaskID. Если ID задачи известен, возобновите получение результата черезGetTaskс новым контекстом. - Если результат создания неизвестен и у вас есть ID запроса, повторно отправьте исходные входные данные с тем же
RequestID. Тот же запрос возвращает существующую задачу; изменение входных данных при повторном использовании ID вызывает конфликт. - Для
*TaskFailedErrorсохранитеErrorCodeи получите завершившуюся с ошибкой задачу, если нужен текст еёError. Прекратите опрос этой задачи и устраните причину, а не отправляйте вслепую те же некорректные входные данные снова.
Различайте HTTP-ошибки и окончательные сбои задач. errors.As позволяет извлечь вложенную *APIError с StatusCode и RetryAfter. Solve уже повторяет опрос при временных ответах HTTP 429, 502, 503 или 504, соблюдая Retry-After в пределах срока действия контекста. Создание задачи никогда не повторяется автоматически.
Источники и справочник API
Запустите первую задачу.
Начните с $5 на балансе рабочего пространства.