# Wykonywanie równoległych requestów

> URL: https://foura.ai/pl/docs/how-to/run-requests-in-parallel
> Updated: 2026-10-15
> Index: https://foura.ai/llms.txt

## Czego się dowiesz

Jak uruchamiać dużą partię żądań FourA współbieżnie bez otrzymywania błędów 429, ograniczając liczbę otwartych wywołań i reagując na odrzucenie zamiast jego powtarzania.

## Wymagania wstępne

- Klucz FourA API ([uzyskaj go tutaj](https://foura.ai/docs/getting-started/authentication))
- Python 3.9+ z `requests` lub Node.js 18+

## Limity, z którymi pracujesz

Twój plan nakłada dwa limity na dany endpoint: ile żądań może być przetwarzanych jednocześnie oraz ile może wystartować w ciągu minuty (Browser ma limit dzienny zamiast minutowego). Single, Proxy i Browser mają własne limity, a zakładka **Limits & Features** na stronie [Usage & Limits](https://foura.ai/docs/dashboard/quota) zawiera ich listę.

Żądanie, które przekracza limit współbieżności, zwraca kod HTTP 429 z `X-FourA-Limit: plan_limit_concurrency` oraz wartością limitu w treści odpowiedzi:

```json
{
  "error": "Concurrency limit reached: your plan allows 50 simultaneous single request(s). Retry when an in-flight request finishes.",
  "reason": "plan_limit_concurrency",
  "documentation": "https://foura.ai/prices",
  "limit": 50,
  "in_flight": 51,
  "retry_after_seconds": 1
}
```

Żądanie przekraczające limit na minutę zwraca `X-FourA-Limit: plan_limit_rate`, a nagłówek `retry_after_seconds` określa czas oczekiwania na możliwość wysłania kolejnego żądania (od 1 do 120 sekund).

W obu przypadkach odrzucenie następuje natychmiastowo. FourA nie kolejkuje wywołań ani nie przetwarza ich z opóźnieniem, więc środki nie są pobierane ani fakturowane. Odrzucone wywołanie nie wlicza się do ruchomego okna minutowego, dzięki czemu konto nieznacznie przekraczające limit nadal wykorzystuje pełną dostępną przepustowość. Zjawisko retry storm jest obsługiwane osobno: gdy odrzucone wywołania nadal napływają z częstotliwością ponad dwukrotnie przewyższającą limit na minutę, każde wywołanie na tym endpoincie jest odrzucane z `cooldown: true` oraz `retry_after_seconds: 30`, dlatego należy wstrzymać ruch na 30 sekund. Pełna dokumentacja pól: [Rate Limits](https://foura.ai/docs/api/rate-limits).

Dwa istotne elementy, które często bywają pomijane:

- Samo wywołanie `POST /api/auto/` nie blokuje slotu, ale każde wywołanie podrzędne Single, Proxy i Browser, które wykonuje w Twoim imieniu, już tak. Jedno wywołanie auto może zajmować więcej niż jeden slot w trakcie działania drabiny eskalacji. (W panelu wywołanie auto widnieje jako request, a jego wywołania podrzędne jako próby).
- Żądanie Browser zajmuje slot przez cały czas renderowania strony, co trwa znacznie dłużej niż w przypadku żądania Single. Seria wywołań browser wyczerpie limit przy mniejszej liczbie żądań niż seria wywołań single.

## Krok 1: Ogranicz własną współbieżność

Wybierz wartość poniżej limitu dla wywoływanego endpointu i jej nie przekraczaj. W puli workerów wystarczy do tego jedna linijka kodu:

```python
import os
import concurrent.futures
import requests

API = "https://eu.api.foura.ai/api/single/"
KEY = os.environ["FOURA_API_KEY"]
HEADERS = {"X-API-Key": KEY, "Content-Type": "application/json"}

# Below your plan's Single concurrency, so a slow request never pushes the batch over it.
MAX_IN_FLIGHT = 30

def fetch_one(url):
    resp = requests.post(API, headers=HEADERS, json={"method": "GET", "url": url}, timeout=60)
    return url, resp.status_code, resp.json()

def fetch_all(urls):
    results = []
    with concurrent.futures.ThreadPoolExecutor(max_workers=MAX_IN_FLIGHT) as pool:
        for outcome in concurrent.futures.as_completed(pool.submit(fetch_one, u) for u in urls):
            results.append(outcome.result())
    return results
```

`max_workers` to cały mechanizm. Pula nigdy nie ma otwartych więcej wywołań niż ta wartość, więc wsad może liczyć milion adresów URL i nadal mieścić się w limicie.

Zostaw margines. Jeśli dwa procesy po Twojej stronie korzystają z tego samego konta, nawet z różnymi kluczami API, dzielą jeden wspólny limit, więc przydziel każdemu z nich połowę. Jeśli szybki cel pozwala puli kończyć żądania szybciej, niż pozwala na to minuta, dostosuj tempo workerów, aby utrzymać się także poniżej limitu minutowego.

## Krok 2: Zastosuj backoff zamiast ponawiać wysyłkę

Jeśli nadejdzie odmowa, złym rozwiązaniem jest natychmiastowe ponowne wysłanie wsadu. Każde wywołanie w nim zostanie ponownie odrzucone, a ponowienia nałożą się na żądania, które już były w toku.

Najpierw odczekaj. Czas oczekiwania znajduje się w nagłówku `Retry-After` oraz w `retry_after_seconds` w treści odpowiedzi:

```python
import time

# Plan limits that no short wait will clear.
STOP_ON = {"plan_limit_browser_daily", "plan_limit_credits", "plan_limit_bandwidth", "plan_limit_feature", "plan_limit_premium"}

def fetch_one(url, attempts=4):
    for attempt in range(attempts):
        resp = requests.post(API, headers=HEADERS, json={"method": "GET", "url": url}, timeout=60)

        limit = resp.headers.get("X-FourA-Limit")
        if limit in STOP_ON:
            raise RuntimeError(f"stopped by {limit}")

        if resp.status_code not in (429, 503):
            return url, resp.status_code, resp.json()

        header = resp.headers.get("Retry-After")
        if header and header.isdigit():
            wait = int(header)
        else:
            body = resp.json()
            wait = body.get("retry_after_seconds") or body.get("retryAfter") or 2 ** attempt
        time.sleep(wait)

    return url, 429, {"error": "still refused after retries"}
```

Dodaj jitter, gdy uruchamiasz wielu workerów. Bez niego każdy worker odrzucony w tej samej sekundzie wznawia działanie w tej samej sekundzie i ponownie zostaje odrzucony razem z innymi.

```python
import random
time.sleep(wait + random.uniform(0, 0.5))
```

## Krok 3: To samo w Node

```javascript
const API = 'https://eu.api.foura.ai/api/single/';
const HEADERS = {
  'X-API-Key': process.env.FOURA_API_KEY,
  'Content-Type': 'application/json',
};
const MAX_IN_FLIGHT = 30;

async function fetchOne(url) {
  const resp = await fetch(API, {
    method: 'POST',
    headers: HEADERS,
    body: JSON.stringify({ method: 'GET', url }),
  });
  return { url, status: resp.status, body: await resp.json() };
}

async function fetchAll(urls) {
  const queue = [...urls];
  const results = [];

  async function worker() {
    while (queue.length) {
      results.push(await fetchOne(queue.pop()));
    }
  }

  await Promise.all(
    Array.from({ length: Math.min(MAX_IN_FLIGHT, urls.length) }, worker)
  );
  return results;
}
```

Stała liczba workerów pobierających zadania z jednej kolejki utrzymuje dokładnie tyle otwartych wywołań, niezależnie od wielkości partii.

## Krok 4: Obserwuj rzeczywiste zużycie

Otwórz **Usage & Limits** w [dashboardzie](https://foura.ai/dashboard). Liczniki na żywo obok limitów pokazują współbieżność, limit na minutę oraz liczbę requestów przeglądarkowych na dzień w czasie rzeczywistym, dzięki czemu możesz dobrać `MAX_IN_FLIGHT` na podstawie rzeczywistego przebiegu, a nie domysłów.

[Activity Log](https://foura.ai/docs/dashboard/activity-log) rejestruje również odrzucenia. Przebieg, który zakończył się prawidłową liczbą wierszy, ale z rozproszonymi wynikami `rate_limit`, sam nakładał na siebie ograniczenia.

## Częste błędy

- **Wysyłanie całej listy naraz.** `asyncio.gather` na 5 000 adresów URL lub `Promise.all` na nieograniczonej tablicy otwiera 5 000 wywołań. Ogranicz pulę, a nie listę.
- **Ponawianie prób równolegle.** Ponowne wysyłanie każdego odrzuconego wywołania w momencie jego odrzucenia odtwarza skok ruchu, który spowodował odmowę. Odczekaj czas wskazany w `Retry-After` i dodaj jitter.
- **Traktowanie limitu dziennego lub okresowego jako zwykłego oczekiwania.** `plan_limit_browser_daily` resetuje się o północy UTC, a `plan_limit_credits` i `plan_limit_bandwidth` na koniec okresu rozliczeniowego. Zatrzymaj przebieg i odczytaj `resets_at` z treści odpowiedzi, jeśli jest dostępny.
- **Liczenie wywołań auto jako pojedynczego slotu.** Mechanizm ladder wewnątrz `/api/auto/` wykonuje rzeczywiste podwywołania i to one wliczają się do limitów.
- **Dzielenie klucza między procesy bez podziału budżetu.** Limity są przypisane do konta, a nie do procesu.
- **Dobieranie jednej puli dla wszystkich endpointów.** Single, Proxy i Browser mają własne limity współbieżności. Pula dobrana pod Single przekroczy mniejszy limit Browser.

## Powiązane

- [Rate Limits](https://foura.ai/docs/api/rate-limits): Wszystkie formaty odrzuceń i znaczenie każdego pola
- [Response Headers](https://foura.ai/docs/api/response-headers): `X-FourA-Limit` i `Retry-After`
- [Request Outcomes](https://foura.ai/docs/api/outcomes): Dlaczego wynik `rate_limit` nigdy nie jest rozliczany
- [Usage & Limits](https://foura.ai/docs/dashboard/quota): Gdzie znajdują się liczniki na żywo i limity Twojego planu
- [Smart Fetch (Auto)](https://foura.ai/docs/guides/smart-fetch): Jak jedno wywołanie auto przekształca się w kilka podwywołań
