Scarnox · Blog

Решатель капчи на Go: рабочий пример с net/http (2026)

Решателю капчи на Go не нужны сторонние пакеты. Стандартных net/http и encoding/json хватает на весь поток: один POST в API Scarnox, один JSON-ответ и одна отправка формы на сайт. Этот гайд строит такой поток для PopularCaptcha, которую Scarnox сегодня решает в варианте proxyless.

Начните с пробного баланса, карта не нужна и запустите программу на странице, которой вы управляете, прежде чем использовать её где-либо ещё.

Требования

Полная программа

Этот один файл решает капчу, повторяет временные сбои и отправляет токен в форму. Сохраните его как 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"

// Вызов блокируется до готовности токена, поэтому таймаут длинный.
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 повторяет временные сбои. Неудачные решения не списываются.
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)
	}

	// Cookie jar хранит сессию, к которой сайт привязывает капчу.
	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)
}

Запуск:

export SCARNOX_API_KEY=scarnox_YOURAPIKEY
go run main.go

Как части работают вместе

Запрос. solveCaptcha преобразует структуру в JSON-тело, которое ожидает API, и задаёт заголовок Authorization. Префикс Bearer обязателен.

Таймаут. API блокируется до готовности токена, поэтому таймаут клиента — 185 секунд. Не задавайте меньше, иначе отмените решения, которые вот-вот бы завершились.

Проверка ошибки. API возвращает errorId равный 0 при успехе. Любое другое значение приходит с errorCode и errorDescription, и функция возвращает их как ошибку. Токен лежит в solution.token.

Отправка. Токен попадает в поле h-captcha-response, а g-recaptcha-response получает то же значение. Большинство сайтов читают одно из них.

Многие страницы регистрации и поиска привязывают капчу к сессии. cookiejar у клиента сайта сохраняет эти cookie между GET, который показывает форму, и POST, который её отправляет. Если его пропустить, сайт может отклонить токен, который в остальном был валидным.

Тайминг

Токен истекает примерно через две минуты, поэтому решайте непосредственно перед отправкой. Не решайте в начале длинной задачи и не ждите. Истечение токена капчи объясняет, как рассчитать окно.

Невидимые виджеты

Если виджет на странице невидимый, передайте аргумент invisible равным true. Тогда запрос включает is_invisible: true. Некоторым страницам также нужна функция-колбэк, которой передают токен. Проверьте скрипт страницы, чтобы её найти. Шаблоны перечислены в гайде невидимая капча.

Конкурентность

В Go легко решать много капч одновременно с помощью горутин. Держите число параллельных решений в пределах того, что выдержат ваш аккаунт и целевой сайт. Один домен не должен получать всплеск запросов, даже если ваш решатель за ними успевает. Соблюдайте robots.txt и условия сайта и ограничивайте парсинг публичными данными, которые вам разрешено собирать.

Тарифы

Scarnox берёт плату за каждое успешное решение. Неудачные решения не списываются. Расценки — на странице цен. Чтобы попробовать API, начните с пробного баланса, карта не нужна.

Что дальше