Zonder KYC — geen ID, geen selfie, geen documenten. Betalen met crypto: BTC, USDT, USDC, ETH, SOL, LTC
Taal: Nederlands
Vraag een API-sleutel aan

Docs · Python

Webpagina naar Markdown in Python

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.

Laatst bijgewerkt: · 5 min leestijd

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
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
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
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.

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
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
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 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.

Vanaf US$ 0,07/1K pagina's · zonder KYC

Vraag een API-sleutel aan