# แปลงหน้าเว็บเป็น Markdown ใน Python

> แปลงหน้าเว็บเป็น Markdown ใน Python ด้วย requests และ async httpx จำกัด concurrency พร้อมแบ่ง chunk สำหรับ RAG ตามหัวข้อ จาก US$0.07/1K หน้า ไม่ต้อง KYC

- URL: https://webreaderapi.com/th/docs/python/
- อัปเดตล่าสุด: 2026-10-22
- ผู้เผยแพร่: WebReaderAPI (https://webreaderapi.com)

สี่สูตรที่ครอบคลุมโปรเจกต์ Python ส่วนใหญ่: อ่านหนึ่งหน้าด้วย requests, อ่านหลายหน้าพร้อมกันด้วย httpx, ส่งลิสต์ให้งานแบบ batch และแบ่ง Markdown เป็น chunk สำหรับ vector store

**คำตอบสั้น — จะแปลงหน้าเว็บเป็น Markdown ใน Python ได้อย่างไร?**

เรียก **WebReaderAPI** พร้อม `requests.get("https://webreaderapi.com/api/v1/read", params={"url": ...})` และ key ของคุณใน header `Authorization` คำตอบ JSON จะเก็บเนื้อหาหลักของหน้าเว็บไว้ใน `markdown` สำหรับหลายหน้า ให้ใช้ `httpx.AsyncClient` พร้อม semaphore หรือใช้งานแบบ batch หนึ่งหน้าคิด 1 เครดิต เริ่มต้นที่ US$0.07/1K หน้า ณ ตุลาคม 2026 ไม่ต้อง KYC จ่ายด้วยคริปโต

## ต้องติดตั้งอะไรบ้าง?

Python 3.9 ขึ้นไปและ HTTP client หนึ่งตัว `requests` เพียงพอสำหรับสคริปต์ที่อ่านทีละหน้า `httpx` เพิ่มการรองรับ async สำหรับหลายหน้าแบบขนาน ไม่ต้องใช้แพ็กเกจของเรา: API เป็น HTTPS และ JSON ธรรมดา

เก็บ key ของคุณออกจากโค้ด อ่านจาก environment variable `WEBREADER_API_KEY` ตามที่ทุกตัวอย่างด้านล่างทำ และให้ key แยกต่างหากกับแต่ละโปรเจกต์เพื่อให้เพิกถอนอันหนึ่งได้โดยไม่กระทบอันอื่น

*ติดตั้ง*

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

## จะอ่านหนึ่งหน้าด้วย requests ได้อย่างไร?

ส่ง `url` และตัวเลือกใด ๆ เป็น query parameter, เรียก raise เมื่อเกิด HTTP error และอ่านฟิลด์ที่ต้องการ ตัวอย่างเก็บเนื้อหาหลัก จำกัดที่ 20,000 ตัวอักษรสำหรับโมเดล และพิมพ์ค่าใช้จ่ายของการเรียก

*requests: หนึ่งหน้า*

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

```

## จะอ่านหลายหน้าพร้อมกันด้วย httpx ได้อย่างไร?

ใช้ `httpx.AsyncClient` หนึ่งตัวและ `asyncio.Semaphore` ที่ตั้งไว้ไม่เกินขีดจำกัด concurrency ของคุณ ทุกบัญชีมีขีดจำกัดจำนวนหน้าที่อ่านพร้อมกัน แดชบอร์ดแสดงให้เห็น และฝ่ายสนับสนุนสามารถเพิ่มให้ได้ ข้อผิดพลาดจากหน้าหนึ่งไม่ควรหยุดหน้าอื่น ดังนั้นตัวอย่างจะรวบรวมไว้ข้างผลลัพธ์แทนการ raise

หากเกินขีดจำกัด API จะตอบ `429 concurrency_limit` ไม่มีการเรียกเก็บเงิน แต่หน้าเว็บจะไม่ถูกอ่าน การตั้ง semaphore ให้ต่ำกว่าขีดจำกัดง่ายกว่าการ retry

*httpx: สูงสุด 10 หน้าพร้อมกัน*

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

```

## เมื่อไรควรใช้ batch job แทน

ใช้เมื่อคุณมีรายการ URL สูงสุด 100 รายการและไม่ต้องการแต่ละหน้าในทันทีที่พร้อม batch job ทำงานฝั่งเรา สคริปต์ของคุณจึงไม่ต้องถือ connection ค้างไว้และไม่ต้องมี retry logic เริ่มด้วย `POST https://webreaderapi.com/api/v1/batch` แล้วเก็บผลลัพธ์จาก job ด้วย cursor loop ที่แสดงบน[หน้าหน้า batch และ crawl](https://webreaderapi.com/th/docs/crawl/)

สำหรับทั้งเว็บไซต์ ให้เริ่ม crawl แทนและปล่อยให้มันตาม sitemap และลิงก์ หน้าเดียวกันมี loop ครบถ้วนที่เขียนไฟล์ `.md` หนึ่งไฟล์ต่อหน้า

*เริ่ม batch job*

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

```

## ฉันจะแบ่ง Markdown เป็น chunk สำหรับ RAG ได้อย่างไร

แบ่งตาม heading ก่อน แล้วจึงแบ่งตามขนาด heading บอกว่าหัวข้อเปลี่ยนตรงไหน chunk ที่เริ่มต้นที่ heading จึงตรงกับ retriever ได้ง่ายกว่า เก็บชื่อหน้าและ heading path ไว้ใน metadata ของแต่ละ chunk เพราะช่วยให้โมเดลอ้างอิงส่วนที่ถูกต้อง

ฟังก์ชันด้านล่างไม่ต้องใช้ไลบรารีเพิ่มเติม ตัดที่ heading `#` ถึง `###` แล้วแบ่งส่วนที่ยาวด้วยบรรทัดว่างเพื่อให้อยู่ภายใต้งบประมาณตัวอักษร เก็บ `final_url` ไว้กับแต่ละ chunk เพื่อไม่ให้สอง URL ที่นำไปสู่หน้าเดียวกันสร้างข้อมูลซ้ำ

*แบ่ง Markdown ตาม 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

```

## งาน Python ราคาเท่าไร

นับหน้า ไม่ใช่นับ request 1 เครดิต = อ่าน 1 หน้าโดยไม่ใช้เบราว์เซอร์; 2 เครดิตเมื่อหน้านั้นต้องใช้เบราว์เซอร์เพื่อเรนเดอร์ JavaScript คำขอที่ล้มเหลว หรือที่ robots.txt ของเว็บไซต์ปฏิเสธ จะไม่ถูกคิดเครดิต การอ่าน 2,000 หน้าเอกสารโดยไม่ใช้เบราว์เซอร์ใช้ประมาณ 2,000 เครดิต ซึ่ง 10,000 หน้า (US$3.75) ครอบคลุมเกินห้าเท่า เครดิตแบบเติมเงินไม่มีวันหมดอายุ: หน้าที่ไม่ได้ใช้จะยังคงอยู่ในบัญชีของคุณจนกว่าคุณจะใช้ pipeline ที่รันเดือนละครั้งจึงใช้เฉพาะสิ่งที่อ่าน

## Python: คำถามที่พบบ่อย

### มีแพ็กเกจ Python อย่างเป็นทางการไหม

ไม่มี โค้ดบนหน้านี้คือทั้งหมดที่คุณต้องใช้: WebReaderAPI เป็น HTTPS API ธรรมดา และ `requests` หรือ `httpx` เรียกใช้ได้โดยตรง

### ฉันจะจัดการกับหน้าที่ robots.txt ปฏิเสธได้อย่างไร

ตรวจสอบรหัสข้อผิดพลาด `blocked_by_robots` และ `target_refused` เป็นการสิ้นสุดและไม่มีค่าใช้จ่าย บันทึกไว้แล้วดำเนินการต่อ [หน้าข้อผิดพลาด](https://webreaderapi.com/th/docs/errors/) มี retry helper ที่ลองใหม่เฉพาะรหัสที่อาจเปลี่ยนได้

### ใช้ API จาก Jupyter notebook ได้ไหม

ได้ สูตร `requests` ใช้ได้ตามเดิม ใน notebook ให้ใช้ `await read_many(urls)` แทน `asyncio.run` เพราะ notebook มี event loop ทำงานอยู่แล้ว

### client ของฉันควรตั้ง timeout เท่าไร

มากกว่า timeout ของ API เองเล็กน้อย: ค่าเริ่มต้นคือ 30 วินาทีและสูงสุด 60 วินาที ดังนั้น 75 วินาทีฝั่ง client จึงเหลือที่ให้เว็บไซต์ที่ช้าและ response

---

WebReaderAPI: WebReaderAPI คือ API อ่านหน้าเว็บสำหรับ AI agent ที่แปลงหน้าเว็บสาธารณะใด ๆ เป็น Markdown สะอาด จ่ายด้วยคริปโตโดยไม่ต้องอัปโหลดบัตรประชาชน เครดิตแบบเติมเงินเริ่มต้นที่ US$0.07/1K หน้า ไม่มีวันหมดอายุ และเคารพ robots.txt ไม่ต้อง KYC — ไม่ต้องใช้บัตรประชาชน ไม่ต้องเซลฟี่ ไม่ต้องเอกสาร จ่ายด้วยคริปโต
