Bạn muốn phân tích PDF?
(eliot-jones.com)- 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
startxrefnhư 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
%EOFlẽ 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 (nhoặcf) đượ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
startxrefcó từ đ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í
startxrefcũ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ạn0
Bảng xref có định dạng bất thường
- Có nhiều dạng như
xrefdí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
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...
Ồ..
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.
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.
Ý kiến trên Hacker News
Câu trả lời khá rõ ràng
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 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
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
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
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 đó