งานที่พบบ่อยในทีมพัฒนาคือมีรายงาน ใบเสนอราคา หรือเอกสารนโยบายเป็น PDF แล้วต้องการเนื้อหาไปค้นหา สรุป หรือป้อนให้ระบบอื่นต่อ ปัญหาคือ PDF สองไฟล์ที่หน้าตาเหมือนกันทุกประการอาจดึงข้อความได้ไฟล์หนึ่ง และดึงไม่ได้เลยอีกไฟล์หนึ่ง บทความนี้อธิบายว่าข้างใน PDF เก็บข้อความอย่างไร ทำไมผลลัพธ์ที่ได้จึงเป็นข้อความธรรมดาไม่ใช่ Markdown เต็มรูปแบบ กรณีไหนที่ผลออกมาไม่ตรงใจ และวิธีเรียกใช้ผ่าน API ที่ไม่เก็บไฟล์ของคุณ

PDF เก็บข้อความอย่างไร

PDF ถูกออกแบบมาเพื่อให้ "พิมพ์ออกมาเหมือนเดิมทุกเครื่อง" ไม่ได้ออกแบบมาเพื่อให้แก้ไขหรืออ่านโครงสร้าง แต่ละหน้าคือชุดคำสั่งวาดภาพ (content stream) ที่บอกว่าให้วางตัวอักษรอะไร ด้วยฟอนต์ไหน ที่พิกัดใด คำสั่งเหล่านี้คือสิ่งที่เรียกกันว่า text layer

PDF จึงมีสองแบบที่ต้องแยกให้ออก

  • PDF ที่มี text layer สร้างจาก Word, Google Docs, เบราว์เซอร์ (Print to PDF) หรือระบบออกรายงาน ตัวอักษรทุกตัวถูกเก็บเป็นข้อมูลพร้อมตำแหน่งบนหน้า คุณลากเมาส์เลือกข้อความได้ ค้นหาด้วย Ctrl+F ได้
  • PDF ที่เป็นรูป มาจากการสแกนหรือถ่ายรูปเอกสาร ข้างในมีแค่ภาพบิตแมปของหน้ากระดาษ ไม่มีคำสั่งวางตัวอักษรเลย จะเลือกข้อความไม่ได้ และต้องใช้ OCR (การอ่านตัวอักษรจากรูป) ถึงจะได้ข้อความ

วิธีเช็กง่ายที่สุดก่อนอัปโหลด: เปิดไฟล์ในโปรแกรมอ่าน PDF แล้วลองลากเลือกข้อความ ถ้าเลือกได้ทีละตัวอักษรคือมี text layer ถ้าเลือกได้ทั้งหน้าเป็นกรอบเดียวคือเป็นรูป

เครื่องมือที่พูดถึงในบทความนี้ อ่านเฉพาะ text layer และไม่มี OCR ไฟล์สแกนจะได้คำตอบ 422 พร้อมรหัส no_text_layer ตรงๆ ไม่ใช่ข้อความว่างเปล่าที่ทำให้เข้าใจผิดว่าเอกสารไม่มีเนื้อหา

ทำไมภาษาไทยถึงอ่านได้บ้างไม่ได้บ้าง

ใน content stream ตัวอักษรไม่ได้เก็บเป็น Unicode โดยตรง แต่เก็บเป็นรหัส glyph ของฟอนต์นั้นๆ และมีตาราง (ToUnicode CMap) บอกว่า glyph ไหนคือตัวอักษรอะไร ไฟล์จาก Word, Google Docs และเบราว์เซอร์ฝังฟอนต์พร้อมตารางนี้มาให้เสมอ ไทยปนอังกฤษปนตัวเลขจึงออกมาครบ แต่ไฟล์เก่าที่ใช้ฟอนต์ไม่ฝังหรือฟอนต์ไทยยุคก่อน Unicode (พบในเอกสารราชการเก่าบางฉบับ) อาจไม่มีตารางนี้ ผลคือตัวอักษรเพี้ยน ซึ่งแก้ฝั่งผู้อ่านไม่ได้ ต้องแก้ที่ต้นทาง

ได้อะไรกลับมา

ผลลัพธ์เป็นข้อความบรรทัดต่อบรรทัดตามที่อ่านได้จากหน้า คั่นหน้าใหม่ด้วย --- ตัวอย่างจากไฟล์ที่ export จาก Word 2 หน้า

plaintext
Quarterly report
Revenue grew in every region this quarter.
Highlights
New office in Chiang Mai opened in August.
Two product launches shipped on time.

---

Outlook
Next quarter focuses on the developer tools line.
Hiring continues in Bangkok and Chiang Mai.

พร้อมข้อมูลกำกับ pages: 2, characters: 259 และ truncated: false

สังเกตว่าไม่มี # นำหน้าหัวข้อ และไม่มีตาราง Markdown เหตุผลอยู่ที่หัวข้อก่อนหน้า: PDF เก็บแค่ว่าตัวอักษรไหนอยู่ตรงไหนด้วยฟอนต์อะไร ไม่ได้เก็บว่า "นี่คือหัวข้อ" หรือ "นี่คือเซลล์ตาราง" การเดาโครงสร้างจากขนาดฟอนต์หรือระยะห่างทำได้แต่ผิดบ่อย เครื่องมือจึงเลือกคืนเฉพาะสิ่งที่แน่ใจ ถ้าต้องการโครงสร้างจริง ขอไฟล์ต้นฉบับ .docx หรือใช้เครื่องมือ layout analysis จะดีกว่า

ไฟล์ภาษาไทยจาก Google Docs ให้ผลแบบนี้

plaintext
รายงานประจำไตรมาส
รายได้เติบโตในทุกภูมิภาคในไตรมาสนี้
สำนักงานใหม่ที่เชียงใหม่เปิดเมื่อเดือนสิงหาคม
ตัวเลขสำคัญ: ยอดขาย 1,250,000 บาท
Mixed line: deadline คือวันศุกร์ที่ 10 ตุลาคม 2569

กรณีที่ผลออกมาไม่ตรงใจ และวิธีรับมือ

หน้าเครื่องมือมีไฟล์ตัวอย่างให้ลองทุกกรณีด้านล่าง เพื่อให้เห็นก่อนว่าไฟล์ประเภทเดียวกับของคุณจะได้อะไร

สองคอลัมน์ เครื่องมืออ่านตามตำแหน่งบนหน้า บรรทัดจากคอลัมน์ซ้ายและขวาที่อยู่ระดับเดียวกันจึงถูกต่อกันเป็นบรรทัดเดียว เช่น Left column, line one. Right column, line one. ถ้าเอกสารเป็นบทความสองคอลัมน์ทั้งฉบับ ผลที่ได้จะอ่านยาก

ตาราง เซลล์ออกมาเป็นคำต่อบรรทัดและช่องว่างระหว่างคอลัมน์หาย เช่น Keyboard 2 1,990 ถ้าต้องการตารางจริง ต้องแยกเองจากระยะห่างหรือใช้ไฟล์ต้นฉบับ

หน้าเปล่าถูกข้าม ไฟล์ 3 หน้าที่หน้ากลางว่าง จะได้ pages: 3 แต่ markdown มีสองก้อนคั่นด้วย --- เดียว จำนวนก้อนข้อความจึงอาจน้อยกว่าจำนวนหน้า

ช่องว่างกลางคำและตัวอักษรแปลก ไฟล์ที่ print จากเบราว์เซอร์วางตัวอักษรทีละกลุ่ม จึงอาจเห็น T wo product launches หรือ quarter . และฟอนต์บางตัวรวม fi fl เป็นตัวเดียว (ligature) ทำให้ได้ office แทน office ใน JavaScript ใช้ text.normalize("NFKC") แยก ligature กลับเป็นตัวอักษรปกติได้ ส่วนช่องว่างแทรกต้องใช้วิธีเฉพาะเอกสาร

ไฟล์ใส่รหัสผ่าน เปิดไม่ได้ ตอบ 422 invalid_input พร้อมเหตุผลจากตัวอ่าน (encrypted PDF: invalid password) ต้องถอดรหัสก่อนอัปโหลด

เรียกใช้ผ่าน API

endpoint POST /api/v1/tools/pdf-text รับไฟล์เป็น multipart/form-data ชื่อฟิลด์ file ไม่ต้องมี key ไม่ต้องสมัคร

Terminal
curl -X POST https://reefsight.tech/api/v1/tools/pdf-text \
  -F "file=@report.pdf"

คำตอบห่อใน data เป็น {markdown, pages, characters, truncated} ส่วน error ทุกกรณีเป็น {error: {code, message, requestId}} โดย requestId ใช้ตามรอยคำขอได้โดยไม่มีเนื้อหาไฟล์

จาก JavaScript บนเว็บไซต์ของคุณ ใช้ FormData ได้ตรงๆ เพราะ API ส่ง CORS header

js
const form = new FormData();
form.append("file", fileInput.files[0]); // <input type="file" accept="application/pdf">

const res = await fetch("https://reefsight.tech/api/v1/tools/pdf-text", {method: "POST", body: form});
const json = await res.json();
if (!res.ok) {
  console.error(json.error.code, json.error.message); // เช่น "no_text_layer"
} else {
  console.log(json.data.pages, json.data.markdown);
}

รหัส error ที่ควรรองรับในโค้ด

HTTPcodeเมื่อไร
422no_text_layerไฟล์สแกน ไม่มีข้อความให้ดึง
422invalid_inputไม่ใช่ PDF, ใส่รหัสผ่าน, เกิน 50 หน้า, ไม่ส่งฟิลด์ file
413too_largeไฟล์เกิน 10 MB ตรวจจากขนาดที่อัปโหลดก่อนอ่าน
429rate_limitedเกิน 30 ครั้งต่อ IP ใน 15 นาที
503busyระบบกำลังอ่านไฟล์พร้อมกันครบ 2 ไฟล์ มี Retry-After ให้

เครื่องมือตัดสินว่าเป็น PDF จาก 5 ไบต์แรกของไฟล์ (%PDF-) ไม่ใช่นามสกุล ไฟล์ PNG ที่เปลี่ยนชื่อเป็น .pdf จะได้ File is not a PDF

ความเป็นส่วนตัวของไฟล์

ไฟล์ถูกส่งไปอ่านใน process ที่แยกออกมาเฉพาะงานนี้และไม่มี secret ใดๆ อยู่ในนั้น ไฟล์อยู่ในหน่วยความจำเท่านั้น ไม่เขียนลงดิสก์ ไม่ log เนื้อหา และถูกลบทันทีที่ตอบกลับ หน้าเว็บของเครื่องมือเรียก API ตัวเดียวกับที่คุณเรียกได้เอง ไม่มีขั้นตอนเพิ่มเติมฝั่งเบราว์เซอร์

ข้อควรระวัง

  • เพดานไฟล์ ไม่เกิน 10 MB และ 50 หน้า เนื้อหาหลังคลายบีบอัดไม่เกิน 3 MB อ่านไม่เกิน 20 วินาที ไฟล์ 51 หน้าถูกปฏิเสธก่อนอ่านเนื้อหา
  • ผลลัพธ์ตัดที่ 2 MB และบอกด้วย truncated: true เอกสารที่ยาวมากควรแบ่งไฟล์ก่อน
  • ไม่มี OCR รูป ตาราง และคอลัมน์ออกมาเป็นข้อความเรียงตามตำแหน่งเท่านั้น
  • โควตา 30 ครั้งต่อ IP ทุก 15 นาที นับรวมครั้งที่ error ด้วย (IPv6 นับทั้ง /64) และระบบอ่านพร้อมกันได้ 2 ไฟล์ทั้งระบบ ถ้าต้องแปลงเป็นชุดใหญ่ ให้เว้นช่วงและรองรับ 429/503
  • ฟอนต์ไม่ฝังแก้ไม่ได้ ถ้าไฟล์ต้นทางไม่มีตาราง Unicode ผลจะเพี้ยนไม่ว่าจะใช้เครื่องมือไหน

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

ไฟล์สแกนหรือรูปถ่ายเอกสารใช้ได้ไหม ยังไม่ได้ จะได้ 422 no_text_layer เพราะไม่มีข้อความในไฟล์ OCR อยู่ในแผนแต่ต้องมีระบบคิวงานก่อน

ทำไมได้ข้อความธรรมดา ไม่มีหัวข้อหรือตาราง Markdown PDF เก็บแค่ตำแหน่งตัวอักษร ไม่มีโครงสร้างหัวข้อหรือตาราง เราจึงคืนบรรทัดตามที่อ่านได้และคั่นหน้าด้วย --- เท่านั้น ถ้าต้องการโครงสร้าง ให้ขอไฟล์ต้นฉบับหรือใช้เครื่องมือ layout analysis

เรียกจาก JavaScript บนเว็บของฉันได้ไหม ได้ endpoint ของเครื่องมือทั้งหมดส่ง Access-Control-Allow-Origin: * แล้ว จึง fetch จากเบราว์เซอร์ได้โดยตรงโดยไม่ต้องมี proxy หรือ key

ลองกับไฟล์ตัวอย่างก่อนใช้กับไฟล์จริง

หน้า reefsight.tech › pdf-to-markdown มีปุ่มโหลดไฟล์ตัวอย่างทุกกรณีในบทความนี้ (Word, ภาษาไทย, สองคอลัมน์, ตาราง, ligature, หน้าเปล่า, สแกน, ใส่รหัสผ่าน) เพื่อให้เห็นว่าแต่ละแบบได้อะไร ลาก PDF ของคุณมาวางได้เลย ฟรี ไม่ต้องสมัคร และไฟล์ไม่ถูกเก็บ

คุยกับทีม Reefsight