Pourquoi une requête proxy a épuisé ses tentatives

Le problème

Un appel POST /api/proxy/ renvoie une erreur et aucune donnée. Le message est court et a toujours la même structure :

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

Cette phrase se lit de la même façon, que chaque sortie ait été bloquée, que chaque sortie ait été inactive, ou que FourA ait récupéré la vraie page à presque chaque tentative et que vos propres règles validate l'aient rejetée. Ces trois cas nécessitent des correctifs opposés.

La réponse : attemptReport

Chaque appel Proxy ayant exécuté ses tentatives et échoué contient un objet attemptReport à côté de l'erreur, y compris lorsqu'il s'est terminé par "error": "Timeout". Un nom d'hôte non résolu échoue avant toute tentative, avec status: 0 et sans rapport. Il comptabilise ce que les tentatives ont réellement rencontré :

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

La chaîne error est délibérément inchangée, afin qu'un client effectuant une correspondance dessus continue de fonctionner. Lisez attemptReport.summary pour obtenir une réponse en une ligne, ou les compteurs si vous souhaitez effectuer des branchements logiques.

Champs

Champ Type Ce qui est compté
total integer Tentatives effectuées
noResponse integer Le point de sortie n'a jamais répondu, ou le site n'a pas répondu à temps
defense integer Le site a répondu et un fournisseur de détection de bots a été identifié sur cette réponse
contentRejected integer HTTP 200, pas de détection de bots, rejeté uniquement par votre validate.data
statusRejected integer Le site a répondu, pas de détection de bots, rejeté par votre validate.status
other integer Réponse reçue, et aucun des cas ci-dessus
vendors string[] Tous les fournisseurs de détection de bots identifiés au cours de la tâche
profilesTried string[] Les profils de navigateur envoyés par la tâche, dans l'ordre de première utilisation. default signifie que la requête est partie exactement comme vous l'avez écrite.
summary string Une phrase générée à partir des compteurs. Convient pour les journaux ou pour un affichage utilisateur.

Interprétation

contentRejected est élevé

Les pages sont bien arrivées. Votre règle validate.data ne correspondait pas à leur contenu.

C'est le cas que vous pouvez corriger vous-même, et c'est celui que tous les autres signaux masquent: les requêtes ressemblent à des échecs dans chaque métrique, alors que FourA délivrait du contenu réel tout le temps. Récupérez la page une fois via POST /api/single/ sans aucun validate, observez ce qui est réellement renvoyé, et réécrivez la règle en conséquence.

Une cause fréquente est une règle unique appliquée à un ensemble de pages qui ne sont pas toutes identiques. Un sélecteur présent sur les pages d'articles mais absent des pages vidéo échouera systématiquement sur une page vidéo, indéfiniment et à coût complet.

statusRejected est élevé

Le site a répondu et votre règle validate.status l'a rejeté. Si ces statuts sont 401, 403, 429 ou 503, le site refuse le client plutôt que d'indiquer que la page n'existe pas. Essayez:

  • Un autre profil de navigateur (browser, os, version sur l'objet request interne)
  • exitCountries si le contenu est restreint par zone géographique
  • POST /api/browser/ si le refus nécessite l'exécution de JavaScript pour être résolu

defense est élevé

Une détection de bots a été identifiée sur les réponses, et vendors indique laquelle. Consultez Contrôles de site pour savoir ce que FourA contourne aujourd'hui et ce qu'il se contente de signaler. Si le fournisseur n'est pas contourné sur cet endpoint, basculez l'appel vers POST /api/browser/ ou POST /api/auto/.

noResponse est élevé

Les points de sortie n'ont pas répondu du tout, aucune information n'a donc été obtenue sur la cible. Augmentez maxTries, augmentez timeout_ms, et vérifiez que l'URL est bien accessible depuis l'Internet public.

other est élevé

Le site a renvoyé un statut, aucune détection de bots n'a été identifiée, et la tentative a échoué pour une autre raison, comme une règle validate.headers. Une cible plus lente que votre timeout_ms est comptabilisée sous noResponse.

Rotation des profils de navigateur

Lorsqu'un site refuse le navigateur envoyé par FourA, Proxy cesse d'insister et essaie une autre famille issue du catalogue public de profils. Cela ne consomme aucune tentative supplémentaire: la rotation modifie ce qu'un retry envoie, mais ne change jamais le fait qu'il ait lieu ou non.

Proxy mémorise également, par site, la dernière famille ayant fonctionné après un refus du profil par défaut. Une requête ultérieure vers ce site peut démarrer directement avec cette famille dès sa première tentative, et FourA passe à la famille suivante dès que le site commence à refuser celle-ci également. Un navigateur mobile n'est mémorisé que pour les sites renvoyant du JSON.

profilesTried vous permet d'observer ce comportement. Une seule entrée signifie que chaque tentative a envoyé le même navigateur: default correspond à la requête telle qu'écrite, et un identifiant de catalogue correspond à la famille que FourA avait déjà mémorisée pour ce site. Plusieurs entrées indiquent que la rotation a eu lieu et que le site a refusé chacune d'elles, ce qui diffère du cas où aucune rotation n'a été effectuée.

Sur une réponse Proxy réussie, un champ profile apparaît uniquement lorsque la rotation a sélectionné un navigateur que vous n'aviez pas demandé:

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

La valeur est un identifiant de catalogue issu de GET /api/profiles. Son absence signifie que la request a été envoyée exactement comme rédigée. Sa présence indique que le navigateur qui a fonctionné n'est pas celui que vous avez saisi, vous devez donc renvoyer cet identifiant via profile lors des appels suivants plutôt que de rejouer celui qui a échoué. Le dashboard Playground gère cela pour vous avec Carry.

Un paramètre explicite profile, browser, os ou version sur votre request n'est jamais écrasé. Il en va de même pour une request comportant votre propre header User-Agent ou Cookie, car une autorisation est liée à la signature qui l'a obtenue.

Lecture dans le code

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", [])))
Mis à jour : 30 septembre 2026