C#에서 Selenium으로 CAPTCHA API 사용하기

작성: solvethree읽는 데 3분

HttpClient로 solvethree를 호출하고 작업이 준비될 때까지 기다린 다음 solution.token을 애플리케이션의 CAPTCHA 필드에 넣으세요. Chrome은 대상 페이지를 열기 전에 동일한 프록시와 반환된 브라우저 식별 정보를 사용해야 합니다.

아래 예제는 .NET 8과 Selenium Chrome을 사용해 직접 소유한 폼을 제출합니다. 공식 C# SDK나 Selenium 플러그인이 아닌 직접 REST 통합 방식입니다.

이 페이지의 내용

프로젝트 및 대상 준비

  • 기본 암시적 using이 활성화된 .NET 8 콘솔 프로젝트를 사용하세요. NuGet 패키지 Selenium.WebDriver와 Selenium.Support를 모두 버전 4.52.0으로 추가하세요.
  • 로컬에 Chrome을 설치하세요. Selenium Manager가 호환되는 Chrome 드라이버를 제공합니다.
  • 대상 호스트 이름에 대해 워크스페이스에서 사용할 수 있는 활성 사이트 프로필이 있는지 확인하세요. 호스트 이름과 액션을 전송한다고 프로필이 생성되지는 않습니다. 먼저 사이트 프로필 설정 안내를 따르세요.
  • URL에 인증 정보가 없는 IP 허용 목록 등록 프록시를 사용하세요. 예제는 동일한 프록시를 solvethree와 Chrome에 전달합니다.
예제에서 사용하는 환경 변수
예제에서 사용하는 환경 변수
변수입력할 값
SOLVETHREE_API_KEY워크스페이스 API 키입니다. 페이지가 아닌 자동화를 실행하는 신뢰할 수 있는 컴퓨터에 보관하세요.
SOLVETHREE_PROXY해결 작업과 브라우저 제출에 모두 사용하는 IP 허용 목록 등록 프록시 URL입니다.
TARGET_URL애플리케이션 폼 페이지의 전체 URL입니다. 예제는 여기서 호스트 이름을 추출해 작업의 site에 사용합니다.

통합에서 다른 액션을 사용한다면 예제의 action을 login에서 변경하세요. 기본 recaptchaType인 enterprise-v3를 사용하므로 작업 요청에 type이나 url 필드가 필요하지 않습니다.

작업 생성 및 폼 제출

API 헬퍼는 워크스페이스 키로 인증하고 성공 페이로드를 파싱하기 전에 성공이 아닌 HTTP 응답을 거부합니다. 프로그램은 작업 하나를 생성하고 상태가 pending 또는 solving인 동안 해당 ID를 폴링하며, 작업이 ready가 된 후에만 Chrome을 시작합니다.

C# · 작업 생성 및 직접 소유한 폼 제출
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Support.UI;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json.Nodes;

var key = Environment.GetEnvironmentVariable("SOLVETHREE_API_KEY")
    ?? throw new Exception("Set SOLVETHREE_API_KEY");
var proxy = Environment.GetEnvironmentVariable("SOLVETHREE_PROXY")
    ?? throw new Exception("Set SOLVETHREE_PROXY");
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", key);
using var deadline = new CancellationTokenSource(TimeSpan.FromSeconds(180));

async Task<JsonObject> Api(HttpMethod method, string path, JsonObject? body = null)
{
    using var request = new HttpRequestMessage(method, "https://api.solvethree.com/v1" + path);
    if (body is not null)
        request.Content = new StringContent(body.ToJsonString(), Encoding.UTF8, "application/json");
    using var response = await http.SendAsync(request, deadline.Token);
    response.EnsureSuccessStatusCode();
    return JsonNode.Parse(await response.Content.ReadAsStringAsync(deadline.Token))!.AsObject();
}

var requestId = Guid.NewGuid().ToString(); // Save with these inputs for recovery.
var input = new JsonObject {
    ["site"] = new Uri(Environment.GetEnvironmentVariable("TARGET_URL") ?? throw new Exception("Set TARGET_URL")).Host, ["action"] = "login",
    ["proxy"] = proxy, ["requestId"] = requestId
};
var task = await Api(HttpMethod.Post, "/tasks?waitMs=0", input);
while (task["status"]!.GetValue<string>() is "pending" or "solving")
{
    var id = Uri.EscapeDataString(task["id"]!.GetValue<string>());
    task = await Api(HttpMethod.Get, "/tasks/" + id + "?waitMs=15000");
    if (task["status"]!.GetValue<string>() is "pending" or "solving")
        await Task.Delay(1000, deadline.Token);
}
if (task["status"]!.GetValue<string>() != "ready")
    throw new Exception("Task failed: " + task["errorCode"]?.GetValue<string>()
        + ": " + task["error"]?.GetValue<string>());
var solution = task["solution"]!.AsObject();
// Use solution["token"], ["userAgent"] and ["secChUa"] with the original proxy.
Console.WriteLine("Ready task: " + task["id"]!.GetValue<string>());
var target = Environment.GetEnvironmentVariable("TARGET_URL") ?? throw new Exception("Set TARGET_URL");
if (!string.IsNullOrEmpty(new Uri(proxy).UserInfo)) throw new Exception("Use an IP-allowlisted proxy");
var options = new ChromeOptions();
options.AddArgument("--proxy-server=" + proxy);
options.AddArgument("--user-agent=" + solution["userAgent"]!.GetValue<string>());
using var browser = new ChromeDriver(options);
browser.ExecuteCdpCommand("Network.enable", new Dictionary<string, object>());
browser.ExecuteCdpCommand("Network.setExtraHTTPHeaders", new Dictionary<string, object> {
    ["headers"] = new Dictionary<string, object> { ["sec-ch-ua"] = solution["secChUa"]!.GetValue<string>() }
});
browser.Navigate().GoToUrl(target);
var wait = new WebDriverWait(browser, TimeSpan.FromSeconds(20));
var field = wait.Until(driver => driver.FindElement(By.Name("g-recaptcha-response")));
browser.ExecuteScript("arguments[0].value=arguments[1]; arguments[0].dispatchEvent(new Event('input',{bubbles:true}));",
    field, solution["token"]!.GetValue<string>());
browser.FindElement(By.CssSelector("button[type=submit]")).Click();
wait.Until(driver => driver.FindElement(By.Id("success"))); // Your application's success marker.

30초의 HTTP 시간 제한, 180초의 API 제한 시간, 20초의 Selenium 대기는 이 예제의 설정이지 서비스 시간 보장이 아닙니다. 애플리케이션의 시간 제한 정책에 맞게 조정하세요.

애플리케이션에 맞게 전달 방식 조정

  1. 1.

    프록시와 반환된 식별 정보를 함께 유지

    ChromeOptions는 원래 프록시와 solution.userAgent를 적용합니다. CDP 명령은 페이지를 열기 전에 네트워킹을 활성화하고 solution.secChUa의 값으로 sec-ch-ua를 설정합니다. 이 순서를 지키세요. 페이지를 먼저 열고 나서 식별 정보를 바꾸지 마세요.

  2. 2.

    애플리케이션이 읽는 위치에 토큰 기록

    예제는 g-recaptcha-response라는 필드를 찾아 solution.token을 할당하고 입력 이벤트를 발생시킵니다. 애플리케이션이 다른 곳에서 토큰을 읽는다면 이 전달 방식을 바꾸세요. 필드 채우기는 전송되는 애플리케이션 요청에 그 값이 실제로 포함될 때만 유효합니다.

  3. 3.

    제출 및 성공 선택자 교체

    예제는 button[type=submit]을 클릭하고 ID가 success인 요소를 기다립니다. 실제 애플리케이션의 선택자를 사용하고, 단순히 버튼을 클릭했다는 것이 아니라 제출한 작업이 성공했음을 확인하는 성공 표시를 선택하세요.

작업이 준비되었다는 것은 솔루션을 사용할 수 있다는 뜻입니다. 제출 후 확인은 별개입니다. 애플리케이션 요청을 수락할지는 여전히 대상이 결정합니다.

불필요한 작업을 생성하지 않고 실패 처리

API 헬퍼가 HTTP 응답을 받고 예외를 발생시킵니다.

HTTP 상태와 오류 응답을 검사하세요. 이는 작업이 failed 상태에 도달하는 것과 다릅니다.

다음을 시도해 보세요: 재시도하기 전에 보고된 요청 또는 인증 문제를 수정하세요. 잘못된 입력을 수정 없이 계속 재전송하지 마세요.

작업이 failed 상태가 됩니다.

errorCode와 error를 확인하세요. site-unreachable은 발생할 수 있는 작업 오류 중 하나입니다.

다음을 시도해 보세요: 해당 작업의 폴링을 중단하고 보고된 원인을 해결하세요. 실패한 해결 작업은 환불됩니다.

작업은 준비되었지만 대상이 제출을 거부합니다.

먼저 전송되는 애플리케이션 요청을 검사하세요. 작업 ID나 이전 토큰이 아닌 solution.token이 포함되어 있나요? 그런 다음 프록시, 사용자 에이전트, 클라이언트 힌트, 대상 URL을 확인하세요.

다음을 시도해 보세요: 다른 해결 작업에 비용을 지불하기 전에 토큰 전달 방식이나 식별 정보 불일치를 수정하세요.

제출 버튼을 클릭한 후 Selenium에서 시간 초과가 발생합니다.

페이지와 애플리케이션 응답을 검사하세요. success 선택자는 예제용으로 지정한 값이지 모든 환경에 통용되는 수락 신호가 아닙니다.

다음을 시도해 보세요: 애플리케이션의 실제 성공 조건으로 교체하거나 애플리케이션이 반환한 거부 응답을 처리하세요.

예제는 requestId를 생성하지만 영구 저장하지는 않습니다. 시간 초과 복구가 필요하다면 생성 전에 정확한 작업 입력과 함께 저장하세요. 생성 요청이 시간 초과되면 동일한 ID와 동일한 입력을 다시 전송해 기존 작업을 복구하세요. 응답을 받지 못했다는 이유만으로 새 ID를 생성하지 마세요. 다른 입력으로 ID를 재사용하면 충돌이 발생합니다. 이미 작업 ID가 있다면 해당 작업을 조회하세요.

첫 작업을 실행하세요.

워크스페이스 크레딧 $5로 시작하세요.

워크스페이스 만들기
C#에서 Selenium으로 CAPTCHA API 사용하기 · solvethree