Por que uma requisição proxy esgotou as tentativas

O problema

Uma chamada POST /api/proxy/ retorna com um erro e sem dados. A mensagem é curta e sempre no mesmo formato:

{
  "error": "Download maxTry limit reached",
  "total": 34.812,
  "request": { "...": "..." }
}

Essa frase soa idêntica se todas as saídas foram bloqueadas, se todas as saídas estavam inativas ou se o FourA buscou a página real em quase todas as tentativas e suas próprias regras validate a descartaram. Esses três casos exigem correções opostas.

A resposta: attemptReport

Cada chamada de Proxy que esgotou suas tentativas e falhou carrega um objeto attemptReport ao lado do erro, incluindo aquela que terminou com "error": "Timeout". Um nome de host que não resolve falha antes de qualquer tentativa, com status: 0 e nenhum relatório. Ele contabiliza o que as tentativas realmente encontraram:

{
  "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
}

A string error permanece deliberadamente inalterada, para que um client que faz correspondência com ela continue funcionando. Leia attemptReport.summary para obter uma resposta em uma única linha, ou as contagens quando quiser criar ramificações condicionais baseadas nelas.

Campos

Campo Tipo O que conta
total integer Tentativas realizadas
noResponse integer A saída nunca respondeu, ou o site não respondeu a tempo
defense integer O site respondeu e um provedor de verificação de bot foi reconhecido nessa resposta
contentRejected integer HTTP 200, sem verificação de bot, rejeitado apenas pelo seu validate.data
statusRejected integer O site respondeu, sem verificação de bot, rejeitado pelo seu validate.status
other integer Respondeu, e nenhum dos casos acima
vendors string[] Cada provedor de verificação de bot reconhecido em qualquer lugar da tarefa
profilesTried string[] Os perfis de navegador que a tarefa enviou, na ordem de primeiro uso. default significa que a request saiu exatamente como você a escreveu.
summary string Uma frase gerada a partir das contagens. Seguro para registrar em logs ou exibir a um usuário.

Como interpretar

contentRejected está alto

As páginas chegaram. Sua regra validate.data não correspondeu a elas.

Este é o caso que você mesmo pode corrigir, e é o que todos os outros sinais ocultam: as requests parecem falhas em todas as métricas, enquanto a FourA estava entregando conteúdo real o tempo todo. Faça o fetch da página uma vez através de POST /api/single/ sem nenhum validate, veja o que realmente retorna e reescreva a regra com base nisso.

Uma causa comum é uma única regra aplicada a um conjunto de páginas que não são todas iguais. Um seletor que existe em páginas de artigos e não em páginas de vídeos falhará toda vez que atingir uma página de vídeo, permanentemente, com custo total.

statusRejected está alto

O site respondeu e sua regra validate.status recusou a resposta. Se esses status forem 401, 403, 429 ou 503, o site está recusando o client em vez de informar que a página não existe. Tente:

  • Outro perfil de navegador (browser, os, version no objeto interno request)
  • exitCountries se o conteúdo tiver restrição regional
  • POST /api/browser/ se a recusa exigir JavaScript para ser liberada

defense está alto

Uma verificação de bot foi reconhecida nas respostas, e vendors indica qual. Consulte Site checks para ver o que a FourA resolve atualmente e o que ela apenas reporta. Se o provedor não for um dos que são resolvidos neste endpoint, migre a chamada para POST /api/browser/ ou POST /api/auto/.

noResponse está alto

As saídas não responderam, portanto nada foi identificado sobre o destino. Aumente maxTries, aumente timeout_ms e verifique se a URL resolve a partir da internet pública.

other está alto

O site respondeu com um status, nenhuma verificação de bot foi reconhecida e a tentativa falhou por outro motivo, como uma regra validate.headers. Um destino mais lento do que o seu timeout_ms é contabilizado em noResponse.

Rotação de perfis de navegador

Quando um site recusa o navegador enviado pela FourA, o Proxy para de insistir nele e tenta outra família do catálogo de perfis públicos. Isso não consome tentativas adicionais: a rotação altera o que uma nova tentativa envia, nunca se ela acontece ou não.

O Proxy também lembra, por site, a última família que funcionou ali depois que o site recusou o padrão. Uma request futura para esse site pode começar nessa família desde a primeira tentativa, e a FourA avança para a próxima família assim que o site começar a recusar essa também. Um navegador mobile só é lembrado para sites que respondem JSON.

profilesTried é como você vê isso acontecer. Uma única entrada significa que todas as tentativas enviaram o mesmo navegador: default é a request como foi escrita, e um ID de catálogo é a família que a FourA já havia memorizado para aquele site. Múltiplas entradas significam que a rotação foi executada e o site recusou cada uma delas, o que é uma situação diferente de não ter rotacionado.

Em uma response de Proxy bem-sucedida, o campo profile aparece apenas quando a rotação escolheu um navegador que você não solicitou:

{
  "status": 200,
  "data": "<!doctype html>...",
  "proxy": "A1B2C3",
  "profile": "...",
  "total": 4.108
}

O valor é um ID de catálogo de GET /api/profiles. A ausência significa que a request foi enviada exatamente como escrita. A presença significa que o navegador que funcionou não foi o que você digitou, portanto passe esse ID de volta como profile nas chamadas seguintes em vez de repetir o que falhou. O painel Playground faz isso por você com Carry.

Um profile, browser, os ou version explícito na sua request nunca é sobrescrito. O mesmo vale para uma request que contenha seu próprio header User-Agent ou Cookie, pois uma autorização está vinculada à assinatura que a obteve.

Lendo isso no código

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", [])))
Atualizado em: 30 de setembro de 2026