15 điểm bởi GN⁺ 2025-08-04 | 5 bình luận | Chia sẻ qua WhatsApp
  • Phân tích PDF về lý thuyết phải hoạt động dựa trên trình tự và cấu trúc rõ ràng, nhưng các tệp thực tế thường không tuân theo đặc tả này
  • Nhiều lỗi và sự không nhất quán xảy ra khi tìm con trỏ cross-reference (xref) và các offset
  • Trên thực tế, rất nhiều vấn đề phát sinh do dữ liệu thừa trước phần header PDF hoặc do con trỏ và offset nằm sai vị trí
  • Cũng có nhiều trường hợp bản thân bảng xref của PDF không rõ ràng hoặc được định dạng sai
  • Vì vậy, các trình xem phổ biến đều triển khai thêm logic để hỗ trợ cả các tệp PDF phi tiêu chuẩn

Cách tiếp cận lý tưởng đối với việc phân tích PDF

  • Về mặt lý thuyết, phân tích PDF diễn ra theo các bước cố định
    • Tìm chú thích header phiên bản ở phần đầu tệp
    • Tìm con trỏ cross-reference (xref)
    • Thu thập mọi offset của đối tượng
    • Tìm từ điển trailer để truy cập cấu trúc catalog tổng thể

Giới thiệu về đối tượng PDF

  • Đối tượng PDF là đơn vị bao bọc và lưu trữ nhiều thành phần PDF như số, chuỗi, từ điển, v.v.
  • Mỗi đối tượng tồn tại giữa các marker obj/endobj
  • Các đối tượng được liên kết với nhau bằng tham chiếu gián tiếp (indirect reference, ví dụ: 16 0 R)
  • Cách phân chia đối tượng trong tệp là linh hoạt, nhưng một số loại đối tượng bắt buộc phải là tham chiếu gián tiếp

Tìm offset cross-reference

  • Về mặt cấu trúc, PDF có bảng cross-reference (xref), đóng vai trò như chỉ mục vị trí của các đối tượng
  • Ở cuối tệp, một vị trí byte cụ thể được chỉ ra bằng cú pháp startxref như một con trỏ
  • Con trỏ này chỉ định vị trí xref, nhưng giữa đặc tả và tệp thực tế có khác biệt. Ví dụ, marker %EOF lẽ ra phải là dòng cuối cùng, nhưng trong PDF ngoài thực tế, nó có thể xuất hiện ở bất kỳ đâu trong 1.024 byte cuối
  • Trong các tệp thực tế, có thể bắt gặp nhiều biến thể như lỗi định dạng con trỏ (startref, v.v.) hoặc thiếu xuống dòng

Tìm offset của đối tượng

  • Bảng xref gồm xref, số bắt đầu của đối tượng, rồi đến số lượng đối tượng; sau đó offset / số thế hệ / trạng thái của từng đối tượng (n hoặc f) được ghi trên từng dòng
  • Có thể có nhiều bảng xref, hoặc chúng liên kết với nhau qua mục /Prev

Xác định vị trí từ điển trailer

  • Phía trên marker startxreftừ điển trailer, chứa siêu dữ liệu bắt buộc để tìm đối tượng gốc
  • Dựa trên đối tượng gốc, có thể bắt đầu diễn giải toàn bộ cấu trúc

Môi trường thực tế: những vấn đề ngoài dự kiến

  • Có rất nhiều tệp không tuân thủ đặc tả PDF, nên trình phân tích thông thường khó xử lý

  • Những trường hợp thường thất bại khi tìm con trỏ cross-reference

    • Con trỏ không nằm ở cuối tệp hoặc trong 1.024 byte cuối
    • Lỗi gõ (startref, v.v.)
    • Định dạng ngoại lệ
  • Trong khảo sát 3.977 mẫu PDF thực tế, khoảng 0,5% có lỗi khai báo xref

Nội dung PDF bắt đầu tại offset khác 0

  • Nếu có dữ liệu rác (junk) trước header, mọi offset byte sẽ bị lệch, khiến vị trí startxref cũng sai theo
  • Cần tính lại offset dựa trên vị trí của header, đồng thời kiểm tra cả hai vị trí
  • Trường hợp này chiếm khoảng 50% tổng số lỗi

Con trỏ xref trỏ vào giữa bảng xref

  • Offset được chỉ định đôi khi có thể rơi vào chính giữa nội dung của bảng xref
  • Phát hiện khoảng 5 trường hợp trong 3.977 mẫu

Con trỏ nằm gần xref

  • Nhiều khi con trỏ không chính xác hoàn toàn, nhưng chỉ lệch khỏi xref đúng bằng khoảng trắng hoặc ký tự xuống dòng ngay trước hoặc sau nó

Con trỏ đúng nhưng offset xref lại sai

  • Bản thân offset được ghi trong bảng xref cũng có thể sai
  • Có thể một số đối tượng là đúng, còn những đối tượng khác lại có lỗi offset

Con trỏ đầu tiên bình thường nhưng offset trước đó (/Prev) lại bất thường

  • Có nhiều trường hợp con trỏ /Prev được tạo ra khi chỉnh sửa PDF chứa giá trị sai, chẳng hạn 0

Bảng xref có định dạng bất thường

  • Có nhiều dạng như xref dính liền với số mà không có xuống dòng, có nhiều mục hơn số đối tượng đã khai báo, hoặc có dữ liệu rác chen giữa bảng
  • Nhiều trường hợp như vậy đã được báo cáo thành issue trong PdfPig và các dự án khác

Kết luận

  • Theo đặc tả, việc phân tích PDF phải được xử lý theo một trình tự chuẩn hóa, nhưng trong thực tế nhiều tệp không như vậy nên phát sinh rất nhiều vấn đề khi phân tích
  • Các trình xem PDF dùng trong thực tế mặc định đều bao gồm khả năng mở rộng hỗ trợ PDF không đúng chuẩn
  • Nội dung tóm tắt lần này chỉ đề cập đến một phần nhỏ của việc phân tích trong đặc tả PDF (22 trên tổng 1300 trang)

5 bình luận

 
mhj5730 2025-08-06

Nội dung tóm tắt lần này chỉ đề cập đến việc phân tích một phần nhỏ của đặc tả PDF (22 trang trong tổng số 1300 trang) <-... 1300 trang đúng là quá khủng...

 
kaydash 2025-08-05

Ồ..

 
spp00 2025-08-05

Nói thật thì PDF là một định dạng thân thiện với con người, được tạo ra để bảo toàn tối đa bố cục do con người làm ra, và lại cực kỳ kém tương thích với máy móc.

 
reagea0 2025-08-05

Tôi hiểu mà. Thật ra, tôi cũng không chắc là nó có dễ đọc không nữa.. quá nặng nề và bất tiện.

 
GN⁺ 2025-08-04
Ý kiến trên Hacker News
  • Câu trả lời khá rõ ràng

    1. PDF hỗ trợ đính kèm metadata ở bất kỳ định dạng nào bạn muốn
    2. Mọi phần mềm tạo PDF đều phải đính kèm cùng một loại thông tin theo cách máy có thể dễ dàng đọc được
    3. Khi đó, người muốn parse PDF chỉ cần nhìn vào metadata
      Thực tế thì, tên tôi là Geoff, nhưng một nửa số trình phân tích CV lại nhận tên tôi thành riêng "Geo" và "ff"
      Điều này là do cách văn bản được đưa vào PDF, và đây là vấn đề tiếp diễn ở nhiều ứng dụng nguồn khác nhau
    • Parse PDF và parse nội dung PDF là hai việc hoàn toàn khác nhau
      Việc parse file PDF vốn đã đau đầu, nhưng bản thân PDF lại dựa trên kiểu "đặt một thứ gì đó tại một vị trí chỉ định", chứ không phải văn bản được xác định rõ trong các hộp ranh giới, nên để trích xuất từ ngữ bạn phải đoán ký tự nào đi cùng với nhau
      Nếu muốn hỗ trợ các trình phân tích CV, có lẽ nên chú ý đến accessibility tree
      Không phải mọi trình render PDF đều xuất được accessibility PDF, nhưng accessibility PDF ít nhất có thể giúp đọc đúng những thứ như tên người
      Vấn đề với "ff" có lẽ là do bộ phân tích CV không xử lý được ký tự không phải ASCII, ví dụ ligature như ff
      Có thể cấu hình để trình render PDF không tạo ligature, nhưng như vậy văn bản có thể sẽ xấu đi

    • Có cảm giác đang kỳ vọng quá nhiều vào từ "nên(should)"
      Nếu việc dùng PDF trong thực tế khá thù địch thì có vẻ mọi người sẽ không nghĩ đến mức đó
      Ngay từ việc nộp CV dưới dạng PDF đã có mục đích là không cho bên trung gian sửa được, và chuyện "chỉnh sửa" cũng có nhiều lý do khác nhau như che bằng cách vẽ hộp lên ảnh, hay làm bảng ở dạng PDF thay vì CSV để khiến việc phân tích khó hơn

    • Thực tế cách này cũng có lúc hoạt động tốt, và một số ứng dụng đang dùng cách đó
      Chỉ là vẫn còn vấn đề hai biểu diễn (nội dung chính/metadata) không thực sự khớp nhau

    • Có người thắc mắc vậy còn các bản scan chữ viết tay hay tài liệu scan khác thì sao, nếu máy quét và máy tính gia đình phổ thông không có hỗ trợ OCR hoàn hảo

    • Có lẽ vấn đề ff phát sinh vì nó được render thành ligature

  • Tôi là nhà sáng lập Tensorlake
    Chúng tôi tạo ra API parse tài liệu cho developer
    Đây là lý do cách tiếp cận Computer Vision hoạt động tốt trong thực tế khi parse PDF
    Chỉ dựa vào metadata trong file thì không thể mở rộng với nhiều nguồn PDF đa dạng
    Vì vậy, cách làm là chuyển PDF thành ảnh, áp dụng trước mô hình nhận diện layout, sau đó chạy các mô hình chuyên biệt như nhận diện văn bản và bảng, rồi ghép các mảnh lại với nhau để thu được kết quả đủ tốt ngay cả trong các lĩnh vực đòi hỏi độ chính xác cao

    • Cách này thoạt nhìn có vẻ buồn cười, nhưng thực ra có lẽ là giải pháp thực tế nhất
      PDF về bản chất là định dạng được thiết kế để biểu diễn bố cục cho con người đọc, không phải để máy đọc, mà tập trung vào hiển thị đẹp mắt
      Vì vậy, cách tiếp cận bắt chước cách con người đọc có vẻ hợp lý
      Dù vậy, vẫn đáng tiếc là suốt hơn 30 năm PDF không bổ sung được khả năng máy đọc tốt hơn
      Tôi tự hỏi đã thiếu động lực gì mà không thể làm được điều đó
      Nếu ai có góc nhìn về chuyện này thì tôi rất muốn nghe

    • Có điểm hơi hài
      In PDF ra rồi scan lại để gửi email thường bị xem là trò lố, nhưng trong parse PDF thì về cơ bản ta lại đang làm đúng điều đó
      Việc phải dùng cách tiếp cận như vậy là một thực tế khá bực bội
      Chẳng ai parse HTML theo kiểu đó cả

    • Tôi là đồng sáng lập Nutrient.io, đã làm việc với PDF hơn 10 năm
      Giống như trình duyệt web, các trình xem PDF phải chấp nhận một lượng PDF cực kỳ đa dạng
      Vì PDF đã quá cũ, các công cụ tạo file thường tự ý chỉnh sửa miễn là hiển thị đúng trên trình xem mà họ dùng
      Vì thế công ty chúng tôi đã tạo ra SDK xử lý tài liệu bằng AI, một REST API nhận PDF làm đầu vào và trả về dữ liệu có cấu trúc dưới dạng JSON
      Không chỉ dựa vào phương pháp thị giác, mà còn nhờ kinh nghiệm tiền xử lý/hậu xử lý mang tính cấu trúc, nên chúng tôi cho kết quả tốt hơn cả về hiệu năng lẫn chi phí so với cách thuần thị giác
      Nếu bạn không muốn tự mình đau đầu xử lý PDF và muốn tập trung vào công việc cốt lõi thì có thể nó sẽ hữu ích
      https://www.nutrient.io/sdk/ai-document-processing

    • Nhân có người rành cấu trúc bên trong của PDF, tôi có một câu hỏi
      Tôi luôn thắc mắc vì sao mupdf-gl lại nhanh hơn hẳn mọi chương trình khác (ít nhất trên Linux desktop mặc định)
      Tốc độ tìm kiếm trong các PDF lớn của nó vượt trội thấy rõ, và tôi luôn tự hỏi vì sao các trình xem khác không thể nhanh như vậy
      Nếu ai có góc nhìn liên quan thì tôi rất muốn nghe

    • Rốt cuộc thì khi render PDF thành ảnh, bạn đang thuê ngoài việc parse cho phần mềm được dùng để render ảnh đó

  • Từ lâu tôi đã nghĩ rằng chúng ta nên rời bỏ kiểu giao tiếp tài liệu nặng về layout
    Tức là bản thân layout được dàn dựng chuyên nghiệp thực ra gần giống một tập quán cũ hơn, và hầu như không liên quan đến việc hiểu nội dung thực tế
    Ví dụ, các tài liệu nộp cho cơ quan quản lý thường cực kỳ dày, và để khớp các quy tắc về layout thì người ta phải mất rất nhiều thời gian thao tác trong Microsoft Word
    Sau đó họ nộp dưới dạng DOCX hoặc PDF để đảm bảo layout, nhưng các định dạng này lại rất không phù hợp để chương trình tự động trích xuất hay xử lý nội dung
    LLM vẫn có thể đọc các file này, nhưng chi phí tính toán cao hơn rất nhiều so với các file thân thiện với máy như text, markdown, XML, JSON

    Thay thế vào đó, tôi nghĩ đến khả năng chuẩn hóa các định dạng đơn giản theo hướng 'machine-first', 'content-first' như JSON, XML, dựa trên HTML, v.v.
    Chỉ cần có cấu trúc tối thiểu và thông tin nhúng hình ảnh, còn khi con người đọc thì ứng dụng xem có thể tái dựng sao cho đẹp mắt
    Việc xử lý bằng máy sẽ dễ hơn rất nhiều
    Dù đã có những định dạng tương tự như HTML/trình duyệt hay EPUB, tôi vẫn nghĩ đã đến lúc cần thay thế cách làm cổ điển
    Tôi hy vọng làn sóng LLM sẽ đưa mọi thứ theo hướng đó, và để việc parse PDF đắt đỏ chỉ còn là một đường ống truyền thống của quá khứ

    • Tôi đồng ý PDF có vấn đề, nhưng có thực sự DOCX cũng tệ đến vậy không
      Tôi chưa từng viết trình phân tích DOCX, nhưng DOCX dựa trên XML, và trừ khi bạn chỉ định layout một cách tường minh thì không phải mọi thứ đều bị tuyệt đối hóa theo tọa độ, nên nếu JPEG là 0 điểm, PDF là 15 điểm, markdown là 100 điểm, thì tôi đoán DOCX cỡ khoảng 80 điểm về độ dễ xử lý
  • Tôi nghĩ đây là một bản tổng kết rất hay, và còn có một điểm bổ sung thú vị mà tôi thấy đáng chú ý
    Chuỗi incremental-save: offset của startxref đầu tiên thì ổn, nhưng các liên kết /Prev mà Acrobat cứ nối thêm mỗi lần chỉnh sửa nhiều khi lại trỏ thiếu vài byte so với xref kế tiếp
    Hầu hết trình xem (PDF.js, MuPDF, thậm chí cả Adobe Reader) đều ngu ngốc quét token obj trên toàn bộ file để dựng lại bảng mới, còn các parser bám sát đặc tả thì phát nổ
    Nếu muốn xử lý các tài liệu ngoài đời thực đã bị nhiều ứng dụng chỉnh sửa lặp đi lặp lại, thì kiểu đường phục hồi như vậy là bắt buộc

    • Nhận xét đó rất đúng, đây là một ca lỗi tôi thường thấy trong tập mẫu
      Rất hay gặp trường hợp tham chiếu trước đó, hoặc một mắt xích nào đó trong chuỗi, trỏ ra offset nằm ngoài file, offset bằng 0, hoặc giá trị sai
      Động lực để tôi viết bài này là việc cải tổ logic parse ban đầu trong dự án PdfPig của mình
      Lúc đầu tôi port mã từ Java PDFBox, nhưng rồi muốn đổi sang cách nhanh hơn và đơn giản hơn
      Logic mới sẽ quét toàn bộ file nếu bỏ lỡ dù chỉ một xref table/stream nào đó, và trong đường phục hồi thì chỉ tin các offset đó
      Nhưng nó chắc chắn chậm hơn trước, và tôi khó mà tự tin liệu thay đổi đó có thực sự ổn không
      Tôi đang dùng bộ kiểm thử 10.000 file để lần ra các trường hợp kỳ lạ đủ kiểu
      https://github.com/UglyToad/PdfPig/pull/1102
  • Nếu có các giả định hoạt động tốt và một parser object PDF phù hợp thì có vẻ sẽ dễ, nhưng tôi nghĩ thực tế hoàn toàn không như vậy
    Tình huống này đúng kiểu địa ngục PDF
    PDF không phải là đặc tả, mà là một thỏa thuận xã hội, một kiểu 'vibe'
    Càng vùng vẫy bạn càng lún sâu hơn, và giờ có cảm giác tất cả chúng ta đang sống trong một vũng lầy nằm ngoài tầm nhìn của Chúa
    Tôi đã bật cười khi đọc đoạn này

    • Đây là câu đùa rằng bài này nghe như thể do James Mickens viết
  • Trước câu hỏi "Có muốn parse PDF không", tôi có thể khẳng định chắc chắn là không đời nào
    Lý do đã được bài gốc giải thích rất rõ

    • Tôi chỉ mong ngân hàng của mình cung cấp dữ liệu ở định dạng dễ đọc hơn, nhưng cho đến lúc đó thì đành chịu

    • Tôi đã từng mắc sai lầm đó rồi, và không định lặp lại nữa

  • Với tư cách người từng viết PDF parser, tôi thấy PDF đúng là một định dạng rất kỳ quái
    Có lẽ chính thiết kế lai giữa nhị phân và văn bản ngay từ gốc đã tạo nên sự quái dị này
    Tôi cũng đoán rằng vấn đề offset xref hơi sai sai kia bắt nguồn từ bug trong lúc xử lý chuyển đổi xuống dòng LF/CR
    Một điều bài viết chưa nhắc đến là PDF mới hơn (v1.5+) thường chứa "xref stream" thay vì xref table văn bản thuần
    Từ v1.6 trở lên, object bản thân nó cũng có thể được chứa trong object stream

    • Tôi cũng ngạc nhiên là bài không đi xa hơn khỏi mức xref table đơn giản để nói đến stream và nén
      Mọi thứ trông có vẻ ổn cho đến khi object bạn cần lại nằm trong một stream, mà stream đó thì dùng một biến thể nén PNG, hoặc offset lại nằm trong một xref stream nén flate
      Chưa kể còn có nhiều phiên bản tài liệu chồng lẫn lên nhau nên việc xác định đâu mới là phần mới nhất cũng rất phức tạp
      Tài liệu PDF 1.7 thì khá dễ kiếm, nhưng mãi đến chỉ 2 năm trước, tài liệu đặc tả PDF 2.0 vẫn còn bị chặn sau paywall
  • PDF là định dạng không được thiết kế cho streaming
    Trailer dictionary nằm ở cuối khiến việc parse trở nên khó khăn cho đến khi toàn bộ file được tải xong
    Tuy vậy, cũng có "streaming-capable PDF", nếu thông tin cần thiết nằm ở phần đầu thì có thể render ngay trang đầu tiên mà không cần tải hết file, dù các phần còn lại có thể không được như vậy
    Tôi cũng nên nói trước là gần đây tôi không còn làm việc sát với mảng PDF nữa

    • Dù có footer, nếu website hỗ trợ Range Request và dùng đúng header Content-Length thì PDF vẫn có thể stream được
      Một trình đọc streaming có thể gửi HEAD request, yêu cầu vài trăm byte cuối file để lấy con trỏ và bảng, rồi tiếp tục nhận phần còn lại
      Nó không phù hợp với PDF được tạo theo thời gian thực, nhưng với các web server đã tồn tại từ lâu thì chỉ thêm 1-2 RTT là đủ
      Đáng tiếc là hiếm ai quan tâm đến parser dựa trên Range theo từng file, nhưng tôi nghĩ về mặt kỹ thuật thì không phải là bất khả thi

    • Đúng vậy, có một định dạng gọi là Linearized PDF, được thiết kế để có thể hiển thị nhanh trang đầu tiên mà không cần tải toàn bộ file
      Trong phần tóm tắt, tôi đã lược bỏ cách này vì nó kéo theo khá nhiều giải thích phụ

  • Một trong những dự án đầu tiên tôi thử khi mới học Python là viết PDF parser
    Tôi định tự động trích xuất bản đồ cho campaign DnD, nhưng kết quả là thất bại thôi rồi (cười)

  • Tôi từng viết một trình đọc TIFF
    TIFF cũng nổi tiếng là dễ ghi nhưng khó đọc
    Tôi thấy PDF cũng thuộc đúng nhóm đó