Chụp hình web với Node.js và Puppeteer

12bit đã có một bài giới thiệu về Puppeteer ứng dụng vào việc scraping dữ liệu. Bài viết hôm nay, chúng ta cùng tìm hiểu một tính năng nữa của Puppeteer đó là screenshot. Đây là một tính răng rất thú vị và hữu ích, bạn có thể thỏa sức sáng tạo để mà dùng tính năng này.

API

Trước khi đi vào ứng dụng, chúng ta cùng xem qua method screenshot có những options nào. Các bạn có thể truy cập vào đây để xem danh sách các method mà Puppeteer cung cấp, mà cụ thể ở đây là screenshot

Cùng xem qua danh sách các options:

  • path: Đường dẫn để lưu hình được tạo ra khi sreenshot.
  • type: Định dạng hình ảnh jpeg hoặc png
  • quality: Chất lượng ảnh từ 0-100. Không áp dụng option này nếu typepng.
  • fullPage: Nếu bạn set bằng true thì Puppeteer sẽ chụp toàn bộ trang web.
  • clip: Không phải lúc nào bạn cũng cần chụp toàn bộ trang web. clip sẽ giúp bạn chụp một vùng trên trang web mà thôi.
  • omitBackground: Trang web nào có background màu trắng sẽ bị loại bỏ thay vào đó là một transparency background.
  • encoding: Định dạng mã hóa của hình, base64 hoặc binary.

Giá trị trả về khi bạn gọi tới method sreenshot đó là một Promise. Khi resolve giá trị nhận được là string hoặc Buffer tùy vào việc bạn gán encoding là gì.

📷 Screenshot

Chụp toàn bộ trang web

Sau khi đọc qua API, chúng ta sẽ thử chụp hình một trang web. Giả sử mình muốn chụp toàn bộ trang web https://thien.dev và lưu thành screenshot.png

Việc cài đặt puppeteer vào khởi tạo browser bạn có thể xem qua ở bài trước nhé.

(async () => {
  const browser = await puppeteer.launch()
  const page = await browser.newPage()
  await page.goto('https://thien.dev')
  await page.screenshot({
    path: './screenshot.png',
    type: 'png',
    fullPage: true
  })
  await browser.close()
})()

Kết quả là:

bấm vô hình để zoom nè.

bấm vô hình để zoom nè.

Chụp một phần

Tiếp theo, thay vì chụp toàn bộ trang web, chúng ta sẽ thử chụp một phần trang web với kích thước 800x400px.

(async () => {
  const browser = await puppeteer.launch()
  const page = await browser.newPage()
  await page.goto('https://thien.dev')
  await page.screenshot({
    path: './sreenshot.png',
    type: 'png',
    clip: {
      x: 0, y: 0,
      width: 800, height: 400
    }
  })
  await browser.close()
})()

Kết quả sẽ là một bức hình với kích thước 800x400px

Encoding

Không phải lúc nào chúng ta cũng muốn lấy một file hình. Sẽ có lúc cần lấy kết quả trả về ở dạng base64. Việc này rất đơn giản, chỉ cần thay đổi option encoding thành base64 như sau:

(async () => {
  const browser = await puppeteer.launch()
  const page = await browser.newPage()
  await page.goto('https://thien.dev')
  const result = await page.screenshot({
    path: './sreenshot.png',
    type: 'png',
    clip: {
      x: 0, y: 0,
      width: 800, height: 400
    },
    encoding: 'base64'
  })

  console.log(result)

  await browser.close()
})()

Kêt quả:

iVBORw0KGgoAAAANSUhEUgAAAyAAAAGQCAYAAABWJQQ0AAAAAXNSR0IArs4c6QAAIABJREFUeJzs3Xd8VfX9x/H395x7k5CEEMIMYQcQEWUPV90o4AT3qnWPOlt3f21tra12WEe1FXdbxQkqirPgRkWWKCPsvUcSRpJ7zvf3xw03uSSBjJsTgq/n48GDc8/6fs64N...

Bạn có thể sử dụng data đó như một data URLs như sau:

body {
  background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAyAAAAGQCAYAAABWJQQ0AAAAAXNSR0IAr...)
}

Những vấn đề gặp phải

Trong quá trình làm việc với screenshot mình gặp phải một vài vấn đề có thể bạn cũng sẽ gặp.

Error: Failed to launch chrome!

Việc khởi tạo một browser bằng puppeteer có thể sẽ phải thêm những options khác nhau trên những môi trường OS khác nhau. Nếu bạn gặp lỗi như trên hay thử thêm option vào puppeteer.launch()

const browser = await puppeteer.launch({
  args: ['--no-sandbox', '--disable-setuid-sandbox'],
})

Screenshot với view port

Đôi khi bạn sẽ gặp phải những vấn đề về view port nếu screenshot với width lớn hơn 800px. Vì viewport mặt định puppeteer set là 800px x 600px. Lúc này bạn cần phải set lại view port bằng cách sử dụng page.setViewport()

// ...
await page.goto('https://github.com')
await page.setViewport({ width: 1200, height: 630 })
const result = await page.screenshot({
  path: './sreenshot.png',
  type: 'png',
  clip: {
    x: 0, y: 0,
    width: 1200, height: 630
  }
})
// ...

Kết quả trước và sau khi set view port:

Lỗi font

Có một trường hợp mình gặp phải đó là khi chụp hình thì bị lỗi font đối với những trang có sử dụng webfont. Nguyên nhân là do font chưa kịp load xong thì hình đã được chụp. Để xử lí vấn đề này puppeteer cung cấp option là waitUntil . Khi bạn gọi page.goto('url', { waitUntil: 'some-value' }) có nghĩa việc “navigation” vào trang web sẽ đợi khi nào sự kiện ở waitUntil thực thi xong thì mới trả về kết quả thành công.

Bạn có thể xem qua phần docs của page.goto() tại đây để xem waitUntil có những sự kiện nào.

Trường hợp của mình sử dụng event là networkidle0 tức là sẽ đợi tới khi không còn một connection (cụ thể là connection tới webfont) nào nữa trong khoảng thời gian là ít nhất 500 ms.

Mời bạn xem qua ví dụ trong library tụi mình mới viết social-image-gen

bấm vô hình để zoom nè.

bấm vô hình để zoom nè.

Kết luận

Bạn có thể ứng dụng tính năng sreenshot vào nhiều ngữ cảnh cách khau. Đối với 12bit, tụi mình đã dùng tính năng này để tự động generate social image mỗi khi publish post (vì tụi mình quá lười để thiết kế một tấm hình đại diện cho bài viết 😂). Các bạn có thể tham khảo tạo repo trên GitHub của 12bit.vn

12bitvn/social-image-gen

📷 The tool that generates your social image based on your markdown content.

  • Star
    9
  • Fork
    1
  • Issues
    5
  • Updated
    thg 9 11, 2022