Scarnox · Blog

빠른 캡차 풀이: 파이프라인의 지연 시간 줄이기

빠른 캡차 솔버는 올바른 순간에, 병렬로, 요청을 낭비하지 않고 호출할 수 있는 솔버입니다. Scarnox 풀이 호출은 토큰이 준비될 때 반환되므로, 소요 시간은 캡차 유형, 대상 사이트, 현재 부하에 따라 달라집니다. 이 가이드는 고정된 시간을 약속하지 않습니다. 대신 여러분의 코드에서 통제할 수 있는 것, 측정하는 방법, 많은 풀이를 동시에 실행하는 방법을 보여 줍니다. 그래서 실제로 쓰는 시간이 잘 쓰이도록 하는 것이 목표입니다.

풀이 시간을 결정하는 요소

세 가지가 중요하며, 그중 여러분의 손에 있는 것은 하나뿐입니다.

노력은 세 번째 항목에 집중하세요. 측정하고 바꿀 수 있는 부분이 바로 그곳입니다.

최적화하기 전에 측정하기

호출을 타이머로 감싸고 결과를 기록하세요. 풀이 시간과, 그 주변에서 코드가 쓰는 시간을 알고 싶습니다.

import time
import requests

API_KEY = "scarnox_YOURAPIKEY"
URL = "https://api.scarnox.com/api/tasks/create"

def timed_solve(sitekey: str, page_url: str) -> dict:
    started = time.perf_counter()
    r = requests.post(
        URL,
        headers={"Authorization": f"Bearer {API_KEY}"},
        json={
            "captcha_type": "PopularCaptchaTaskProxyless",
            "site_key": sitekey,
            "site_url": page_url,
        },
        timeout=185,
    )
    elapsed = time.perf_counter() - started
    data = r.json()
    if data.get("errorId") != 0:
        return {"ok": False, "error": data.get("errorCode"), "seconds": elapsed}
    return {"ok": True, "token": data["solution"]["token"], "seconds": elapsed}

result = timed_solve("10000000-ffff-ffff-ffff-000000000001", "https://example.com/signup")
print(result["ok"], round(result["seconds"], 2), "s")

실제 작업을 돌릴 시간대에 관심 있는 페이지에 대해 이것을 실행하세요. 숫자를 기록해 두세요. 변경이 실제로 도움이 되었는지 알려 줍니다.

병렬로 풀이하기

풀이들은 서로 독립적입니다. sitekey를 열 개 처리해야 한다면, 하나씩 차례로 기다리는 것은 다른 것을 할 수 있었던 시간을 낭비합니다. 각 풀이는 대부분의 시간을 기다리며 보내는 HTTP 요청 하나이므로, 대부분의 스크립트에는 스레드 풀이면 충분합니다.

from concurrent.futures import ThreadPoolExecutor

jobs = [
    ("10000000-ffff-ffff-ffff-000000000001", "https://example.com/signup"),
    ("10000000-ffff-ffff-ffff-000000000001", "https://example.com/register"),
    ("10000000-ffff-ffff-ffff-000000000001", "https://example.com/contact"),
]

def run(job):
    sitekey, page_url = job
    return timed_solve(sitekey, page_url)

# 작게 시작하세요. 오류가 낮게 유지되는 동안에만 워커 수를 늘리세요.
with ThreadPoolExecutor(max_workers=5) as pool:
    results = list(pool.map(run, jobs))

for r in results:
    status = "ok" if r["ok"] else r["error"]
    print(f"{r['seconds']:.2f} s  {status}")

워커 수를 늘렸을 때 용량이나 제한과 관련된 것처럼 보이는 오류가 나타나면, 워커 수를 줄이세요. 병렬 처리는 서버가 그 속도를 따라갈 때만 도움이 됩니다.

너무 일찍 풀지 않기

가장 빠른 코드는 아직 쓰지 않을 토큰을 기다리지 않는 코드입니다. 토큰을 미리 요청해 저장해 두는 것은 흔한 실수입니다. 토큰은 발급 후 약 2분 뒤에 만료되므로, 저장된 토큰은 필요할 때 이미 오래된 경우가 많습니다. 폼이 막 제출되려는 시점에 풀이하세요. 자세한 이유는 캡차 토큰 만료: 원인과 해결에 있습니다.

나머지 경로는 짧게 유지하기

만료되지 않는 것은 재사용하기

일부 작업은 여러 풀이에 걸쳐 안전하게 공유할 수 있습니다. 다음은 핫 패스에서 빼 두세요.

이런 변경은 비용이 적게 들며, 모든 호출에 나타나는 오버헤드를 없앱니다.

평균이 아니라 꼬리를 측정하기

평균 풀이 시간은 배치를 붙잡는 느린 호출을 가립니다. 각 풀이 시간을 기록한 뒤 중앙값과 95 백분위수를 보세요. 가장 느린 작업만큼 걸리는 파이프라인은 꼬리에 의해 제한되므로, 느린 호출을 재시도하는 방식을 조금 바꾸는 것이 중앙값을 바꾸는 것보다 더 큰 효과를 낼 수 있습니다.

import statistics

def summarise(seconds: list[float]) -> str:
    ordered = sorted(seconds)
    p95 = ordered[int(0.95 * (len(ordered) - 1))]
    return f"median {statistics.median(ordered):.2f} s, p95 {p95:.2f} s, n={len(ordered)}"

print(summarise([r["seconds"] for r in results]))

변경 전후의 요약을 비교하세요. p95가 거의 움직이지 않는다면, 시간은 코드의 다른 곳에서 쓰이고 있는 것입니다.

속도만이 아니라 결과도 확인하기

빠른 실패도 여전히 실패입니다. 오류를 반환한 풀이 수를 세고, 오류율이 올라가면 속도를 낮추라는 신호로 취급하세요. 실패한 풀이는 청구되지 않지만, 그에 쓰인 시간은 여전히 파이프라인이 잃어버린 시간입니다. 재시도 패턴 전체는 캡차 솔버 오류 처리에 있습니다.

다음 단계