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
| 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. 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. |
{
"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
{
"job_id": "job_5c1e8a90d2"
}Przykłady kodu
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"}}'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"])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. |
{
"url": "https://docs.example.com/",
"limit": 500,
"max_depth": 3,
"include_paths": [
"/docs/*"
],
"exclude_paths": [
"/docs/archive/*"
],
"options": {
"format": "markdown"
}
}Odpowiedź 202 Accepted
{
"job_id": "job_8b27f41c0e"
}Przykłady kodu
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/*"]}'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"])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
{
"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
}Przykłady kodu
curl -sG "https://webreaderapi.com/api/v1/jobs/job_8b27f41c0e" \
-H "Authorization: Bearer $WEBREADER_API_KEY" \
-d cursor=c_000002import 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"))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.
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_pathsiexclude_pathsprzyjmują 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
limitstron.
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ą.