Convert HTML sang PDF bằng Node.js + Puppeteer

0

Cũng khá lâu rồi, tầm mấy năm trước, mình có tạo một ứng dụng Android để convert webpage HTML sang PDF. Mặc dù tính năng đơn giản, nhưng mình thấy cũng có khá nhiều người sử dụng. Ưu điểm của PDF là bạn có thể mang đi in ấn mà không sợ mất định dạng, hầu hết máy tính bây giờ đều cài sẵn ứng dụng đọc PDF…

Về kỹ thuật chung, bạn có 2 lựa chọn:

  • Một là convert trực tiếp trên mobile. Với giải pháp này, bạn sẽ không cần tới server làm trung gian, tất cả sẽ xử lý hết trên ứng dụng mobile. Nhưng nhược điểm là cần nhiều tài nguyên để xử lý, mà bạn biết rồi đấy, tài nguyên của điện thoại luôn hạn chế, không thể nào so bì với server được.
  • Hai là tạo một server để convert html sang PDF, ứng dụng mobile chỉ có nhiệm vụ download file PDF về mà thôi.

Bài viết hôm nay, mình sẽ hướng dẫn các bạn tạo một server sử dụng Node.js để convert HTML sang PDF.

Kỹ thuật sử dụng trong bài viết:

Chúng ta bắt đầu thôi nào!

Convert HTML sang PDF sử dụng Node.js + Puppeteer

Trước khi bắt tay thực hành, chúng ta cùng ngó qua xem Pupperteer là gì đã nhé?

Nguyên bản định nghĩa của tác giả:

Puppeteer is a Node library which provides a high-level API to control Chrome or Chromium over the DevTools Protocol. Puppeteer runs headless by default, but can be configured to run full (non-headless) Chrome or Chromium.Puppeteer Official Document

Về cơ bản, bạn hiểu Puppeteer là một trình duyệt chạy trên nền Node.js. Nếu bạn đọc tài liệu của Puppeteer, điều đầu tiên tác giả nhắc tới đó là khả năng tạo ảnh chụp màn hình và PDF từ các trang web.

🔥 Bài viết hướng dẫn tạo ảnh chụp màn hình với Puppeteer: Chụp ảnh trang web với Node.js + Puppeteer

#1- Cài đặt và sử dụng Puppeteer cơ bản

Để cài đặt Puppeteer, bạn thực hiện giống như mọi module khác thôi. Dùng NPM thì gõ lệnh sau:

npm i puppeteer
# or "yarn add puppeteer"

Còn đây là đoạn code sử dụng Puppeteer cơ bản:

const puppeteer = require('puppeteer')
 
async function printPDF() {
  const browser = await puppeteer.launch({ headless: true });
  const page = await browser.newPage();
  await page.goto('https://vntalking.com', {waitUntil: 'networkidle0'});
  const pdf = await page.pdf({ format: 'A4' });
 
  await browser.close();
  return pdf
})

Trong ví dụ trên, chúng ta đơn giản là truyền vào một URL và yêu cầu Puppeteer chuyển sang PDF.

Đầu tiên, chúng ta mở trình duyệt (việc tạo PDF chỉ được hỗ trợ ở chế độ headless, đó là lý do chúng ta phải tạo thiết lập giá trị headless: true). Sau đó, chúng ta mở một trang mới, đặt chế độ xem và điều hướng tới URL được cung cấp (trong ví dụ này là https://vntalking.com).

Tùy chọn waitUntil: ‘networkidle0’ thực chất là chúng ta thiết lập giá trị timeout cho Puppeteer. Nếu không có kết nối mạng trong 500ms, Puppeteer sẽ tự động kết thúc request tới URL. Tham khảo các thông số cài đặt API của Pupppeteer tại đây.

Cuối cùng, chúng ta lưu PDF vào một biến và đóng trình duyệt và trả lại PDF file.

Lưu ý: Hàm page.pdf({...} nhận một option. Với option này, bạn có thể cấu hình để lưu file PDF vào disk theo một đường dẫn chỉ định. Nếu bạn không chỉ định điều này, hàm này chỉ trả về một buffer của PDF mà thôi.

Trong một số trường hợp, bạn cần tạo PDF từ các trang web cần phải đăng nhập. Ví dụ: Bạn muốn xuất PDF từ trang Admin dashboard để làm báo cáo. Để làm được điều này, bạn cần phải điều hướng tới trang đăng nhập, kiểm trang form đăng nhập để biết ID hoặc tên rồi đền thông tin đăng nhập.

await page.type('#email', process.env.PDF_USER)
await page.type('#password', process.env.PDF_PASSWORD)
await page.click('#submit')

Lưu ý: Thông tin đăng nhập nên lưu vào các biến môi trường, không nên mã hóa chúng.

#2- Xử lý Style

Puppeteer cũng có một số giải pháp để bạn thao tác và xử lý style CSS của trang HTML trước khi tạo PDF.

await page.addStyleTag({ content: '.nav { display: none} .navbar { border: 0px} #print-button {display: none}' })

#3- Send PDF file tới client

Sau khi chúng ta đã tạo PDF xong ở trên server, nhiệm vụ tiếp theo là gửi chúng tới client.

Để client có thể download PDF này về, nếu bạn không lưu PDF thành file trên disk ở server, bạn sẽ cần phải gửi buffer đó (phải đúng content-type) tới client.

printPDF.then(pdf => {
    res.set({ 'Content-Type': 'application/pdf', 'Content-Length': pdf.length })
    res.send(pdf)

Phía client chỉ đơn giản là gửi request tới server để lấy PDF về.

Ví dụ mã javascript phía client:

function getPDF() {
 return axios.get(`${API_URL}/your-pdf-endpoint`, {
   responseType: 'arraybuffer',
   headers: {
     'Accept': 'application/pdf'
   }
 })

Khi có được buffer, việc chuyển buffer sang PDF cũng do client thực hiện.

Ví dụ mã Javascript lưu pdf phía client:

savePDF = () => {
    this.openModal(‘Loading…’) // open progress dialog
   return getPDF() // API call
     .then((response) => {
       const blob = new Blob([response.data], {type: 'application/pdf'})
       const link = document.createElement('a')
       link.href = window.URL.createObjectURL(blob)
       link.download = `your-file-name.pdf`
       link.click()
       this.closeModal() // close progress dialog
     })
   .catch(err => /** error handling **/)
 }
...
<button onClick={this.savePDF}>Save as PDF</button>

Vậy là xong rồi đấy, cuối cùng thì người dùng click vào nút “save as PDF” để trình duyệt lưu PDF.

Thay lời kết

Qua bài viết này, chúng ta đã biết cách xây dựng một server để convert một webpage HTML sang PDF bằng Node.js + Puppeteer. Khi bạn đã có server như này rồi, việc bạn tạo ứng dụng client rất đơn giản, nó có thể là ứng dụng Android, iOS hoặc web app trên trình duyệt.

Mình hi vọng bài viết giúp được các bạn trong dự án hiện tại và sắp tới. Bạn có sử dụng phương pháp này để generate PDF từ trang web HTML không? Để lại bình luận bên dưới cho mọi người biết nhé.

💦 Đọc thêm bài viết về Node.js

Tài liệu tham khảo:

  • https://github.com/puppeteer/puppeteer
  • https://medium.com/the-node-js-collection/generating-pdf-from-html-with-node-js-and-puppeteer-c5a0622e8e0a
  • https://nodejs.org/en/docs/
Dịch vụ phát triển ứng dụng mobile giá rẻ - chất lượng

Bình luận. Cùng nhau thảo luận nhé!

avatar
  Theo dõi bình luận  
Thông báo