# Batch i crawl API: czytaj wiele URL-i lub całą witrynę

> Dokumentacja Batch i crawl API: przeczytaj do 100 URL-i na zadanie lub witrynę do 10 000 stron, z zachowaniem robots.txt. Bez KYC, płatność kryptowalutą.

- URL: https://webreaderapi.com/pl/docs/crawl/
- Ostatnia aktualizacja: 2026-10-22
- Wydawca: WebReaderAPI (https://webreaderapi.com)

Dwa endpointy uruchamiają zadania w tle: `/batch` dla listy do 100 URL-i, `/crawl` dla jednej witryny. Oba zwracają ID zadania; strony odbierasz z `/jobs/{job_id}`, po jednej stronie wyników naraz.

**Szybka odpowiedź — Jak scrawlować witrynę do Markdown za pomocą WebReaderAPI?**

Wyślij URL startowy do `POST https://webreaderapi.com/api/v1/crawl` z parametrem strony `limit`. **WebReaderAPI** czyta witrynę w tle, tylko w obrębie tej samej domeny, zgodnie z jej sitemap, robots.txt i Crawl-delay, do 10 000 stron na zadanie. Odbieraj strony Markdown z zadania, po jednej stronie wyników naraz. Każde odczytanie strony kosztuje 1 kredyt (2 z przeglądarką), od 0,07 USD/1K stron stan na październik 2026. Bez KYC; płatność kryptowalutą.

## Zadania w skrócie

| Atrybut | Wartość |
| --- | --- |
| Batch | Do 100 URL-i na jedno zadanie wsadowe: `POST https://webreaderapi.com/api/v1/batch` |
| Crawl | Crawling jednej witryny na zadanie, do 10 000 stron, zgodnie z jej sitemapą, jej robots.txt i jej Crawl-delay: `POST https://webreaderapi.com/api/v1/crawl` |
| Wyniki | `GET https://webreaderapi.com/api/v1/jobs/{job_id}`, potem `?cursor=…` po następną stronę wyników |
| Rozliczanie | Każde odczytanie strony jest rozliczane jak wywołanie `/read`; strony pominięte przez robots.txt i nieudane strony nic nie kosztują |
| Zakres crawlu | Jedna domena; linki do innych domen są wymieniane, ale nie czytane |
| Anulowanie zadania | Niedostępne w v1: ustaw `limit` na największą liczbę stron, za którą jesteś gotów zapłacić |

## Uruchom zadanie batch

`POST /api/v1/batch`

Czyta w tle do 100 URL-i z tymi samymi opcjami. Opcje używają nazw i wartości z [/read](https://webreaderapi.com/pl/docs/read/). Odpowiedzią jest ID zadania; wartości są przykładowe.

Uwierzytelnianie: klucz API w nagłówku `Authorization: Bearer API_KEY`.

**Treść zapytania**

| Pole | Typ | Wymagane | Opis |
| --- | --- | --- | --- |
| `urls` | array of strings | Tak | Od 1 do 100 adresów URL http lub https. Duplikaty są odczytywane raz. |
| `options` | object | Nie | Dowolny parametr `/read` oprócz `url`: `format`, `main_content`, `render`, `include_links`, `include_images`, `country`, `max_chars`, `timeout`. |

*Przykład: three articles as Markdown*

```json
{
  "urls": [
    "https://example.com/blog/post-1",
    "https://example.com/blog/post-2",
    "https://example.org/news/launch"
  ],
  "options": {
    "format": "markdown",
    "max_chars": 50000
  }
}
```

**Odpowiedź** `202 Accepted`

```json
{
  "job_id": "job_5c1e8a90d2"
}
```

*curl*

```bash
curl -s "https://webreaderapi.com/api/v1/batch" \
  -H "Authorization: Bearer $WEBREADER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"urls": ["https://example.com/blog/post-1", "https://example.com/blog/post-2"], "options": {"format": "markdown"}}'
```

*Python*

```python
import os
import requests

resp = requests.post(
    "https://webreaderapi.com/api/v1/batch",
    headers={"Authorization": "Bearer " + os.environ["WEBREADER_API_KEY"]},
    json={"urls": ["https://example.com/blog/post-1", "https://example.com/blog/post-2"], "options": {"format": "markdown"}},
    timeout=30,
)
print(resp.json()["job_id"])
```

*Node.js*

```javascript
const res = await fetch('https://webreaderapi.com/api/v1/batch', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.WEBREADER_API_KEY}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({ urls: ['https://example.com/blog/post-1', 'https://example.com/blog/post-2'], options: { format: 'markdown' } }),
});
console.log((await res.json()).job_id);
```

## Rozpocznij zadanie crawlowania

`POST /api/v1/crawl`

Odczytuje jedną witrynę od URL startowego, wszerz. Crawler pozostaje w domenie URL startowego, używa sitemapy wskazanej w robots.txt lub znalezionej pod `/sitemap.xml` i respektuje robots.txt oraz Crawl-delay. Wartości są przykładowe.

Uwierzytelnianie: klucz API w nagłówku `Authorization: Bearer API_KEY`.

**Treść zapytania**

| Pole | Typ | Wymagane | Opis |
| --- | --- | --- | --- |
| `url` | string | Tak | Strona startowa, np. strona główna witryny z dokumentacją. |
| `limit` | integer | Nie | Maksymalna liczba stron do odczytania, do 10 000. Zadanie kończy się po osiągnięciu tej liczby. |
| `max_depth` | integer | Nie | Maksymalna liczba kroków po linkach od URL startowego. |
| `include_paths` | array of strings | Nie | Proste wzorce ścieżek do zachowania, np. `/docs/*`. |
| `exclude_paths` | array of strings | Nie | Proste wzorce ścieżek do pominięcia, np. `/blog/tag/*`. |
| `options` | object | Nie | Dowolny parametr `/read` oprócz `url`, stosowany do każdej strony. |

*Przykład: the docs section of a site, 500 pages at most*

```json
{
  "url": "https://docs.example.com/",
  "limit": 500,
  "max_depth": 3,
  "include_paths": [
    "/docs/*"
  ],
  "exclude_paths": [
    "/docs/archive/*"
  ],
  "options": {
    "format": "markdown"
  }
}
```

**Odpowiedź** `202 Accepted`

```json
{
  "job_id": "job_8b27f41c0e"
}
```

*curl*

```bash
curl -s "https://webreaderapi.com/api/v1/crawl" \
  -H "Authorization: Bearer $WEBREADER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://docs.example.com/", "limit": 500, "include_paths": ["/docs/*"]}'
```

*Python*

```python
import os
import requests

resp = requests.post(
    "https://webreaderapi.com/api/v1/crawl",
    headers={"Authorization": "Bearer " + os.environ["WEBREADER_API_KEY"]},
    json={"url": "https://docs.example.com/", "limit": 500, "include_paths": ["/docs/*"]},
    timeout=30,
)
print(resp.json()["job_id"])
```

*Node.js*

```javascript
const res = await fetch('https://webreaderapi.com/api/v1/crawl', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.WEBREADER_API_KEY}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({ url: 'https://docs.example.com/', limit: 500, include_paths: ['/docs/*'] }),
});
console.log((await res.json()).job_id);
```

## Pobierz status zadania i wyniki

`GET /api/v1/jobs/{job_id}`

Zwraca status zadania oraz kolejne gotowe wyniki. Każdy element `results` to albo pełna odpowiedź `/read`, albo `{url, error}` dla strony, której nie udało się odczytać. Gdy `next_cursor` występuje, wywołaj ponownie z `cursor` ustawionym na tę wartość; gdy go nie ma, nie ma jeszcze kolejnych gotowych wyników. Wartości są przykładowe.

Uwierzytelnianie: klucz API w nagłówku `Authorization: Bearer API_KEY`.

**Parametry**

| Nazwa | W | Typ | Wymagane | Opis |
| --- | --- | --- | --- | --- |
| `job_id` | path | string | Tak | ID zwrócone przez `/batch` lub `/crawl`. |
| `cursor` | query | string | Nie | Wartość `next_cursor` z poprzedniego wywołania, aby pobrać kolejne wyniki. |

**Odpowiedź** `200`

```json
{
  "job_id": "job_8b27f41c0e",
  "status": "running",
  "results": [
    {
      "url": "https://docs.example.com/docs/install",
      "final_url": "https://docs.example.com/docs/install",
      "status_code": 200,
      "rendered": false,
      "credits_used": 1,
      "title": "Install",
      "markdown": "# Install\n\nRun the installer...",
      "word_count": 418
    },
    {
      "url": "https://docs.example.com/docs/private",
      "error": {
        "code": "blocked_by_robots",
        "message": "robots.txt disallows this page for WebReaderAPI."
      }
    }
  ],
  "next_cursor": "c_000002",
  "credits_used": 1
}
```

*curl*

```bash
curl -sG "https://webreaderapi.com/api/v1/jobs/job_8b27f41c0e" \
  -H "Authorization: Bearer $WEBREADER_API_KEY" \
  -d cursor=c_000002
```

*Python*

```python
import os
import requests

resp = requests.get(
    "https://webreaderapi.com/api/v1/jobs/job_8b27f41c0e",
    headers={"Authorization": "Bearer " + os.environ["WEBREADER_API_KEY"]},
    params={"cursor": "c_000002"},
    timeout=30,
)
job = resp.json()
print(job["status"], len(job["results"]), job.get("next_cursor"))
```

*Node.js*

```javascript
const res = await fetch('https://webreaderapi.com/api/v1/jobs/job_8b27f41c0e?cursor=c_000002', {
  headers: { Authorization: `Bearer ${process.env.WEBREADER_API_KEY}` },
});
const job = await res.json();
console.log(job.status, job.results.length, job.next_cursor);
```

## Jak czekać na zakończenie zadania?

Odpytuj `/jobs/{job_id}` i zachowuj kursor. Przetwarzaj otrzymane wyniki, a następnie wywołaj ponownie z `cursor` ustawionym na `next_cursor`. Gdy `next_cursor` nie występuje, a `status` to `done`, masz już wszystko; gdy go nie ma, a zadanie nadal jest w stanie `running`, odczekaj i zapytaj ponownie z tym samym kursorem.

Zacznij odpytwać co 2 sekundy i podwajaj czas oczekiwania do 30 sekund, dopóki nie pojawi się nic nowego. Crawl, który respektuje Crawl-delay witryny, może potrwać, więc długie oczekiwanie jest normalne i nic nie kosztuje.

*Python: odpytuj zadanie i zapisuj każdą stronę jako plik .md*

```python
import os
import pathlib
import re
import time

import requests

JOBS = "https://webreaderapi.com/api/v1/jobs"
HEADERS = {"Authorization": "Bearer " + os.environ["WEBREADER_API_KEY"]}


def save_job(job_id, folder="pages"):
    out = pathlib.Path(folder)
    out.mkdir(exist_ok=True)
    cursor, wait, saved = None, 2, 0
    while True:
        params = {"cursor": cursor} if cursor else {}
        job = requests.get(f"{JOBS}/{job_id}", headers=HEADERS, params=params, timeout=30).json()
        for item in job["results"]:
            if "error" in item:
                print("skipped", item["url"], item["error"]["code"])
                continue
            name = re.sub(r"[^a-z0-9]+", "-", item["final_url"].lower()).strip("-")[:120]
            (out / f"{name}.md").write_text(item["markdown"], encoding="utf-8")
            saved += 1
        if job.get("next_cursor"):
            cursor, wait = job["next_cursor"], 2
            continue
        if job["status"] == "done":
            return saved
        time.sleep(wait)
        wait = min(wait * 2, 30)


if __name__ == "__main__":
    print(save_job("job_8b27f41c0e"), "pages saved")

```

## Które strony odczytuje crawl?

Crawl zaczyna się od Twojego URL i posuwa się na zewnątrz o jeden krok po linkach naraz, wszerz, do `max_depth`. Odczytuje też sitemapę witryny, z adresu podanego w robots.txt lub z `/sitemap.xml`, aby znaleźć strony, do których prowadzi niewiele linków.

- **Tylko ta sama domena.** Linki do innych domen są wymieniane w wynikach, gdy poprosisz o `include_links`, ale nie są odczytywane.
- **Filtry ścieżek.** `include_paths` i `exclude_paths` przyjmują proste wzorce, np. `/blog/*`.
- **Jedna kopia na stronę.** Gdy witryna deklaruje kanoniczny URL, strona osiągnięta przez kilka adresów jest odczytywana raz.
- **Twardy limit.** Zadanie kończy się po odczytaniu `limit` stron.

## Jak crawl respektuje witrynę?

Każde zapytanie niesie User-Agent `WebReaderAPI/1.0 (+https://webreaderapi.com/bot/)`, a crawler przestrzega reguł robots.txt dla `WebReaderAPI` lub dla wszystkich robotów, gdy żaden go nie wskazuje. Zablokowane strony są pomijane i pojawiają się w `results` z błędem `blocked_by_robots`; nic nie kosztują.

Gdy robots.txt ustawia `Crawl-delay`, crawler czeka tyle między dwoma zapytaniami do witryny. Jeśli witryna zadeklaruje Content-Signal: ai-input=no w swoim robots.txt, nie czytamy jej stron dla Ciebie. Jeśli witryna odrzuci nasze zapytanie, zwracamy jej odpowiedź: nigdy nie rozwiązujemy CAPTCHA, nie logujemy się do kont ani nie próbujemy obejść blokady lub paywalla.

## Ile kosztuje crawl?

Każda odczytana strona kosztuje tyle, co wywołanie `/read`: 1 kredyt, 2, jeśli strona wymagała przeglądarki, plus 10 z `country`. Pominięte i nieudane strony są darmowe. Pole `credits_used` zadania sumuje to, co zostało już rozliczone.

Witryna z dokumentacją licząca 500 stron odczytana bez przeglądarki zużywa około 500 kredytów, co pakiet 10 000 stron (3,75 USD) pokrywa wielokrotnie. Ponieważ zadania nie można anulować w v1, `limit` to Twój limit wydatków: ustaw go na maksymalną liczbę stron, za które jesteś gotowy zapłacić.

## Batch i crawl: najczęstsze pytania

### Czy używać /batch czy wielu wywołań /read?

Użyj `/batch`, gdy masz już URL-e i nie potrzebujesz każdej odpowiedzi od razu. Oszczędza Ci zarządzania współbieżnością i ponawianiem. Użyj `/read`, gdy agent potrzebuje jednej strony teraz.

### Jak długo przechowywane są wyniki zadań?

Odbieraj je, gdy tylko będą gotowe. Wyniki czekają, aż je pobierzesz; po dostarczeniu nie jest przechowywana żadna kopia treści strony, więc zapisz to, czego potrzebujesz, po swojej stronie.

### Czy crawl może podążać za linkami do innych witryn?

Nie. Crawl odczytuje jedną domenę. Inne domeny pojawiają się jako linki, gdy o nie poprosisz, a te URL-e możesz wysłać do `/batch`, jeśli chcesz je odczytać.

### Czy mogę zatrzymać rozpoczęty crawl?

Nie w v1. Zadanie zatrzymuje się na `limit` stronach, więc wybierz limit odpowiadający Twojemu budżetowi. Strony już odczytane pozostają rozliczone; strony nigdy nieodczytane nic nie kosztują.

---

WebReaderAPI: WebReaderAPI to API czytnika stron internetowych dla agentów AI, które zamienia każdą publiczną stronę internetową w czysty Markdown, z płatnością kryptowalutą i bez przesyłania dokumentów tożsamości. Przedpłacone kredyty od 0,07 USD/1K stron nigdy nie wygasają, a robots.txt jest respektowany. Bez KYC — bez dowodu, bez selfie, bez dokumentów. Płatność kryptowalutą.
