使用puppeteer擷取網頁畫面
前陣子因為想把特定的youtube
的對話訊息轉發到twitter
上,但是只把文字轉出去又覺得哪裡感覺不對,所以就打算把訊息做成一張圖後再上傳,而且這樣也可以附上發言人的頭像。
這邊就先寫擷取網頁畫面的部分,流程大概是這樣:
- 讀取聊天室訊息
- 取得到需要轉發的訊息
- 呼叫
HTTP API
,傳入網址
、視窗寬度
、視窗高度
、等待時間
- 收到請求
- 啟動瀏覽器
- 開啟網頁
- 等待網頁讀取完成
- 截圖
- 產生
base64
字串並回傳
這次是用nodejs
開發:
// server.js
const express = require('express');
const bodyParser = require("body-parser");
const puppeteer = require('puppeteer');
const app = express();
const PORT = process.env.PORT || 8080;
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: true
}));
async function doNewPage(b) {
return new Promise((resolve) => {
let isWaiting = true;
setTimeout(() => {
if (isWaiting) {
b.close();
process.exit(4);
}
}, 5000);
b.newPage().then((res) => {
isWaiting = false;
resolve(res);
})
})
}
// link = 需要截圖的網址
// w = 瀏覽器寬度
// h = 瀏覽器高度
// f = 是否為全頁截圖 ; puppeteer
// ms = 延遲毫秒數 ; 自訂等待時間
async function task(link, w, h, f, ms) {
let browser = null;
try {
browser = await puppeteer.launch({
defaultViewport: null,
timeout: 5000,
}).catch((e) => {
process.exit(2);
});
} catch (e) {
process.exit(3);
}
let page = await doNewPage(browser);
let fullPage = f == 't' ? true : false;
let b64 = '';
if (w > 0 && h > 0) {
await page.setViewport({ width: w, height: h, deviceScaleFactor: 1 });
}
await page.goto(link, {
waitUntil: 'networkidle2',
});
if (ms > 0) {
await page.waitForTimeout(ms);
}
b64 = await page.screenshot({ fullPage, encoding: "base64" });
// to file
//let rnd = new Date().getTime();
//await page.screenshot({ path: `${rnd}.png`, fullPage });
browser.close();
return b64;
}
////
app.get('/', (req, res) => {
res.status(200).send('not here');
});
app.post('/shot', async (req, res) => {
let link = decodeURI(req.body.link);
let w = parseInt(req.body.w, 10);
let h = parseInt(req.body.h, 10);
let f = req.body.f; // 't' or other
let ms = parseInt(req.body.ms, 10);
let pass = link != '' && w >= 0 && h >= 0 && ms >= 0 && ms <= 15000;
let b64 = '';
if (pass) {
b64 = await task(link, w, h, f, ms);
} else {
console.log('input failed');
}
res.status(200).send({ b64 });
})
app.listen(PORT, () => {
console.log(`App listening on port ${PORT}`);
});
這邊定義了POST
方法/shot
,會讀取下列參數:
- link = 網址
- w = 視窗寬度
- h = 視窗高度
- f = 是否全頁截圖
- ms = 需要等待的毫秒數
puppeteer
還可以檢查頁面的變數相關的值或操作,不過這邊還不需要所以沒仔細去試。
接下來弄個Dockerfile
:
FROM node:17-alpine
RUN apk add --no-cache \
chromium \
nss \
freetype \
harfbuzz \
ca-certificates \
ttf-freefont \
nodejs \
yarn \
font-noto-cjk \
font-noto-emoji
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true \
PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
RUN yarn add [email protected]
RUN yarn add express
RUN addgroup -S pptruser && adduser -S -g pptruser pptruser \
&& mkdir -p /home/pptruser/Downloads /app \
&& chown -R pptruser:pptruser /home/pptruser \
&& chown -R pptruser:pptruser /app
USER pptruser
WORKDIR /app
COPY ./server.js /app/server.js
RUN yarn
EXPOSE 8080
當時忘記產生package.json
了,而是直接在dockerfile
直個yarn add
。
接下來就是建置:
docker build -t express-puppeteer . --no-cache
然後啟動容器:
docker run -itd --cap-add=SYS_ADMIN -p 9999:8080 -v ${PWD}:/app --name exp-pup express-puppeteer node server.js
-v
參數是用來測試用的。
最後就可以使用curl
來測試:
curl -s -X POST -H "Content-Type: application/json" -d '{ "link" : "https://www.nijisanji.jp/works" , "w" : 1920 , "h" : 1080 , "f" : "t" , "ms" : 5000 }' "http://127.0.0.1:9999/shot"
順利的話就會回傳個帶有b64
屬性的json
字串。
{ "b64" : ".........." }
當然測試的時候也可以改成直接存成圖檔:
// ...
// base64
b64 = await page.screenshot({ fullPage, encoding: "base64" });
// to file
let rnd = new Date().getTime();
await page.screenshot({ path: `${rnd}.png`, fullPage });
// ...
以後只好寫好html
後就可以靠這API
產生出圖檔了。
Read other posts