什么是 Puppeteer?
Puppeteer 是一个控制 headless Chrome 的 Node.js API 。它是一个 Node.js 库,通过 DevTools 协议提供了一个高级的 API 来控制 headless Chrome。它还可以配置为使用完整的(非 headless)Chrome。
在浏览器中手动完成的大多数事情都可以通过使用 Puppeteer 完成,下面是一些入门的例子:
生成屏幕截图和 PDF 页面
检索 SPA 并生成预渲染内容(即 “SSR”)
从网站上爬取内容
自动提交表单,UI 测试,键盘输入等
创建一个最新的自动测试环境。使用最新的 JavaScript 和浏览器功能,在最新版本的 Chrome 中直接运行测试
捕获网站的时间线跟踪,以帮助诊断性能问题
npm install -g puppeteer
下面是一个用例:
const puppeteer = require('puppeteer');
async function printPDF() {
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.goto('https://blog.risingstack.com', {waitUntil: 'networkidle0'});
const pdf = await page.pdf({ format: 'A4' });
await browser.close();
return pdf;
})
这是一个简单的功能,可以导航到 URL 并生成站点的 PDF 文件。
首先,启动浏览器(PDF 生成仅在无头浏览器模式下支持),然后打开一个新页面,设置视口大小,并导航到提供的 URL。
设置该 waitUntil: ‘networkidle0’ 选项意味着当至少 500 毫秒没有网络连接时,Puppeteer 认为导航已完成。(查看 API 文档 以获取更多信息。)
最后,将 PDF 保存到一个变量中,关闭浏览器并返回 PDF。
注意:该 page.pdf方法接收一个 options 对象,也可以在其中使用 ‘path’ 选项将文件保存到磁盘。如果未提供路径,PDF 将不会保存到磁盘,而会获得一个缓冲区。(稍后再讨论如何处理它。)
如果你需要先登录以从受保护的页面生成 PDF,那么首先需要导航到登录页面,检查表单元素的 ID 或名称,填写它们,然后提交表单:
await page.type('#email', process.env.PDF_USER)
await page.type('#password', process.env.PDF_PASSWORD)
await page.click('#submit')
为了安全,要始终将登录凭据存储在环境变量中,不要对其进行硬编码!
样式操作
Puppeteer 也有针对这种样式操作的解决方案。你可以在生成 PDF 前插入样式标签,Puppeteer 会生成一个带有修改样式的文件。
await page.addStyleTag({
content: '.nav { display: none} .navbar { border: 0px} #print-button {display: none}'
});
将文件发送到客户端并保存
现在已经在后端生成了一个 PDF 文件。接下来做什么?
正如上文提到的,如果不将文件保存到磁盘,将获得一个缓冲区。服务端只需要将具有正确内容类型的缓冲区发送到前端。
printPDF().then(pdf => {
res.set({ 'Content-Type': 'application/pdf', 'Content-Length': pdf.length });
res.send(pdf);
})
而在前端,可以简单地向服务端发送请求,以获取生成的 PDF。
function getPDF() {
return axios.get(`${API_URL}/your-pdf-endpoint`, {
responseType: 'arraybuffer',
headers: {
'Accept': 'application/pdf'
}
});
}
发送请求后,缓冲区应开始下载。现在最后一步是将缓冲区转换为 PDF 文件。
savePDF = () => {
this.openModal('Loading…'); // open modal
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 modal
})
.catch(err => /** error handling **/);
}
在 Docker 中使用 Puppeteer
这是实现中最棘手的部分。官方文档指出“在 Docker 中启动和运行无头 Chrome 可能很棘手”。官方文档有一个 故障排除 部分,你可以在其中找到有关使用 Docker 安装 puppeteer 的所有必要信息。
如果你在 Alpine 映像上安装 Puppeteer,请确保向下滚动到 页面的这一部分。否则,你可能会无法运行最新版本的 Puppeteer,并且你还需要使用一个标志来禁用 shm 的使用:
const browser = await puppeteer.launch({
headless: true,
args: ['--disable-dev-shm-usage']
});
否则,Puppeteer 子进程可能在它正常启动之前就耗尽内存。
# 拉取node镜像
FROM node:10-alpine
# 设置镜像作者
LABEL MAINTAINER="qiyang.hqy@dtwave-inc.com"
# 设置国内阿里云镜像站、安装chromium 68、文泉驿免费中文字体等依赖库
RUN echo "https://mirrors.aliyun.com/alpine/v3.8/main/" > /etc/apk/repositories \
&& echo "https://mirrors.aliyun.com/alpine/v3.8/community/" >> /etc/apk/repositories \
&& echo "https://mirrors.aliyun.com/alpine/edge/testing/" >> /etc/apk/repositories \
&& apk -U --no-cache update && apk -U --no-cache --allow-untrusted add \
zlib-dev \
xorg-server \
dbus \
ttf-freefont \
chromium \
wqy-zenhei@edge \
bash \
bash-doc \
bash-completion -f
# 设置时区
RUN rm -rf /etc/localtime && ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
# 设置环境变量
ENV NODE_ENV production
# 创建项目代码的目录
RUN mkdir -p /workspace
# 指定RUN、CMD与ENTRYPOINT命令的工作目录
WORKDIR /workspace
# 复制宿主机当前路径下所有文件到docker的工作目录
COPY . /workspace
# 清除npm缓存文件
RUN npm cache clean --force && npm cache verify
# 如果设置为true,则当运行package scripts时禁止UID/GID互相切换
# RUN npm config set unsafe-perm true
# 安装pm2
RUN npm i pm2 -g
# 安装依赖
RUN npm install
# 暴露端口
EXPOSE 3000
# 运行命令
ENTRYPOINT pm2-runtime start docker_pm2.json
Puppeteer 示例
方式1:puppeteer.js ,表示从对应URL处获取信息,并返回pdf流。具体代码如下:
const puppeteer = require('puppeteer');
const options = process.argv;
var address, types;
(async() => {
if(options.length>=4){
address=options[2];
types=options[3];
}
const browser = await puppeteer.launch();
const page = await browser.newPage();
const userAgent = "Mozilla/5.0 (Linux; Android 8.1.0; MI 8 Build/OPM1.171019.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.84 Mobile Safari/537.36";
page.setUserAgent(userAgent);
//await page.setViewport({ width: 1920, height: 1080 });
await page.setViewport({ width: 480, height: 800,isMobile: true});
await page.goto(address, {waitUntil: 'networkidle2'});
if(types == 'pdf') {
const pdf = await page.pdf({path: 'd://page.pdf', format: 'A4'});
await browser.close();
process.stdout.write(pdf);
}else {
await browser.close();
}
})();
方式2:setContent.js (window系统使用),表示从html文件获取源文件流,并返回pdf流。具体代码如下:
const puppeteer = require('puppeteer');
var fs = require('fs');
const options = process.argv;
var htmlContent;
(async() => {
htmlFilePath=options[2];
const browser = await puppeteer.launch();
const page = await browser.newPage();
const userAgent = "Mozilla/5.0 (Linux; Android 8.1.0; MI 8 Build/OPM1.171019.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.84 Mobile Safari/537.36";
page.setUserAgent(userAgent);
//await page.setViewport({ width: 1920, height: 1080 });
await page.setViewport({ width: 480, height: 800,isMobile: true});
//const buff2 = Buffer.from(htmlContent, 'base64')
//const htmlContentResult = buff2.toString('utf-8')
var contentHtml = fs.readFileSync(htmlFilePath, 'utf-8');
//await page.goto('file://d:\\test2.html');
await page.setContent(contentHtml);
const pdf = await page.pdf({path: 'page.pdf', format: 'A4'});
await browser.close();
process.stdout.write(pdf);
})();
setContent.js (linux系统使用)
const puppeteer = require('puppeteer');
var fs = require('fs');
const options = process.argv;
var htmlContent;
(async() => {
htmlFilePath=options[2];
const browser = await puppeteer.launch({
args: ['--no-sandbox', '--disable-setuid-sandbox'],
});
const page = await browser.newPage();
const userAgent = "Mozilla/5.0 (Linux; Android 8.1.0; MI 8 Build/OPM1.171019.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.84 Mobile Safari/537.36";
page.setUserAgent(userAgent);
//await page.setViewport({ width: 1920, height: 1080 });
await page.setViewport({ width: 1800, height: 1800});
//const buff2 = Buffer.from(htmlContent, 'base64')
//const htmlContentResult = buff2.toString('utf-8')
var contentHtml = fs.readFileSync(htmlFilePath, 'utf-8');
//await page.goto('file://d:\\test2.html');
await page.setContent(contentHtml);
const pdf = await page.pdf({path: 'page.pdf', fullPage: true});
await browser.close();
process.stdout.write(pdf);
})();x
- puppeteer.js对应的java调用代码如下:
/**
* html转pdf,直接通过流输出到浏览器
*
* @param response 浏览器响应
* @param fileName 文件名称
* @param puppeteerjs 要采用哪个js文件执行
* @param webSiteUrl 要生成pdf/图片的网页
* @param types 类型 :pdf代表要生成pdf文件,jpg代表要生成jpg图片
*/
public static void parseHtml2Pdf(HttpServletResponse response, String fileName, String puppeteerjs, String webSiteUrl, String types) {
try {
Runtime rt = Runtime.getRuntime();
//Process p = rt.exec("node C:\\Users\\boshi\\Desktop\\iview-admin-master\\hn.js https://www.baidu.com pdf");
Process p = rt.exec("node "+puppeteerjs+" "+webSiteUrl+" "+types);
InputStream is = p.getInputStream();
BufferedInputStream bf = new BufferedInputStream(is);
byte[] data = IOUtils.toByteArray(bf);
fileName = URLEncoder.encode(fileName, "UTF-8");
response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
response.addHeader("Content-Length", "" + data.length);
response.setContentType("application/octet-stream;charset=UTF-8");
OutputStream outputStream = new BufferedOutputStream(response.getOutputStream());
outputStream.write(data);
outputStream.flush();
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
- setContent.js 对应的java调用代码如下:
public void parseHtml2Pdf(String htmlFileName) {
try {
Runtime rt = Runtime.getRuntime();
Process p = rt.exec("node " + createPdfJsPath + "setContent.js " + this.getHtmlTempPath(htmlFileName));
InputStream is = p.getInputStream();
BufferedInputStream bf = new BufferedInputStream(is);
byte[] data = IOUtils.toByteArray(bf);
File file = new File(diseaseControlPdfPath + htmlFileName + ".pdf");
IOUtils.write(data,new FileOutputStream(file));
} catch (IOException e) {
log.error("in parseHtml2Pdf has an error,e is ",e);
}
}
最后编辑:Jeebiz 更新时间:2024-03-12 09:16