使用node+puppeteer+express搭建截圖服務
寫在之前
一開始我們的需求是開啟報表的某個頁面然後把圖截出來,然後呼叫企業微信傳送給業務群
這中間我嘗試了多種技術,比如html2image
,pdf2image
、selenium
這些,這其中截圖
比體驗較好的也就selenium
了,不過我們有些頁面載入的時間較長,selenium似乎對html互操作性
也不是很完美(通過Thread.sleep並不能完美的相容絕大多數報表),另外還有一個比較要命的
是Chromium渲染出來的頁面似乎也有不同程度的問題(就是不好看),當然後面一個偶然的機會在
某不知名網站看到有網友用puppeteer
來實現截圖,遂~,一通騷操作就搭了一套出來(雖然最終方案並不是這個
,當然這是後話哈~),這裡就拿出來說說哈~
準備
由於整個系統是基於node+express的web服務,puppeteer只是node的一個plugin,所以需要做的準備大致有下
- 一臺linux伺服器,這裡實用centos
- node安裝包(用於搭建node環境)
- 字型檔案
安裝node環境
wget https://nodejs.org/dist/v14.15.3/node-v14.15.3-linux-x64.tar.xz
tar --strip-components 1 -xvJf node-v* -C /usr/local
npm config set registry https://registry.npm.taobao.org
安裝pm2(用於守護node服務)
【注意:安裝pm2前必須安裝npm,如果只是非正式環境可以不用安裝pm2】
npm install pm2 -g
- 其它操作請見https://pm2.keymetrics.io
安裝字型
【這個其實很重要,我也繞了彎,原本以為改改字型編碼就可以了,後來發現不是】
- step1: 將window字型複製到linux下
- windows: C:\Windows\Fonts
- Linux: /usr/share/fonts/
- step2: 建立字型索引資訊並更新字型快取
- cd /usr/share/fonts/
- mkfontscale
- mkfontdir
- fc-cache
準備程式碼
- index.js
// 引入express module
// 引入puppeteer module
const express = require('express'),
app = express(),
puppeteer = require('puppeteer');
// 函式::頁面載入監控
const waitTillHTMLRendered = async (page, timeout = 30000) => {
const checkDurationMsecs = 1000;
const maxChecks = timeout / checkDurationMsecs;
let lastHTMLSize = 0;
let checkCounts = 1;
let countStableSizeIterations = 0;
const minStableSizeIterations = 3;
while(checkCounts++ <= maxChecks){
let html = await page.content();
let currentHTMLSize = html.length;
let bodyHTMLSize = await page.evaluate(() => document.body.innerHTML.length);
console.log('last: ', lastHTMLSize, ' <> curr: ', currentHTMLSize, " body html size: ", bodyHTMLSize);
if(lastHTMLSize != 0 && currentHTMLSize == lastHTMLSize)
countStableSizeIterations++;
else
countStableSizeIterations = 0; //reset the counter
if(countStableSizeIterations >= minStableSizeIterations) {
console.log("Page rendered fully..");
break;
}
lastHTMLSize = currentHTMLSize;
await page.waitFor(checkDurationMsecs);
}
};
//建立一個 `/screenshot` 的route
app.get("/screenshot", async (request, response) => {
try {
const browser = await puppeteer.launch({ args: ['--no-sandbox'] });
const page = await browser.newPage();
await page.setViewport({
width:!request.query.width?1600:Number(request.query.width),
height:!request.query.height?900:Number(request.query.height)
});
// 這裡執行登入操作(非公共頁面需要登入)
if(request.query.login && request.query.login=="true"){
// wait until page load
await page.goto('認證(登入)地址', { waitUntil: 'networkidle0' });
await page.type('#username', '登入使用者名稱');
await page.type('#password', '登入密碼');
// click and wait for navigation
await Promise.all([
page.click('#loginBtn'),
page.waitForNavigation({ waitUntil: 'networkidle0' }),
]);
}
await page.goto(request.query.url,{'timeout': 12000, 'waitUntil':'load'});
await waitTillHTMLRendered(page);
const image = await page.screenshot({fullPage : true,margin: {top: '100px'}});
await browser.close();
response.set('Content-Type', 'image/png');
response.send(image);
} catch (error) {
console.log(error);
}
});
// listener 監聽 3000埠
var listener = app.listen(3000, function () {
console.log('Your appliction is listening on port ' + listener.address().port);
});
- package.json
{
"name": "funnyzpc",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
依賴安裝
-
npm i --save puppeteer express
[注意:如果安裝失敗 請檢查是否更改為taobao源]
啟動及管理
- 直接使用node啟動服務
node index.js
- 使用pm2啟動(如果安裝了pm2)
- 啟動:
pm2 start index.js
- 程式:
pm2 list
- 刪除:
pm2 delete 應用ID
- 啟動:
使用
由於以上程式碼已經對截圖的載入做過處理的,所以無需在使用執行緒睡眠
同時程式碼也對寬度(width)和高度(height)做了處理,所以具體訪問地址如下
http://127.0.0.1:3000/screenshot/?login=[是否登入true or false]&width=[頁面寬度]&height=[頁面高度]&url=[截圖地址]
最後
雖然我們我們使用puppeteer
能應對絕大多數報表,後來發現puppeteer
對多元件圖表存在渲染問題,所以就要求
提供商提供匯出圖片功能(使用者頁面匯出非api),所以最終一套就是 http模擬登入+呼叫截圖介面+圖片生成監控+推送圖片
好了,關於截圖就分享到這裡了,各位元旦節快樂哈~《@.@》