Proxy Request 재시도 횟수가 초과된 이유
문제
POST /api/proxy/ 호출 시 데이터 없이 오류가 반환됩니다. 메시지는 짧으며 항상 동일한 형식입니다.
{
"error": "Download maxTry limit reached",
"total": 34.812,
"request": { "...": "..." }
}
이 문장은 모든 출구가 차단되었든, 모든 출구가 다운되었든, 또는 FourA가 거의 모든 시도에서 실제 페이지를 가져왔으나 사용자 정의 validate 규칙에 의해 폐기되었든 상관없이 동일하게 표시됩니다. 이 세 가지 상황에는 각기 다른 해결책이 필요합니다.
해결 방법: attemptReport
시도를 실행했으나 실패한 모든 Proxy 호출은 오류 외에도 attemptReport 객체를 포함하며, 여기에는 "error": "Timeout"(으)로 종료된 호출도 포함됩니다. 확인되지 않는 호스트 이름은 시도 전에 status: 0(으)로 실패하며 리포트가 생성되지 않습니다. 이 객체는 각 시도에서 실제로 발생한 상황을 집계합니다:
{
"error": "Download maxTry limit reached",
"attemptReport": {
"total": 25,
"noResponse": 0,
"defense": 0,
"contentRejected": 25,
"statusRejected": 0,
"other": 0,
"vendors": [],
"profilesTried": ["default"],
"summary": "25 attempt(s): 25 returned HTTP 200 with no defense present and were rejected only by your validate.data - the page was fetched, your content rule did not match it"
},
"total": 34.812
}
error 문자열은 의도적으로 변경되지 않으므로 이를 매칭하는 클라이언트는 계속 정상 작동합니다. 한 줄 요약은 attemptReport.summary에서 확인하거나, 조건부 분기를 처리할 때는 카운트 값을 확인하십시오.
필드
| 필드 | 타입 | 집계 대상 |
|---|---|---|
total |
integer | 시도 횟수 |
noResponse |
integer | exit 노드에서 응답이 없었거나 사이트가 제시간에 응답하지 않음 |
defense |
integer | 사이트가 응답했으며 해당 응답에서 bot-check 벤더가 감지됨 |
contentRejected |
integer | HTTP 200, 봇 체크 없음, 사용자의 validate.data 설정에 의해서만 거부됨 |
statusRejected |
integer | 사이트가 응답함, 봇 체크 없음, 사용자의 validate.status 설정에 의해 거부됨 |
other |
integer | 응답을 받았으나 위의 어느 항목에도 해당하지 않음 |
vendors |
string[] | 작업 전체에서 감지된 모든 bot-check 벤더 |
profilesTried |
string[] | 작업에서 전송한 브라우저 프로필 목록(최초 사용 순서). default는 작성된 그대로 요청이 전송되었음을 의미합니다. |
summary |
string | 카운트 값으로 생성된 한 문장 요약. 로그에 기록하거나 사용자에게 표시하기에 안전합니다. |
데이터 해석하기
contentRejected 값이 높은 경우
페이지는 수신되었습니다. 사용자의 validate.data 규칙이 해당 페이지와 일치하지 않았습니다.
이 문제는 직접 해결할 수 있는 영역이며, 다른 모든 지표가 가리고 있는 부분이기도 합니다. 즉, 모든 메트릭에서는 요청 실패로 표시되지만 FourA는 실제로 정상 콘텐츠를 계속 전달하고 있었습니다. validate 없이 POST /api/single/를 통해 페이지를 한 번 가져와 실제 응답 내용을 확인한 후, 그에 맞게 규칙을 다시 작성하십시오.
흔한 원인 중 하나는 서로 구성이 다른 페이지 세트에 단일 규칙을 적용한 경우입니다. 기사 페이지에는 존재하지만 동영상 페이지에는 없는 선택자는 동영상 페이지에 도달할 때마다 매번 실패하며 비용이 계속 발생합니다.
statusRejected 값이 높은 경우
사이트가 응답했으나 사용자의 validate.status 규칙에 의해 거부되었습니다. 해당 상태 코드가 401, 403, 429 또는 503인 경우 사이트에 페이지가 없는 것이 아니라 클라이언트를 거부하고 있는 것입니다. 다음을 시도하십시오:
- 다른 브라우저 프로필 사용(내부
request객체의browser,os,version) - 콘텐츠가 특정 지역으로 제한된 경우
exitCountries적용 - 거부 해제를 위해 JavaScript 실행이 필요한 경우
POST /api/browser/적용
defense 값이 높은 경우
응답에서 봇 체크가 감지되었으며, vendors 항목에 감지된 벤더가 명시됩니다. FourA가 현재 해결 가능한 항목과 단순 보고만 제공하는 항목은 Site checks에서 확인하십시오. 해당 벤더가 이 endpoint에서 자동 해결되지 않는 대상이라면 호출을 POST /api/browser/ 또는 POST /api/auto/(으)로 전환하십시오.
noResponse 값이 높은 경우
exit 노드가 전혀 응답하지 않아 타깃에 대한 정보를 파악하지 못했습니다. maxTries 값을 늘리고, timeout_ms 값을 늘린 후, 해당 URL이 공용 인터넷에서 정상 확인되는지 점검하십시오.
other 값이 높은 경우
사이트가 상태 코드로 응답했고 봇 체크는 감지되지 않았으며, validate.headers 규칙 등 다른 원인으로 시도가 실패했습니다. 설정한 timeout_ms보다 느린 대상은 noResponse(으)로 집계됩니다.
브라우저 프로필 로테이션
사이트가 FourA가 보낸 브라우저를 거부하면 Proxy는 해당 브라우저를 고집하지 않고 공개 프로필 카탈로그의 다른 패밀리를 시도합니다. 추가 시도 횟수가 소모되지 않습니다. 로테이션은 재시도 시 전송되는 내용을 변경할 뿐, 재시도 발생 여부 자체를 바꾸지 않습니다.
또한 Proxy는 사이트가 기본 브라우저를 거부한 후 해당 사이트에서 마지막으로 작동했던 패밀리를 사이트별로 기억합니다. 이후 해당 사이트로 전송되는 request는 첫 번째 시도부터 해당 패밀리로 시작할 수 있으며, 사이트가 이 패밀리마저 거부하기 시작하면 FourA는 다음 패밀리로 넘어갑니다. 모바일 브라우저는 JSON으로 응답하는 사이트에 대해서만 기억됩니다.
profilesTried 항목을 통해 이 동작 과정을 확인할 수 있습니다. 항목이 하나라면 모든 시도에서 동일한 브라우저를 전송했음을 의미합니다. default는 작성된 그대로의 request이며, 카탈로그 ID는 FourA가 해당 사이트에 대해 이미 학습한 패밀리입니다. 항목이 여러 개라면 로테이션이 실행되었으나 사이트가 각각을 모두 거부했음을 의미하며, 이는 로테이션을 전혀 실행하지 않은 상태와는 다른 상황입니다.
성공적인 Proxy response에서는 로테이션이 요청하지 않은 브라우저를 선택한 경우에만 profile 필드가 나타납니다.
{
"status": 200,
"data": "<!doctype html>...",
"proxy": "A1B2C3",
"profile": "...",
"total": 4.108
}
이 값은 GET /api/profiles의 카탈로그 ID입니다. 값이 없으면 request가 작성된 그대로 전송되었음을 의미합니다. 값이 있으면 작동한 브라우저가 직접 입력한 것과 다르다는 의미이므로, 실패한 브라우저를 재시도하는 대신 후속 호출에서 해당 ID를 profile로 다시 전달하십시오. 대시보드 Playground에서는 Carry를 통해 이 작업을 자동으로 처리합니다.
request에 명시된 profile, browser, os 또는 version은 절대 재정의되지 않습니다. 클리어런스는 이를 획득한 시그니처에 바인딩되므로, 자체 User-Agent 또는 Cookie header가 포함된 request 역시 재정의되지 않습니다.
코드에서 읽기
import requests
API = "https://eu.api.foura.ai"
H = {"X-API-Key": "YOUR_API_KEY", "Content-Type": "application/json"}
r = requests.post(f"{API}/api/proxy/", headers=H, json={
"maxTries": 5,
"request": {
"method": "GET",
"url": "https://example.com/product/42",
"validate": {"data": {"accept": ["Add to cart"]}},
},
}).json()
if "error" in r:
rep = r.get("attemptReport", {})
print(rep.get("summary", r["error"]))
if rep.get("contentRejected", 0) > rep.get("total", 0) / 2:
# The pages arrived. The validate rule is what threw them away.
raise SystemExit("validate.data did not match the real page")
if rep.get("defense", 0):
print("bot check met:", ", ".join(rep.get("vendors", [])))
관련 문서
- API Endpoints: Proxy 요청 및 응답 전체 레퍼런스
- Site checks: 벤더,
defense및 통과 세션 재사용 - Request Outcomes: 거부된 응답의 분류 및 과금 방식
- Common Issues: 기타 문제 및 해결 방법
- Choosing the Right Endpoint: Proxy 사용이 적절하지 않은 경우