# Webpagina naar Markdown in Python

> Converteer webpagina's naar Markdown in Python: requests, async httpx met concurrency-limiet, batchjobs en RAG-chunking. Vanaf US$ 0,07/1K pagina's, zonder KYC.

- URL: https://webreaderapi.com/nl/docs/python/
- Laatst bijgewerkt: 2026-10-22
- Uitgever: WebReaderAPI (https://webreaderapi.com)

Vier recepten die de meeste Python-projecten dekken: lees één pagina met requests, lees veel pagina's gelijktijdig met httpx, geef een lijst aan een batchjob, en splits de Markdown in chunks voor een vector store.

**Kort antwoord — Hoe converteer ik een webpagina naar Markdown in Python?**

Roep **WebReaderAPI** aan met `requests.get("https://webreaderapi.com/api/v1/read", params={"url": ...})` en je sleutel in de header `Authorization`; het JSON-antwoord bevat de hoofdinhoud van de pagina in `markdown`. Voor veel pagina's gebruik je `httpx.AsyncClient` met een semaphore of een batchjob. Eén pagina kost 1 credit, vanaf US$ 0,07/1K pagina's per oktober 2026. Zonder KYC; betalen met crypto.

## Wat moet ik geïnstalleerd hebben?

Python 3.9 of later en één HTTP-client. `requests` is genoeg voor scripts die één pagina tegelijk lezen; `httpx` voegt async-ondersteuning toe voor veel pagina's parallel. Er is geen package van ons nodig: de API is gewoon HTTPS en JSON.

Houd je sleutel uit de code. Lees hem uit de environment variable `WEBREADER_API_KEY`, zoals elk voorbeeld hieronder doet, en geef elk project een eigen sleutel zodat je er één kunt intrekken zonder de andere aan te raken.

*Installeren*

```bash
pip install requests httpx
export WEBREADER_API_KEY="your-key"
```

## Hoe lees ik één pagina met requests?

Stuur `url` en eventuele opties als query parameters, laat een HTTP-fout een exception geven, en lees de velden die je nodig hebt. Het voorbeeld bewaart de hoofdinhoud, begrenst die op 20.000 tekens voor een model, en print wat de call kostte.

*requests: één pagina*

```python
import os

import requests

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


def read(url, **options):
    resp = requests.get(API, headers=HEADERS, params={"url": url, **options}, timeout=75)
    if resp.status_code != 200:
        error = resp.json()["error"]
        raise RuntimeError(error["code"] + ": " + error["message"])
    return resp.json()


page = read("https://example.com/docs/getting-started", max_chars=20000)
print(page["title"], page["word_count"], "words", page["credits_used"], "credit(s)")
print(page["markdown"][:500])

```

## Hoe lees ik veel pagina's tegelijk met httpx?

Gebruik één `httpx.AsyncClient` en een `asyncio.Semaphore` op of onder je concurrency-limiet. Elk account heeft een limiet voor het aantal pagina's dat het tegelijk leest; je dashboard toont die, en support kan hem verhogen. Fouten van één pagina mogen de andere niet stoppen, dus het voorbeeld verzamelt ze naast de resultaten in plaats van een exception te laten optreden.

Ga je boven de limiet, dan antwoordt de API met `429 concurrency_limit`; er wordt niets gefactureerd, maar de pagina wordt niet gelezen. De semaphore onder de limiet houden is eenvoudiger dan opnieuw proberen.

*httpx: tot 10 pagina's tegelijk in behandeling*

```python
import asyncio
import os

import httpx

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


async def read_one(client, sem, url):
    async with sem:
        resp = await client.get(API, params={"url": url, "max_chars": 50000})
    body = resp.json()
    if resp.status_code == 200:
        return url, body, None
    return url, None, body["error"]["code"]


async def read_many(urls, limit=10):
    sem = asyncio.Semaphore(limit)
    async with httpx.AsyncClient(headers=HEADERS, timeout=75) as client:
        return await asyncio.gather(*(read_one(client, sem, u) for u in urls))


if __name__ == "__main__":
    urls = [f"https://example.com/blog/post-{n}" for n in range(1, 21)]
    for url, page, error in asyncio.run(read_many(urls)):
        print(url, "->", error or page["title"])

```

## Wanneer gebruik ik in plaats daarvan een batchjob?

Wanneer je een lijst van maximaal 100 URL's hebt en niet elke pagina nodig hebt op het moment dat die klaar is. Een batchjob draait aan onze kant, dus je script houdt geen verbindingen open en doet geen retry-logica. Start hem met `POST https://webreaderapi.com/api/v1/batch` en haal de resultaten daarna op uit de job met de cursor-loop die je ziet op de [pagina over batches en crawlen](https://webreaderapi.com/nl/docs/crawl/).

Voor een hele site start je in plaats daarvan een crawl en laat je die de sitemap en links volgen; dezelfde pagina heeft een complete loop die één `.md`-bestand per pagina schrijft.

*Een batchjob starten*

```python
import os

import requests

urls = [f"https://example.com/changelog/{year}" for year in range(2020, 2027)]
resp = requests.post(
    "https://webreaderapi.com/api/v1/batch",
    headers={"Authorization": "Bearer " + os.environ["WEBREADER_API_KEY"]},
    json={"urls": urls, "options": {"format": "markdown", "main_content": True}},
    timeout=30,
)
resp.raise_for_status()
print("job:", resp.json()["job_id"])

```

## Hoe splits ik de Markdown in chunks voor RAG?

Splits eerst op koppen en daarna op grootte. Koppen markeren waar onderwerpen veranderen, dus een chunk die bij een kop begint, is makkelijker te matchen voor een retriever. Zet de paginatitel en het kop-pad in de metadata van elke chunk: ze helpen het model de juiste sectie te citeren.

De functie hieronder heeft geen extra library nodig. Hij knipt bij `#` tot `###` koppen en splitst vervolgens elke lange sectie op lege regels om onder een karakterbudget te blijven. Sla `final_url` bij elke chunk op, zodat twee adressen die naar dezelfde pagina leiden geen duplicaten opleveren.

*Markdown per kop in chunks splitsen*

```python
import re


def chunk_markdown(page, max_chars=2000):
    """Split a /read response into chunks with a heading path and the source URL."""
    chunks, path, buf = [], [], []

    def flush():
        text = "\n".join(buf).strip()
        while text:
            part = text[:max_chars]
            cut = part.rfind("\n\n")
            if len(text) > max_chars and cut > max_chars // 2:
                part = part[:cut]
            chunks.append({"text": part.strip(), "section": " > ".join(path), "title": page.get("title"), "source": page["final_url"]})
            text = text[len(part):].strip()
        buf.clear()

    for line in page["markdown"].splitlines():
        m = re.match(r"^(#{1,3}) (.+)", line)
        if m:
            flush()
            level = len(m.group(1))
            path[:] = path[: level - 1] + [m.group(2).strip()]
        buf.append(line)
    flush()
    return chunks

```

## Hoeveel kost een Python-job?

Tel pagina's, niet requests. 1 credit = 1 pagina lezen zonder browser; 2 credits wanneer de pagina een browser nodig heeft om JavaScript te renderen. Een verzoek dat mislukt, of dat de robots.txt van een site weigert, wordt niet in rekening gebracht. Het lezen van 2.000 documentatiepagina's zonder browser kost ongeveer 2.000 credits, wat 10.000 pagina's (US$ 3,75) ruim vijf keer dekt. Prepaid credits verlopen nooit: ongebruikte pagina's blijven op je account staan tot je ze gebruikt, dus een pipeline die één keer per maand draait, gebruikt alleen wat hij leest.

## Python: veelgestelde vragen

### Is er een officieel Python-package?

Nee. De code op deze pagina is alles wat je nodig hebt: WebReaderAPI is een gewone HTTPS API, en `requests` of `httpx` spreken die direct aan.

### Hoe ga ik om met een pagina die robots.txt weigert?

Controleer de foutcode. `blocked_by_robots` en `target_refused` zijn definitief en gratis; leg ze vast en ga verder. De [foutenpagina](https://webreaderapi.com/nl/docs/errors/) heeft een retry-helper die alleen de codes opnieuw probeert die kunnen veranderen.

### Kan ik de API vanuit een Jupyter-notebook gebruiken?

Ja. Het `requests`-recept werkt zoals het is. In een notebook vervangt `await read_many(urls)` de `asyncio.run`, omdat het notebook al een event loop draait.

### Welke timeout moet mijn client gebruiken?

Iets meer dan die van de API zelf: de standaard is 30 seconden en het maximum 60, dus 75 seconden aan de clientkant laat ruimte voor een trage site en de response.

---

WebReaderAPI: WebReaderAPI is een web reader-API voor AI-agents die elke openbare webpagina omzet in schone Markdown, betaald met crypto zonder ID te uploaden. Prepaid credits vanaf US$ 0,07/1K pagina's verlopen nooit, en robots.txt wordt gerespecteerd. Zonder KYC — geen ID, geen selfie, geen documenten. Betalen met crypto.
