# Halaman web ke Markdown di Python

> Ubah halaman web menjadi Markdown di Python: requests, httpx async dengan batas konkurensi, job batch, dan chunking RAG. Dari US$0,07/1K halaman, tanpa KYC.

- URL: https://webreaderapi.com/id/docs/python/
- Terakhir diperbarui: 2026-10-22
- Penerbit: WebReaderAPI (https://webreaderapi.com)

Empat resep yang mencakup sebagian besar proyek Python: baca satu halaman dengan requests, baca banyak halaman secara bersamaan dengan httpx, serahkan daftar ke job batch, dan pecah Markdown menjadi chunk untuk vector store.

**Jawaban singkat — Bagaimana cara mengubah halaman web menjadi Markdown di Python?**

Panggil **WebReaderAPI** dengan `requests.get("https://webreaderapi.com/api/v1/read", params={"url": ...})` dan API key Anda di header `Authorization`; jawaban JSON-nya berisi konten utama halaman dalam `markdown`. Untuk banyak halaman, gunakan `httpx.AsyncClient` dengan semaphore atau job batch. Satu halaman berbiaya 1 kredit, mulai dari US$0,07/1K halaman per Oktober 2026. Tanpa KYC; bayar dengan kripto.

## Apa yang perlu saya instal?

Python 3.9 atau lebih baru dan satu HTTP client. `requests` cukup untuk skrip yang membaca satu halaman sekaligus; `httpx` menambahkan dukungan async untuk banyak halaman secara paralel. Tidak perlu paket dari kami: API-nya HTTPS dan JSON biasa.

Jauhkan API key Anda dari kode. Bacalah dari environment variable `WEBREADER_API_KEY`, seperti yang dilakukan setiap contoh di bawah, dan beri setiap proyek API key sendiri agar Anda bisa mencabut satu tanpa menyentuh yang lain.

*Instal*

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

## Bagaimana cara membaca satu halaman dengan requests?

Kirim `url` dan opsi apa pun sebagai parameter kueri, raise saat ada error HTTP, dan baca field yang Anda butuhkan. Contoh ini menyimpan konten utama, membatasinya di 20.000 karakter untuk model, dan mencetak biaya panggilannya.

*requests: satu halaman*

```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])

```

## Bagaimana cara membaca banyak halaman sekaligus dengan httpx?

Gunakan satu `httpx.AsyncClient` dan `asyncio.Semaphore` yang disetel pada atau di bawah batas konkurensi Anda. Setiap akun memiliki batas berapa banyak halaman yang dibaca sekaligus; dashboard Anda menampilkannya, dan support dapat menaikkannya. Error dari satu halaman tidak boleh menghentikan yang lain, jadi contoh ini mengumpulkannya di samping hasil alih-alih me-raise.

Jika Anda melewati batas, API menjawab `429 concurrency_limit`; tidak ada yang ditagih, tetapi halaman tidak dibaca. Menjaga semaphore di bawah batas lebih sederhana daripada mencoba ulang.

*httpx: hingga 10 halaman berjalan bersamaan*

```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"])

```

## Kapan sebaiknya saya menggunakan batch job?

Saat Anda memiliki daftar hingga 100 URL dan tidak membutuhkan setiap halaman begitu siap. Batch job berjalan di sisi kami, jadi skrip Anda tidak menahan koneksi tetap terbuka dan tidak melakukan logika retry. Mulai dengan `POST https://webreaderapi.com/api/v1/batch`, lalu kumpulkan hasil dari job dengan loop cursor seperti yang ditunjukkan di [halaman batch dan crawl](https://webreaderapi.com/id/docs/crawl/).

Untuk satu situs penuh, mulai crawl sebagai gantinya dan biarkan mengikuti sitemap dan tautan; halaman yang sama memiliki loop lengkap yang menulis satu file `.md` per halaman.

*Mulai job batch*

```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"])

```

## Bagaimana cara memecah Markdown menjadi chunk untuk RAG?

Pecah berdasarkan heading dulu, lalu berdasarkan ukuran. Heading menandai tempat topik berubah, jadi chunk yang dimulai pada heading lebih mudah dicocokkan oleh retriever. Simpan judul halaman dan jalur heading di metadata setiap chunk: keduanya membantu model mengutip bagian yang tepat.

Fungsi di bawah tidak memerlukan library tambahan. Fungsi ini memotong pada heading `#` hingga `###`, lalu memecah bagian yang panjang pada baris kosong agar tetap di bawah batas karakter. Simpan `final_url` bersama setiap chunk, agar dua alamat yang mengarah ke halaman yang sama tidak membuat duplikat.

*Pecah Markdown berdasarkan heading*

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

```

## Berapa biaya job Python?

Hitung halaman, bukan request. 1 kredit = 1 halaman dibaca tanpa browser; 2 kredit jika halaman memerlukan browser untuk merender JavaScript. Permintaan yang gagal, atau yang ditolak oleh robots.txt suatu situs, tidak dikenai biaya. Membaca 2.000 halaman dokumentasi tanpa browser menghabiskan sekitar 2.000 kredit, yang ditanggung 10.000 halaman (US$3,75) lima kali lipat. Kredit prabayar tidak pernah kedaluwarsa: halaman yang belum digunakan tetap ada di akun Anda sampai Anda memakainya, jadi pipeline yang berjalan sebulan sekali hanya memakai apa yang dibacanya.

## Python: pertanyaan umum

### Apakah ada paket Python resmi?

Tidak. Kode di halaman ini sudah cukup: WebReaderAPI adalah API HTTPS biasa, dan `requests` atau `httpx` dapat memanggilnya langsung.

### Bagaimana cara menangani halaman yang ditolak robots.txt?

Periksa kode error. `blocked_by_robots` dan `target_refused` bersifat final dan gratis; catat lalu lanjutkan. [Halaman errors](https://webreaderapi.com/id/docs/errors/) memiliki helper retry yang hanya mengulang kode yang bisa berubah.

### Bisakah saya menggunakan API dari notebook Jupyter?

Bisa. Resep `requests` berfungsi apa adanya. Di notebook, `await read_many(urls)` menggantikan `asyncio.run`, karena notebook sudah menjalankan event loop.

### Timeout berapa yang harus dipakai klien saya?

Sedikit lebih besar dari timeout API itu sendiri: default-nya 30 detik dan maksimal 60, jadi 75 detik di sisi klien memberi ruang untuk situs yang lambat dan responsnya.

---

WebReaderAPI: WebReaderAPI adalah API pembaca web untuk AI agent yang mengubah halaman web publik apa pun menjadi Markdown bersih, dibayar dengan kripto tanpa unggah identitas. Kredit prabayar mulai dari US$0,07/1K halaman tidak pernah kedaluwarsa, dan robots.txt dihormati. Tanpa KYC — tanpa identitas, tanpa selfie, tanpa dokumen. Bayar dengan kripto.
