可根据个人需求来自己改动编写逻辑
示例用到的第三方库
node >= 18
express、playwright、fs、moment、crypto
安装浏览器快照工具
npx playwright codegen
目录结构图
GET请求生成快照(仅支持单个地址截图)
需要传入快照截图的地址,例如:/snapshots?url=xxx
router.get('/snapshots', async (req, res) => {
try {
const browserType = 'chromium'; // 指定浏览器类型 'chromium'、'firefox'、'webkit'
const browser = await playwright[browserType].launch();// 启动指定浏览器实例
const context = await browser.newContext();// 创建窗口
const page = await context.newPage();// 创建页面
await page.goto(req.query.url);// 跳转url路径
let screenshotBuffer = await page.screenshot({ fullPage: true });// 捕获截图
const parentDir = path.dirname(__dirname);
const screenshotDir = path.join(parentDir, 'screenshot');
if (!fs.existsSync(screenshotDir)) {
fs.mkdirSync(screenshotDir);
}
const timestamp = Date.now();
const screenshotPath = path.join(screenshotDir, `screenshot-${timestamp}.png`);
fs.writeFileSync(screenshotPath, screenshotBuffer);
res.send(`Screenshot saved as ${path.basename(screenshotPath)}`);
await browser.close();
} catch (error) {
console.error(error);
res.status(500).send('Error taking screenshot');
}
});
POST 请求生成快照(支持多个地址截图)
需要创建一个data.txt文本文件,编写需要快照的地址(以行切分)
router.get('/batch_snapshots', async (req, res) => {
const parentDir = path.dirname(__dirname);
const screenshotDir = path.join(parentDir, 'screenshot');
if (!fs.existsSync(screenshotDir)) {
fs.mkdirSync(screenshotDir);
}
let pngInfo = [] // 存取现有screenshot目录下照片的地址,区分新增和历史图片
await fs.promises.readdir(parentDir + '/screenshot')
.then(files => {
const pngFiles = files.filter(file => file.endsWith('.png'));
pngInfo = pngFiles
})
.catch(err => {
console.error('Error reading directory:', err);
});
function generateUniqueHashForUrls(urlArray) {
const timestamp = Date.now().toString(); // 获取当前时间戳
const randomString = Math.random().toString(36).substring(2, 15); // 生成随机字符串
const hash = crypto.createHash('sha256');
// 更新哈希值,包括时间戳和随机字符串,以及所有URL
hash.update(timestamp + randomString, 'utf8');
urlArray.forEach(url => {
hash.update(url, 'utf8');
});
return hash.digest('hex');
}
try {
const batchTime = moment().format('YYYY-MM-DD');
const startTime = moment().format('YYYY-MM-DD HH:mm:ss');
fs.readFile('data.txt', 'utf8', async (err, data) => {
let urlInfo = []
if (err) {
console.error('An error occurred:', err);
return;
}
urlInfo = data.toString().split("\n")
urlInfo = urlInfo.filter(url => url.trim() !== '');
if (!urlInfo) {
return res.json({});
}
const batch = generateUniqueHashForUrls(urlInfo);
const browserType = 'chromium';
const browser = await playwright[browserType].launch();
const context = await browser.newContext();
const results = [];
const errors = []
for (const url of urlInfo) {
let status = false
const page = await context.newPage();
// networkidle
// domcontentloaded
// load
await page.goto(url, { waitUntil: 'networkidle', timeout: 60000 }).catch(err => {
errors.push(url)
status = true
});
if (status){
continue
}
const screenshotBuffer = await page.screenshot({ fullPage: true });
let urlName = url.split("://")[1].replace(/\./g, "_").replace(/\/+/g, '_')
let urlNameIndex = 0
pngInfo.map((item) => {
if (item.includes(urlName)) {
urlNameIndex = parseInt(item.match(/\d+/g).pop()) + 1;
}
})
const screenshotPath = path.join(screenshotDir, `${urlName}-${batch}-${batchTime}-${urlNameIndex}.png`);
pngInfo.push(`${urlName}-${batch}-${batchTime}-${urlNameIndex}.png`)
// 将截图写入文件
fs.writeFileSync(screenshotPath, screenshotBuffer);
results.push(`${url}` + " ←saved as→ " + `${path.basename(screenshotPath)}`);
await page.close();
}
await browser.close();
const endTime = moment().format('YYYY-MM-DD HH:mm:ss');
// 告诉客户端所有截图已保存
const timeDiff = moment(endTime).diff(moment(startTime), "seconds")
// 输出时间差
let resBody = {
time: [startTime, endTime],
timeDiff,
batch,
errorNum:errors.length,
successfulNum:results.length,
errors,
results
}
res.json(resBody);
});
} catch (error) {
console.error(error);
res.status(500).send('Error taking screenshots');
}
});
全部代码奉上
var express = require('express');
var router = express.Router();
const playwright = require('playwright');
const fs = require('fs');
const path = require('path');
const moment = require("moment")
const crypto = require('crypto');
/**
* @name GET 支持单个快照生成
* @description 可通过?url=地址 来创建截图
* @example /download/screenshot?url=https://www.baidu.com/index.html
*/
router.get('/snapshots', async (req, res) => {
try {
const browserType = 'chromium'; // 指定浏览器类型 'chromium'、'firefox'、'webkit'
const browser = await playwright[browserType].launch();// 启动指定浏览器实例
const context = await browser.newContext();// 创建窗口
const page = await context.newPage();// 创建页面
await page.goto(req.query.url);// 跳转url路径
let screenshotBuffer = await page.screenshot({ fullPage: true });// 捕获截图
const parentDir = path.dirname(__dirname);
const screenshotDir = path.join(parentDir, 'screenshot');
if (!fs.existsSync(screenshotDir)) {
fs.mkdirSync(screenshotDir);
}
const timestamp = Date.now();
const screenshotPath = path.join(screenshotDir, `screenshot-${timestamp}.png`);
fs.writeFileSync(screenshotPath, screenshotBuffer);
res.send(`Screenshot saved as ${path.basename(screenshotPath)}`);
await browser.close();
} catch (error) {
console.error(error);
res.status(500).send('Error taking screenshot');
}
});
/**
* @name get 批量查询地址生成快照
* @description 该代码取值的地址是data.txt文档里保存的地址,一行表示一个地址,支持空行跳过
*/
router.get('/batch_snapshots', async (req, res) => {
const parentDir = path.dirname(__dirname);
const screenshotDir = path.join(parentDir, 'screenshot');
if (!fs.existsSync(screenshotDir)) {
fs.mkdirSync(screenshotDir);
}
let pngInfo = [] // 存取现有screenshot目录下照片的地址,区分新增和历史图片
await fs.promises.readdir(parentDir + '/screenshot')
.then(files => {
const pngFiles = files.filter(file => file.endsWith('.png'));
pngInfo = pngFiles
})
.catch(err => {
console.error('Error reading directory:', err);
});
function generateUniqueHashForUrls(urlArray) {
const timestamp = Date.now().toString(); // 获取当前时间戳
const randomString = Math.random().toString(36).substring(2, 15); // 生成随机字符串
const hash = crypto.createHash('sha256');
// 更新哈希值,包括时间戳和随机字符串,以及所有URL
hash.update(timestamp + randomString, 'utf8');
urlArray.forEach(url => {
hash.update(url, 'utf8');
});
return hash.digest('hex');
}
try {
const batchTime = moment().format('YYYY-MM-DD');
const startTime = moment().format('YYYY-MM-DD HH:mm:ss');
fs.readFile('data.txt', 'utf8', async (err, data) => {
let urlInfo = []
if (err) {
console.error('An error occurred:', err);
return;
}
urlInfo = data.toString().split("\n")
urlInfo = urlInfo.filter(url => url.trim() !== '');
if (!urlInfo) {
return res.json({});
}
const batch = generateUniqueHashForUrls(urlInfo);
const browserType = 'chromium';
const browser = await playwright[browserType].launch();
const context = await browser.newContext();
const results = [];
const errors = []
for (const url of urlInfo) {
let status = false
const page = await context.newPage();
// networkidle
// domcontentloaded
// load
await page.goto(url, { waitUntil: 'networkidle', timeout: 60000 }).catch(err => {
errors.push(url)
status = true
});
if (status){
continue
}
const screenshotBuffer = await page.screenshot({ fullPage: true });
let urlName = url.split("://")[1].replace(/\./g, "_").replace(/\/+/g, '_')
let urlNameIndex = 0
pngInfo.map((item) => {
if (item.includes(urlName)) {
urlNameIndex = parseInt(item.match(/\d+/g).pop()) + 1;
}
})
const screenshotPath = path.join(screenshotDir, `${urlName}-${batch}-${batchTime}-${urlNameIndex}.png`);
pngInfo.push(`${urlName}-${batch}-${batchTime}-${urlNameIndex}.png`)
// 将截图写入文件
fs.writeFileSync(screenshotPath, screenshotBuffer);
results.push(`${url}` + " ←saved as→ " + `${path.basename(screenshotPath)}`);
await page.close();
}
await browser.close();
const endTime = moment().format('YYYY-MM-DD HH:mm:ss');
// 告诉客户端所有截图已保存
const timeDiff = moment(endTime).diff(moment(startTime), "seconds")
// 输出时间差
let resBody = {
time: [startTime, endTime],
timeDiff,
batch,
errorNum:errors.length,
successfulNum:results.length,
errors,
results
}
res.json(resBody);
});
} catch (error) {
console.error(error);
res.status(500).send('Error taking screenshots');
}
});
module.exports = router;