Scarnox · Blog

Go로 만드는 캡차 솔버: net/http를 사용한 실전 예제 (2026)

Go에서 캡차 솔버를 만드는 데는 서드파티 패키지가 필요 없습니다. 표준 라이브러리의 net/http와 encoding/json이 전체 흐름을 감당합니다. Scarnox API에 POST 한 번, JSON 응답 한 번, 사이트 폼 제출 한 번입니다. 이 가이드는 Scarnox가 현재 프록시 없이 풀 수 있는 PopularCaptcha를 대상으로 이 흐름을 만듭니다.

무료 체험 크레딧으로 시작하세요. 카드 등록은 필요 없습니다. 다른 곳에 사용하기 전에, 내가 관리하는 페이지에서 프로그램을 먼저 실행해 보세요.

요구 사항

완전한 프로그램

이 단일 파일은 캡차를 풀고, 일시적인 실패를 재시도하고, 토큰을 폼에 제출합니다. 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"

// solve 호출은 토큰이 준비될 때까지 블록되므로 타임아웃을 길게 잡습니다.
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)
	}

	// 쿠키 저장소는 캡차가 묶여 있는 세션을 유지합니다.
	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는 구조체를 API가 기대하는 JSON 본문으로 마샬링하고 Authorization 헤더를 설정합니다. Bearer 접두사가 필요합니다.

타임아웃. API는 토큰이 준비될 때까지 블록되므로 클라이언트 타임아웃은 185초입니다. 이보다 낮게 설정하면 곧 끝날 풀이를 취소하게 됩니다.

오류 검사. API는 성공 시 errorId를 0으로 반환합니다. 다른 값이 오면 errorCode와 errorDescription이 함께 오며, 함수는 이를 오류로 반환합니다. 토큰은 solution.token에 있습니다.

제출. 토큰은 h-captcha-response 필드에 들어가고, g-recaptcha-response에도 같은 값이 들어갑니다. 대부분의 사이트는 둘 중 하나를 읽습니다.

쿠키 저장소 사용하기

많은 회원가입과 검색 페이지가 캡차를 세션에 묶어 둡니다. 사이트 클라이언트의 cookiejar는 폼을 보여 주는 GET과 폼을 제출하는 POST 사이의 쿠키를 유지합니다. 이것을 생략하면, 사이트가 그렇지 않았다면 유효했을 토큰을 거부할 수 있습니다.

타이밍

토큰은 약 2분 뒤에 만료되므로 제출 직전에 풀이하세요. 긴 작업의 시작 부분에서 풀어 놓고 기다리지 마세요. 캡차 토큰 만료에서 기간을 어떻게 잡을지 설명합니다.

비가시형 위젯

페이지의 위젯이 보이지 않는 형태라면 invisible 인자를 true로 설정하세요. 그러면 요청에 is_invisible: true가 포함됩니다. 일부 페이지는 토큰과 함께 호출해야 하는 콜백 함수도 필요로 합니다. 페이지 스크립트에서 이를 확인하세요. 패턴은 비가시형 캡차 가이드에 나와 있습니다.

동시성

Go에서는 고루틴으로 많은 캡차를 동시에 푸는 것이 쉽습니다. 동시 풀이 수는 내 계정과 대상 사이트가 감당할 수 있는 범위 안에 두세요. 솔버가 따라갈 수 있다고 해서 한 도메인에 요청 폭주를 보내면 안 됩니다. 사이트의 robots.txt와 약관을 존중하고, 수집이 허용된 공개 데이터로만 스크래핑을 제한하세요.

요금

Scarnox는 성공한 풀이 건당 과금합니다. 실패한 풀이는 청구되지 않습니다. 요금은 현재 요금 페이지를 참고하세요. API를 시험해 보려면 무료 체험 크레딧으로 시작하세요. 카드 등록은 필요 없습니다.

다음 단계