คำตอบสั้น
จะแปลงหน้าเว็บเป็น 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 แยกต่างหากกับแต่ละโปรเจกต์เพื่อให้เพิกถอนอันหนึ่งได้โดยไม่กระทบอันอื่น
pip install requests httpx
export WEBREADER_API_KEY="your-key"จะอ่านหนึ่งหน้าด้วย requests ได้อย่างไร?
ส่ง url และตัวเลือกใด ๆ เป็น query parameter, เรียก raise เมื่อเกิด HTTP error และอ่านฟิลด์ที่ต้องการ ตัวอย่างเก็บเนื้อหาหลัก จำกัดที่ 20,000 ตัวอักษรสำหรับโมเดล และพิมพ์ค่าใช้จ่ายของการเรียก
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
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
สำหรับทั้งเว็บไซต์ ให้เริ่ม crawl แทนและปล่อยให้มันตาม sitemap และลิงก์ หน้าเดียวกันมี loop ครบถ้วนที่เขียนไฟล์ .md หนึ่งไฟล์ต่อหน้า
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 ที่นำไปสู่หน้าเดียวกันสร้างข้อมูลซ้ำ
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 เป็นการสิ้นสุดและไม่มีค่าใช้จ่าย บันทึกไว้แล้วดำเนินการต่อ หน้าข้อผิดพลาด มี 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