Warum ein Proxy-Request keine Versuche mehr übrig hat
Das Problem
Ein POST /api/proxy/-Aufruf liefert einen Fehler und keine Daten zurück. Die Meldung ist kurz und hat immer dieselbe Struktur:
{
"error": "Download maxTry limit reached",
"total": 34.812,
"request": { "...": "..." }
}
Dieser Satz liest sich genau gleich, egal ob jeder Exit blockiert war, jeder Exit tot war oder FourA die echte Seite bei fast jedem Versuch abgerufen hat und deine eigenen validate-Regeln sie verworfen haben. Diese drei Fälle erfordern jeweils völlig andere Lösungen.
Die Antwort: attemptReport
Jede fehlgeschlagene Proxy-Response enthält neben dem Fehler ein attemptReport-Objekt. Es zählt, worauf die Versuche tatsächlich gestoßen sind:
{
"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
}
Der error-String bleibt bewusst unverändert, damit ein Client, der darauf matcht, weiterhin funktioniert. Lies attemptReport.summary für eine einzeilige Antwort oder die Zähler, wenn du darauf verzweigen möchtest.
Fields
| Field | Type | What it counts |
|---|---|---|
total |
integer | Unternommene Versuche |
noResponse |
integer | Der Exit hat nie geantwortet, die Site wurde also nie erreicht |
defense |
integer | Die Site hat geantwortet und ein Bot-Check-Anbieter wurde in dieser Antwort erkannt |
contentRejected |
integer | HTTP 200, kein Bot-Check, nur durch dein validate.data abgelehnt |
statusRejected |
integer | Die Site hat geantwortet, kein Bot-Check, durch dein validate.status abgelehnt |
other |
integer | Geantwortet, und keines der obigen Kriterien |
vendors |
string[] | Jeder Bot-Check-Anbieter, der irgendwo im Task erkannt wurde |
profilesTried |
string[] | Die vom Task gesendeten Browser-Profile in der Reihenfolge der ersten Verwendung. default bedeutet, dass der Request genau so gesendet wurde, wie du ihn geschrieben hast. |
summary |
string | Ein aus den Zählern erstellter Satz. Sicher zum Loggen oder für die Anzeige für Nutzer. |
Reading It
contentRejected is high
Die Seiten sind angekommen. Deine validate.data-Regel hat nicht darauf gematcht.
Das kannst du selbst beheben, und es ist der Fall, den jedes andere Signal verbirgt: Die Requests sehen in jeder Metrik wie Fehler aus, obwohl FourA die ganze Zeit echte Inhalte geliefert hat. Rufe die Seite einmal über POST /api/single/ ganz ohne validate ab, sieh dir an, was tatsächlich zurückkommt, und schreibe die Regel passend dazu um.
Eine häufige Ursache ist eine einzelne Regel für mehrere Seiten, die nicht identisch aufgebaut sind. Ein Selektor, der auf Artikelseiten existiert, aber nicht auf Videoseiten, schlägt jedes Mal fehl, wenn er auf einer Videoseite landet, dauerhaft und bei vollen Kosten.
statusRejected is high
Die Site hat geantwortet und deine validate.status-Regel hat abgelehnt. Wenn diese Statuscodes 401, 403, 429 oder 503 sind, weist die Site den Client ab, anstatt zu melden, dass die Seite nicht existiert. Versuche:
- Ein anderes Browser-Profil (
browser,os,versionim innerenrequest-Objekt) exitCountries, falls der Inhalt regional gesperrt istPOST /api/browser/, falls die Ablehnung JavaScript zur Freigabe erfordert
defense is high
Ein Bot-Check wurde in den Antworten erkannt, und vendors benennt den Anbieter. Siehe Anti-Bot Defenses dafür, was FourA derzeit umgeht und was es nur meldet. Wenn der Anbieter an diesem Endpoint nicht umgangen wird, verlagere den Aufruf auf POST /api/browser/ oder POST /api/auto/.
noResponse is high
Die Exits haben überhaupt nicht geantwortet, daher liegen keine Erkenntnisse über das Ziel vor. Erhöhe maxTries, erhöhe timeout_ms und prüfe, ob die URL aus dem öffentlichen Internet auflösbar ist.
other is high
Geantwortet und in keine der obigen Kategorien eingestuft. Prüfe total_time im Vergleich zu deinem timeout_ms: Ein Ziel, das langsamer als dein Budget ist, landet hier.
Browser Profile Rotation
Wenn eine Website den von FourA gesendeten Browser ablehnt, beharrt Proxy nicht darauf, sondern versucht es mit einer anderen Familie aus dem öffentlichen Profilkatalog. Das kostet keinen zusätzlichen Versuch: Die Rotation ändert, was ein Retry sendet, nicht, ob einer stattfindet.
profilesTried zeigt dir, wie das abläuft. Ein Eintrag bedeutet, dass der Request jedes Mal wie vorgegeben gesendet wurde. Mehrere Einträge bedeuten, dass die Rotation lief und die Website jeden davon abgelehnt hat. Das ist ein anderer Fall, als wenn überhaupt keine Rotation stattgefunden hätte.
Bei einer erfolgreichen Proxy-Response erscheint ein profile-Feld nur dann, wenn die Rotation einen Browser gewählt hat, den du nicht angefordert hast:
{
"status": 200,
"data": "<!doctype html>...",
"proxy": "A1B2C3",
"profile": "...",
"total": 4.108
}
Der Wert ist eine Katalog-ID aus GET /api/profiles. Fehlt er, wurde der Request exakt wie angegeben gesendet. Ist er vorhanden, war der funktionierende Browser nicht der von dir eingegebene. Übergib diese ID bei Folgeaufrufen als profile, anstatt den fehlgeschlagenen erneut zu senden. Das Dashboard Playground erledigt das mit Carry automatisch für dich.
Ein explizites profile, browser, os oder version in deinem Request wird niemals überschrieben. Dasselbe gilt für Requests mit deinem eigenen User-Agent- oder Cookie-Header, da eine Clearance an die Signatur gebunden ist, die sie erhalten hat.
Im Code auslesen
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", [])))
Verwandte Themen
- API Endpoints: Die vollständige Proxy Request- und Response-Referenz
- Anti-Bot Defenses: Anbieter,
defenseund das erneute Abspielen einer Clearance - Request Outcomes: Wie eine abgewiesene Response klassifiziert und abgerechnet wird
- Common Issues: Weitere Probleme und deren Behebung
- Choosing the Right Endpoint: Wann Proxy das falsche Tool ist