Java로 만드는 캡차 솔버: HttpClient를 이용한 실전 예제 (2026)
Java에서 캡차 솔버는 HTTP 호출 한 번입니다. Java 11 이상에는 java.net.http.HttpClient가 기본으로 들어 있으므로, 이 가이드에 필요한 의존성은 JSON 처리를 위한 Gson 하나뿐입니다. Scarnox API에 사이트키와 페이지 URL을 POST하고, solution.token을 읽은 뒤, 토큰을 사이트 폼에 제출합니다. 이 가이드는 Scarnox가 현재 프록시 없이 풀 수 있는 PopularCaptcha를 다룹니다.
무료 체험 크레딧으로 시작하세요. 카드 등록은 필요 없습니다. 다른 곳에 사용하기 전에, 내가 관리하는 페이지에서 코드를 먼저 시험해 보세요.
요구 사항
- Java 17 이상 (
java -version으로 확인). - Gson 2.10 이상. Maven에서는
com.google.code.gson:gson:2.10.1. - Scarnox API 키. 환경 변수에서 읽어옵니다.
- 페이지의 PopularCaptcha 위젯 sitekey. 찾는 방법은 캡차 sitekey 찾기를 참고하세요.
솔버 클래스
이 클래스는 쿠키를 저장하는 HttpClient 하나를 감쌉니다. 그래서 폼 제출까지 세션이 이어질 수 있습니다. solve 메서드는 Bearer 헤더를 설정하고, JSON 본문을 보내고, errorId를 확인합니다.
// ScarnoxSolver.java
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.net.CookieManager;
import java.net.CookiePolicy;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.LinkedHashMap;
import java.util.Map;
public class ScarnoxSolver {
private static final String API_URL = "https://api.scarnox.com/api/tasks/create";
private static final Gson GSON = new Gson();
private final String apiKey;
private final HttpClient http = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.cookieHandler(new CookieManager(null, CookiePolicy.ACCEPT_ALL))
.build();
public ScarnoxSolver(String apiKey) {
this.apiKey = apiKey;
}
/** 토큰을 반환합니다. 토큰이 준비될 때까지 호출이 블록됩니다. */
public String solve(String sitekey, String pageUrl, boolean invisible) throws Exception {
Map<String, Object> body = new LinkedHashMap<>();
body.put("captcha_type", "PopularCaptchaTaskProxyless");
body.put("site_key", sitekey);
body.put("site_url", pageUrl);
if (invisible) {
body.put("is_invisible", true);
}
HttpRequest request = HttpRequest.newBuilder(URI.create(API_URL))
.timeout(Duration.ofSeconds(185))
.header("Authorization", "Bearer " + apiKey)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(GSON.toJson(body)))
.build();
HttpResponse<String> response = http.send(request, HttpResponse.BodyHandlers.ofString());
JsonObject data;
try {
data = JsonParser.parseString(response.body()).getAsJsonObject();
} catch (RuntimeException e) {
throw new IllegalStateException("HTTP " + response.statusCode() + ": not JSON");
}
if (data.get("errorId").getAsInt() != 0) {
throw new IllegalStateException(
data.get("errorCode").getAsString() + ": "
+ data.get("errorDescription").getAsString());
}
return data.getAsJsonObject("solution").get("token").getAsString();
}
/** 토큰을 사이트 폼에 제출하고 HTTP 상태 코드를 반환합니다. */
public int submit(String pageUrl, String email, String token) throws Exception {
// 세션 쿠키가 설정되도록 먼저 페이지를 불러옵니다.
http.send(HttpRequest.newBuilder(URI.create(pageUrl)).GET().build(),
HttpResponse.BodyHandlers.discarding());
String form = "email=" + enc(email)
+ "&h-captcha-response=" + enc(token)
+ "&g-recaptcha-response=" + enc(token);
HttpRequest request = HttpRequest.newBuilder(URI.create(pageUrl))
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(form))
.build();
return http.send(request, HttpResponse.BodyHandlers.discarding()).statusCode();
}
private static String enc(String value) {
return URLEncoder.encode(value, StandardCharsets.UTF_8);
}
}
Authorization 헤더에는 Bearer 접두사를 사용합니다. 요청 타임아웃은 185초인데, API가 토큰이 준비될 때까지 블록되기 때문입니다. 폴링 단계는 없습니다.
메인 프로그램
main 메서드는 몇 번 재시도하며 풀이한 뒤 토큰을 제출합니다.
// Main.java
public class Main {
public static void main(String[] args) throws Exception {
String apiKey = System.getenv("SCARNOX_API_KEY");
if (apiKey == null || apiKey.isBlank()) {
throw new IllegalStateException("Set SCARNOX_API_KEY");
}
String pageUrl = "https://example.com/signup";
String sitekey = "YOUR-SITEKEY-UUID";
ScarnoxSolver solver = new ScarnoxSolver(apiKey);
String token = null;
for (int attempt = 1; attempt <= 3 && token == null; attempt++) {
try {
token = solver.solve(sitekey, pageUrl, false);
} catch (IllegalStateException e) {
System.err.println("Attempt " + attempt + " failed: " + e.getMessage());
Thread.sleep(attempt * 1000L);
}
}
if (token == null) {
throw new IllegalStateException("No token after 3 attempts");
}
int status = solver.submit(pageUrl, "you@example.com", token);
System.out.println("Status: " + status);
}
}
실행하세요.
export SCARNOX_API_KEY=scarnox_YOURAPIKEY
javac -cp gson-2.10.1.jar *.java
java -cp .:gson-2.10.1.jar Main
이렇게 구조를 잡은 이유
클라이언트 하나당 솔버 하나. HttpClient는 스레드 안전하며 재사용하도록 설계되었습니다. 요청마다 새로 만들면 연결이 낭비됩니다.
쿠키 사용. 클라이언트의 CookieManager는 폼을 불러온 세션을 유지하므로, 토큰이 사이트가 기대하는 쿠키와 함께 전송됩니다.
명시적인 오류 검사. API는 성공 시 errorId를 0으로 반환합니다. 다른 값이 오면 errorCode와 errorDescription이 함께 오며, 솔버는 그 텍스트와 함께 예외를 던집니다. 토큰은 solution.token에 있습니다.
두 캡차 필드 모두. 폼에는 h-captcha-response와 g-recaptcha-response가 모두 들어갑니다. 대부분의 사이트는 둘 중 하나를 읽기 때문입니다.
타이밍
토큰은 약 2분 뒤에 만료됩니다. 긴 작업의 시작 부분이 아니라 제출 직전에 풀이하세요. 캡차 토큰 만료에서 기간을 잡는 방법과, 만료된 토큰과 거부된 토큰을 구별하는 방법을 설명합니다.
비가시형 위젯
위젯이 보이지 않는 형태라면 solve의 세 번째 인자로 true를 전달하세요. 그러면 요청에 is_invisible: true가 포함됩니다. 페이지가 토큰과 함께 호출할 JavaScript 콜백을 필요로 한다면, 서버 측 클라이언트로는 이를 트리거할 수 없으므로 브라우저 자동화 도구가 더 나은 선택입니다. 패턴은 비가시형 캡차 가이드에 나와 있습니다.
오류 처리
재시도는 일시적인 실패를 다룹니다. 잘못된 sitekey나 위젯이 실행되지 않는 페이지 URL처럼 바뀌지 않을 오류에서는 재시도를 멈추고, 검토를 위해 기록하세요. 어떤 코드에서 멈출지를 포함한 전체 정책은 캡차 솔버 오류 처리에 있습니다.
스크래핑은 책임감 있게
프로그램이 공개 페이지를 스크래핑한다면, 먼저 robots.txt와 사이트 약관을 확인하세요. 요청 사이에 지연을 두고, 필요한 개인 데이터만 보관하세요. 법적 배경은 웹 스크래핑은 합법인가를 참고하세요.
요금
성공한 풀이 건당 요금이 청구되며, 실패한 풀이는 무료입니다. 요금은 현재 요금 페이지를 참고하세요. 시작하려면 무료 체험 크레딧으로 시작하세요. 카드 등록은 필요 없습니다.