Không cần KYC — không ID, không selfie, không giấy tờ. Thanh toán bằng crypto: BTC, USDT, USDC, ETH, SOL, LTC
Ngôn ngữ: Tiếng Việt
Lấy khóa API

Docs · Python

Chuyển trang web sang Markdown trong Python

Bốn công thức bao quát hầu hết dự án Python: đọc một trang bằng requests, đọc nhiều trang đồng thời bằng httpx, giao một danh sách cho tác vụ hàng loạt và chia Markdown thành các đoạn cho vector store.

Cập nhật lần cuối: · 5 phút đọc

Câu trả lời nhanh

Làm cách nào để chuyển một trang web sang Markdown trong Python?

Gọi WebReaderAPI với requests.get("https://webreaderapi.com/api/v1/read", params={"url": ...}) và khóa của bạn trong header Authorization; câu trả lời JSON chứa nội dung chính của trang trong markdown. Với nhiều trang, dùng httpx.AsyncClient với một semaphore hoặc một tác vụ hàng loạt. Một trang tốn 1 credit, từ 0,07 US$/1K trang tính đến tháng 10 năm 2026. Không cần KYC; thanh toán bằng crypto.

Tôi cần cài gì?

Python 3.9 trở lên và một HTTP client. requests là đủ cho các script đọc từng trang một; httpx thêm hỗ trợ async để đọc nhiều trang song song. Không cần gói nào của chúng tôi: API chỉ là HTTPS và JSON thuần.

Đừng để khóa trong mã. Hãy đọc nó từ biến môi trường WEBREADER_API_KEY, như mọi ví dụ bên dưới làm, và cấp cho mỗi dự án một khóa riêng để bạn có thể thu hồi một khóa mà không ảnh hưởng đến các khóa khác.

Cài đặt
pip install requests httpx
export WEBREADER_API_KEY="your-key"

Làm cách nào để đọc một trang bằng requests?

Gửi url và bất kỳ tùy chọn nào dưới dạng query parameter, raise khi gặp lỗi HTTP, và đọc các field bạn cần. Ví dụ giữ nội dung chính, giới hạn ở 20.000 ký tự cho mô hình, và in ra chi phí của lần gọi.

requests: một trang
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])

Làm cách nào để đọc nhiều trang cùng lúc bằng httpx?

Dùng một httpx.AsyncClient và một asyncio.Semaphore đặt ở mức bằng hoặc dưới giới hạn đồng thời của bạn. Mỗi tài khoản có giới hạn số trang đọc cùng lúc; dashboard của bạn hiển thị giới hạn đó, và bộ phận hỗ trợ có thể tăng lên. Lỗi từ một trang không nên chặn các trang khác, nên ví dụ thu thập chúng bên cạnh kết quả thay vì raise.

Nếu bạn vượt giới hạn, API trả về 429 concurrency_limit; không tính phí gì, nhưng trang không được đọc. Giữ semaphore dưới giới hạn đơn giản hơn là thử lại.

httpx: tối đa 10 trang cùng lúc
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"])

Khi nào tôi nên dùng batch job thay vào đó?

Khi bạn có danh sách tối đa 100 URL và không cần từng trang ngay khi nó sẵn sàng. Batch job chạy phía chúng tôi, nên script của bạn không giữ kết nối nào mở và không cần logic thử lại. Khởi động nó bằng POST https://webreaderapi.com/api/v1/batch, rồi thu thập kết quả từ job bằng vòng lặp cursor hiển thị trên trang batch và crawl.

Với cả một trang web, hãy khởi động crawl thay vào đó và để nó theo sitemap và các liên kết; cùng trang đó có vòng lặp hoàn chỉnh ghi một file .md cho mỗi trang.

Khởi chạy job batch
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"])

Làm sao để chia Markdown thành các chunk cho RAG?

Chia theo heading trước, rồi theo kích thước. Heading đánh dấu nơi chủ đề thay đổi, nên một chunk bắt đầu từ heading sẽ dễ được retriever khớp hơn. Giữ tiêu đề trang và đường dẫn heading trong metadata của mỗi chunk: chúng giúp model trích dẫn đúng phần.

Hàm bên dưới không cần thư viện bổ sung. Nó cắt tại các heading # đến ###, rồi chia bất kỳ phần dài nào theo dòng trống để nằm dưới mức giới hạn ký tự. Lưu final_url cùng mỗi chunk, để hai địa chỉ dẫn tới cùng một trang không tạo ra bản trùng lặp.

Chia chunk Markdown theo heading
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

Một job Python tốn bao nhiêu?

Đếm số trang, không đếm số truy vấn. 1 credit = đọc 1 trang không cần trình duyệt; 2 credit khi trang cần trình duyệt để render JavaScript. Yêu cầu thất bại, hoặc bị robots.txt của trang web từ chối, không bị tính phí. Đọc 2.000 trang tài liệu không dùng trình duyệt tốn khoảng 2.000 credit, mà 10.000 trang (3,75 US$) đủ cho gấp năm lần. Credit trả trước không bao giờ hết hạn: số trang chưa dùng vẫn ở trong tài khoản của bạn cho đến khi bạn dùng, nên một pipeline chạy mỗi tháng một lần chỉ tốn phần nó đọc.

Python: các câu hỏi thường gặp

Có package Python chính thức không?

Không. Đoạn code trên trang này là tất cả những gì bạn cần: WebReaderAPI là một HTTPS API thuần, và requests hoặc httpx nói trực tiếp với nó.

Làm sao xử lý trang bị robots.txt từ chối?

Kiểm tra mã lỗi. blocked_by_robots và target_refused là cuối cùng và miễn phí; ghi lại rồi tiếp tục. Trang errors có một helper thử lại chỉ thử lại các mã có thể thay đổi.

Tôi có thể dùng API từ Jupyter notebook không?

Có. Công thức requests hoạt động nguyên trạng. Trong notebook, await read_many(urls) thay cho asyncio.run, vì notebook đã chạy sẵn event loop.

Client của tôi nên dùng timeout bao nhiêu?

Nhỉnh hơn một chút so với timeout của chính API: mặc định là 30 giây và tối đa 60, nên 75 giây phía client để dư chỗ cho một trang chậm và phản hồi.

Từ 0,07 US$/1K trang · không cần KYC

Lấy khóa API