Python-pdfkit Chuyển Đổi Mọi Nội Dung Sang PDF

Python-pdfkit Chuyển Đổi Mọi Nội Dung Sang PDF

Bạn đang có nhu cầu muốn chuyển đổi file text, html, hay một trang web sang định dạng pdf ? Bạn muốn tìm một thư viện để thực hiện nó với đầy đủ các chức năng hỗ trợ như giữ nguyên font chữ, định dạng chữ, hỗ trợ Unicode ? Bài viết dưới đây là sự kết hợp thư viện pdfkit & "enginee" wkhtmltopdf nổi tiếng sẽ giải quyết được hết các yêu cầu của các bạn.

PDF là gì ?

PDF (Portable Document Format) là định dạng tài liệu di động, tập tin văn bản khá phổ biến của hãng Adobe. PDF hỗ trợ các loại font chữ, định dạng, màu sắc,...PDF có một ưu điểm vượt trội so với Office Word là một văn bản PDF sẽ được hiển thị giống nhau trên những môi trường làm việc khác nhau, có nghĩa là bạn sẽ không phải lo việc thiếu font chữ khi mở một file pdf trên một máy khác hoặc in ấn mà không bị "vỡ" định dạng.

Chính vì vậy mà càng lúc PDF càng được sử dụng nhiều trong việc trao đổi thông tin trên môi trường internet. Càng lúc càng nhiều nhiều loại báo cáo được yêu cầu xuất sang định dạng PDF (thay vì word như trước khi).

Lựa chọn thư viện chuyển đổi dữ liệu sang PDF.

Trong quá trình tìm hiểu việc convert dữ liệu sang pdf, tôi đã tìm hiểu các thư viện html2pdf, xhtml2pdf,...Các thư viện này được cái là để áp dụng vào bài toán convert dữ liệu thì rất nhanh và nhiều ví dụ. Nhưng khi đưa vào xử lý dữ liệu tiếng Việt có dấu thì .... kết quả thu được là ô vuông và dấu ?
Loay hoay với bài toán convert dữ liệu unicode/UTF-8 mãi không được, đến lúc chán chán muốn "buông" thì tôi nhớ đến một thư viện mã nguồn mở đã từng dùng hồi còn code .NET là wkhtmltopdf. Đây là một phần mềm mã nguồn mở (theo chuẩn LGPLv3), cung cấp giao diện command-line để thực hiện convert HTML (hoặc text) sang định dạng PDF, nó hỗ trợ được các vấn đề liên quan quan font chữ Unicode và các loại định dạng khác nhau.
Thật tuyệt là có thư viện pdfkit của Python hỗ trợ đến "tận răng" cho việc kết hợp với wkhtmltopdf.

Các bạn có thể tìm hiểu thêm về thư viện wkhtmltopdf này tại trang chủ: https://wkhtmltopdf.org/ và pypi của pdfkit: https://pypi.org/project/pdfkit.

Cài đặt wkhtmltopdf

Để bắt đầu cài đặt, chúng ta sẽ đến trang chủ của phần mềm để thực hiện download: https://wkhtmltopdf.org/downloads.html, tùy vào hệ điều hành mà chúng ta sẽ download đúng phiên bản tương ứng. Open-Source này hỗ trợ các hệ điều hành: Windows, macOS và hầu hết các distro của Linux (Debian, Ubuntu, CentOS,...).

Sau khi cài đặt, chúng ta cần thực hiện thiết lập $PATH (CLASS_PATH nếu là Windows) cho hệ điều hành trỏ đến đúng vị trí file executable của phần mềm. Cách thực hiện thiết lập $PATH, các bạn có thể tìm kiếm trên google.
Ví dụ: Tôi đang sử dụng hệ điều hành Windows, tôi cài đặt wkhtmltopdf vào đường dẫn C:\Program Files\wkhtmltopdf, tôi sẽ thực hiện set CLASS_PATH cho wkhtmltopdf với đường dẫn: C:\Program Files\wkhtmltopdf\bin
Sau khi cài đặt & thực hiện thiếp lập $PATH xong, các bạn thực hiện gõ lệnh kiểm tra xem việc làm của chúng ta đã thành công hay chưa bằng cách mở màn hình terminal và gõ: wkhtmltopdf --version
Kết quả hiện ra: wkhtmltopdf 0.12.5 (with patched qt) như vậy là đã thiếp lập thành công.

Cài đặt pdfkit

Pdfkit là một thư viện của Python nên chúng ta sẽ install nó từ "kho" pypi. Để đảm bảo không tạo "rác" trong máy tính, các bạn nên tạo riêng một virtual environment (tham khảo hướng dẫn Làm Chủ Python Virtual Environment - Môi Trường Lập Trình Ảo ?).

Thực hiện install thư viện bằng dòng lệnh:

pip install pdfkit

Kết quả:
Collecting pdfkit
Downloading https://files.pythonhosted.org/packages/57/da/48fdd627794cde49f4ee7854d219f3a65714069b722b8d0e3599cd066185/pdfkit-0.6.1-py3-none-any.whl
Installing collected packages: pdfkit
Successfully installed pdfkit-0.6.1

Sử dụng pdfkit trong chuyển đổi nội dung

1. Thực hiện convert một đoạn text thuần sang file pdf.

Bài toán: Yêu cầu thực hiện convert các message thu được từ hệ thống cảnh báo sang file định dạng PDF để thực hiện gửi vào email tự động cho lực lượng vận hành.

Để giải quyết yêu cầu bài toán này, chúng ta sẽ dùng thư viện pdfkit với câu lệnh sau:

import pdfkit

def text2pdf(message, file_name):
    pdfkit.from_string(message, file_name)


if __name__ == "__main__":
    message = "This is a message from codelearn blogger"
    text2pdf(message, "message_en.pdf")

    message = "Đây là thông điệp từ tác giả bài viết trên codelearn"
    text2pdf(message, "message_vi.pdf")

Lưu đoạn code trên vào file 00_text2pdf.py, sau đó thực hiện chạy từ terminal


>python 00_text2pdf.py
Loading pages (1/6)
Counting pages (2/6)
Resolving links (4/6)
Loading headers and footers (5/6)
Printing pages (6/6)
Done
Loading pages (1/6)
Counting pages (2/6)
Resolving links (4/6)
Loading headers and footers (5/6)
Printing pages (6/6)
Done

Ta thu được kết quả:

Chữ tiếng Anh thì hiển thị bình thường nhưng tiếng Việt thì bị lỗi font chữ unicode. Để giải quyết được vấn đề này thì pdfkit cho phép các bạn thực hiện khai báo thêm một số "cấu hình" trước khi thực hiện convert.

def text2pdf(message, file_name):
    options = {'encoding': "UTF-8"}
    pdfkit.from_string(message, file_name, options=options)

Và đây là kết quả:

2. Thực hiện convert một trang web sang pdf.

Ví dụ: Chúng ta cùng nhau convert dữ liệu của url https://www.google.com/search?q=codelearn.io
Lưu đoạn source code dưới đây vào file 01_url2pdf.py

import pdfkit
pdfkit.from_url("https://www.google.com/search?q=codelearn.io", "code_learn_google.pdf")

Đứng từ terminal, thực hiện convert dữ liệu:

>python 01_url2pdf.py
Loading pages (1/6)
Counting pages (2/6)
Resolving links (4/6)
Loading headers and footers (5/6)
Printing pages (6/6)
Done

Chúng ta thu được file kết quả:


Khá đơn giản đúng không. Nhưng đôi khi chúng ta sẽ gặp lỗi liên quan đến việc load dữ liệu trực tiếp từ iframe hoặc responsive của web.

Ví dụ: các bạn thử thay đường dẫn ở trên bằng đường dẫn: https://codelearn.io/sharing/post/quangvinh1986 - danh sách các bài viết của tôi trên codelearn

pdfkit.from_url("https://codelearn.io/sharing/post/quangvinh1986", "my_codelearn.pdf")

Khi thực hiện convert, chúng ta sẽ nhận được Warning cảnh báo:


>>> import pdfkit
>>> pdfkit.from_url("https://codelearn.io/sharing/post/quangvinh1986", "my_codelearn.pdf")
Loading pages (1/6)
libpng warning: iCCP: extra compressed data==> ] 74%
Warning: A finished ResourceObject received a loading finished signal. This might be an indication of an iframe taking too long to load.
Warning: A finished ResourceObject received a loading progress signal. This might be an indication of an iframe taking too long to load.
Counting pages (2/6)
Resolving links (4/6)
Loading headers and footers (5/6)
Printing pages (6/6)
Done
True

Và khi mở ra, chúng ta sẽ thu được giao diện bị khuất một phần dữ liệu:

Funny question: Các bạn có thấy tại sao khi thực hiện convert dữ liệu text thì chúng ta cần thêm đoạn khai báo unicode mà convert cả trang web thì không cần không?
Câu trả lời sẽ có ở phần bên dưới.

3. Thực hiện convert file dữ liệu html sang file pdf

Yêu cầu: Thực hiện chuyển đổi nội dung file html sang pdf, yêu cầu giữ nguyên được format trên file html như khi hiển thị trên web-browser.

Để thực hiện được phần này, chúng ta sẽ tạo ra một file html có nội dung như bên dưới và lưu nó lại với cái tên demo.html

<html>
    <head>
        <title>
            Chuyển đổi mọi nội dung sang pdf bằng pdfkit và wkhtmltopdf 
        </title>
        
    </head>

    <body>
        <p>This is a message from codelearn blogger</p>
        <p>Đây là thông điệp từ tác giả bài viết trên codelearn</p>
        <p style="color: red;">Red text</p>
        <p style="color: blue;">Blue text</p>
        <p style="color: green;">Green text</p>
    </body>
</html>

Để đơn giản thì chúng ta sẽ tạo ra file 03_html2pdf.py chứa code thực hiện convert sẽ đặt "tạm" ở cùng thư mục với demo.html,

import pdfkit

def html2pdf(html_file_name, pdf_file_name):
    pdfkit.from_file(html_file_name, pdf_file_name)


if __name__ == "__main__":
    html2pdf("demo.html", "demo.pdf")

Sau đó, thực hiện khởi chạy file trên từ màn hình terminal

>python 03_html2pdf.py
Loading pages (1/6)
Counting pages (2/6)
Resolving links (4/6)
Loading headers and footers (5/6)
Printing pages (6/6)
Done

Thu được file kết quả:

Lại một lần nữa font chữ tiếng Việt lại làm phiền chúng ta. Quay lại câu hỏi phía trên của tôi, tại sao các website mà chúng ta thực hiện convert lại không bị lỗi font chữ mà cái file html bé tẹo phía trên lại bị lỗi font chữ ?
Câu trả lời nằm ở dữ liệu html tạo ra file html và response content chúng ta nhận được từ các website. Một cái có thẻ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> đặt trong thẻ <head> và một cái thì không có.

Bây giờ chúng ta chúng ta thực hiện thêm đoạn thẻ <meta> trên vào file html và thực hiện chạy lại file convert. Kết quả thu được:

Kết quả thì đã có dữ liệu unicode được hiển thị thành công nhưng tốt nhất là chúng ta vẫn nên thêm vào các khai báo cấu hình liên quan đến unicode khi thực hiện convert file.

def html2pdf(html_file_name, pdf_file_name):
    options = {'encoding': "UTF-8"}
    pdfkit.from_file(html_file_name, pdf_file_name, options=options)

Ngoài việc hỗ trợ UTF-8 encoding, pdfkit còn hỗ trợ nhiều loại tham số cấu hình khác nhau. Chúng ta sẽ tìm hiểu nó trong phần tiếp theo.

Một số lưu ý khi sử dụng pdfkit và wkhtmltopdf

1. Các loại tham số cấu hình mà pdfkit hỗ trợ.

Đầu tiên là cấu hình giống như khi chúng ta thực hiện in ấn một văn bản ra máy in văn phòng:

options = {
        'page-size': 'A4',
        'orientation': 'Portrait',
        'quiet': '',
        'dpi': 96,
        'margin-top': '0.2in',
        'margin-bottom': '0.2in',
        'margin-right': '0.72in',
        'margin-left': '0.72in',
        'encoding': "UTF-8"
    }

page-size: Khổ giấy của file pdf sau khi convert. Mặc định là A4, có thể thay đổi thành cách giá trị Letter, A5,...

orientation: Mặc định là khổ giấy xoay dọc (Portrait), có thể thay đổi sang xoay ngang (Landscape)

quiet: Hiển thị log trong quá trình convert sang pdf. Giá trị ưng với các level đặt log (INFO, WARNING,...). Thường đặt '' trên production để tránh lỗi hiển thị terminal.

dpi: Dots Per Inch - Độ đậm nét của ký tự trên file pdf. Mặc định là 96. Tham số này không nên thay đổi. Với Windows, UNIX thì wkhtmltopdf sẽ reset về 96pdi, nếu chuyển qua convert trên máy OSX thì mới có thể tăng số lượng dpi lên được.

margin: Giãn cách từ vị trí các mép tương ứng đến phần dữ liệu đầu tiên của nội dung của file. Tùy theo nhu cầu mà chúng ta thay đổi hoặc giữ nguyên mặc định.

Do pdfkit thực hiện dựa trên thư viện gốc wkhtmltopdf nên các tham số cấu hình của wkhtmltopdf cũng có thể sử dụng với pdfkit (ví dụ: enable/disable css, javascript, ...), Các tham số này, các bạn có thể tham khảo thêm từ help của wkhtmltopdf

wkhtmltopdf -H

2. Xử lý trường hợp không có quyền thiết lập $PATH.

Đôi khi bạn không có quyền thực hiện set $PATH trên hệ thống hoặc hệ thống bị clear mất CLASS_PATH, khi thực hiện chạy các lệnh convert sẽ đẩy ra một trong số các cảnh báo:

IOError: 'No wkhtmltopdf executable found'
OSError: 'No wkhtmltopdf executable found'

Nếu mà vẫn muốn dùng pdfkit, pdfkit sẽ cung cấp một tham số để bạn thực hiện trỏ đường dẫn đến vị trí cài đặt wkhtmltopdf.

Đầu tiên thì phải tìm xem đường dẫn đến file chạy wkhtmltopdf trên hệ điều hành (exe trên windows hoặc file class trên UNIX).

Trên Windows thì chúng ta chỉ cần thực hiện search wkhtmltopdf.exe là ra được file và vị trí file.
Trên UNIX thì chúng ta dùng command-line: which wkhtmltopdf

Thường thì đường dẫn sẽ lưu ở: /usr/local/bin/wkhtmltopdf

Thực hiện copy dữ liệu PATH trên và đưa vào cấu hình tham số:

wkhtmltopdf_path = "/usr/local/bin/wkhtmltopdf"
path_config = pdfkit.configuration(wkhtmltopdf=wkhtmltopdf_path)
pdfkit.from_file(html_file_path, pdf_file_path, options=options, configuration=config)

3. Thực hiện convert nhiều trang web nội dung vào một file pdf

Thư viện pdfkit cho phép chúng ta thực hiện "ghép" nội dung nhiều page vào một file pdf duy nhất như ví dụ dưới đây:

import pdfkit

urls = ["https://www.google.com/search?q=codelearn.io", "https://www.google.com/search?q=codelearn.io+%2B+quangvinh1986",
        "https://www.google.com/search?q=codelearn.io+python"]
pdfkit.from_url(urls, "multi_codelearn.pdf")

Khi thực hiện chạy, ta thu được 1 file dữ liệu có nội dung:

Nhìn các chỉ mục ta cũng có thể thấy file pdf phía trên là tổng hợp dữ liệu của 3 url đầu vào.

Các bạn có thể làm tương tự với các function .from_string(), .from_file()

Kết 

Trên đây, tôi đã thực hiện giới thiệu với các bạn về thư viện pdfkit, chúc các bạn có thể chủ động được việc giải quyết các vấn đề với file pdf. Toàn bộ source code được lưu trữ tại python-pdfkit.
Cảm ơn các bạn đã đọc bài viết của tôi.