资讯专栏INFORMATION COLUMN

使用 Puppeteer 导出声享 PPT

Codeing_ls / 3413人阅读

摘要:而打印所用的页面需要用到用户信息,所以我们登录了一个超管帐号来执行打印操作。在访问页面的时候通过参数校验判断是否是打印而打开的页面,如果是则登录超管帐号。

现状

声享是一个基于 ThinkJS 开发的在线制作 PPT 平台。声享制作的 PPT 支持代码高亮、图片上传、神奇效果等功能,同时你可以在声享收藏自己喜欢的 PPT 、对自己的 PPT 进行分类管理。其中有一个 PDF 导出的功能,可以将自己制作的 PPT 导出成 PDF 保存到本地。

功能实现比较简单,只是提供了一个页面,用户需要手动去打印成 PDF。这个方案存在一些问题:

由于使用了 iframe 懒加载导致未加载的 iframe 无法正常显示。

该种方案只能打印所有页面的初始状态。如果页面中存在切换动画,可能会丢失部分 PPT 信息。

需要用户手动操作,提高了使用难度。

如果是前端来生成 PDF,这些问题基本可以得到解决,但是开发量比较大而且存在一个效率问题。如果 PPT 页面存在多个 iframe,PDF 的生成时间过长会让用户长时间等待,明显不太合适。最终还是决定服务端来生成 PDF,才有了后来 Puppeteer 的尝试。

Puppeteer

什么是Puppeteer呢?官方给的解释是:

Puppeteer is a Node library which provides a high-level API to control Chrome or Chromium over the DevTools Protocol. Puppeteer runs headless by default, but can be configured to run full (non-headless) Chrome or Chromium.

简而言之,这货是一个提供高级 API 的 node 库,能够通过 devtool 控制 headless 模式的 Chrome 或者 Chromium,它可以在 headless 模式下模拟任何的人为操作。通过它我们可以实现:

生成页面的截图或者 PDF。

抓取 SPA(单页应用)并生成预渲染内容(即“SSR”(服务器端渲染))。

自动提交表单,进行 UI 测试,键盘输入等。
...

通过 Puppeteer,我们可以直接使用 Chrome 把我们需要的内容导出为 PDF。对比以前的实现方式有以下优点:

不需要用户手动操作,服务端生成 PDF 后直接以邮件的方式发送给用户。

PPT 中的动画可以模拟用户翻页的动作触发,然后以初始、结束两张 PDF 的方式展示,不会丢失 PPT 内容。

不需要考虑图片/ iframe 跨域等问题。

可以说 Puppeteer 完美的解决来我们一期 PDF 导出存在的问题。

解决方案

我们基本的实现思路是:

打开一个正常的 PPT 播放页,获取需要打印的 DOM 元素并翻页 。

重复第一步操作直至到最后一页 。

清空页面内容并将前两步获得的页面内容依次填充到当前页面(为什么要依次填充会在后面解释)。

对应上述方案实现的部分代码如下:

通过 Puppeteer 打开指定的页面。

// 测试时建议headless设置为false,以便可以直观看到页面效果
this.browser = await puppeteer.launch({headless: this.isDebug});
this.page = await this.browser.newPage();
await this.page.goto("https://xxxxx.com", { waitUntil:"networkidle2" });

打开页面后可以通过 Puppeteer 模拟用户翻页操作,每次翻页后缓存需要打印的 DOM 元素字符串。

let canNext;
let i = 0;
const content = {};
do {
    canNext = await this.page.$(".navigate-right.enabled");
    const iframes = await this.page.$$(".PluginPage.present iframe").length;
    content[i++] = {
        iframe: iframes,
        domStr: await this.page.$eval(".RevealViewPort", el => el.outerHTML)
    }
    if (canNext) {
        await this.page.click(".navigate-right");
        // 等待翻页动画
        await this.page.waitFor(1000);
    }
} while (canNext);

获取到要打印的所有页面 DOM 后,替换掉原来的页面内容。因为 $evaluate 方法中不支持调用外部变量所以只能以传参的方式使用。

this.page.evaluate(domStr => document.body.innerHTML = domStr, content);

调用生成 PDF 的 API。

this.page.pdf({
    path: path.join(think.ROOT_PATH, "runtime/xxx.pdf"),
    format: "A4",
    landscape: true,
    printBackground: true //如果要显示背景,此属性要设置为true
})

使用 nodemailer 发送邮件给用户。这一步如果想使用本地的 SMTP 服务请用 nodemailer 的 2.7.5 的版本,此版本后这项功能被删除了。

let transporter = nodemailer.createTransport({
    host: "smtp.ym.163.com",
    port: 994,
    secure: true,
    auth: {
        user: "xxx@xxx.com",
        pass: "xxx"
    }
});
transporter.sendMail({
    from: "xxx@xxx.com",
    to: "xxx@xxx.com",,
    subject: "【声享】xxx",
    attachments: [{
        filename: "xxx.pdf",
        path: path.join(think.ROOT_PATH, "runtime/xxx.pdf"),
        contentType: "application/pdf"
    }]
})
开发中需要注意的问题

用户登录

使用 Puppeteer 打开页面相当于你新启动了一个浏览器实例,页面中的 seession 和 cookie 是空的。而打印所用的页面需要用到用户信息,所以我们登录了一个超管帐号来执行打印操作。在 ThinkJS 中可以通过中间件来实现这项功能。在访问页面的时候通过参数校验判断是否是打印而打开的页面,如果是则登录超管帐号。

// 打开指定页面时通过校验后面参数判断是否以超管登录
module.exports = options => {
    return async (ctx, next) => {
        const { token, ctime } = ctx.query;
        const md5Str = tockenGenerator();
        if (md5Str === token) {
            await ctx.session("userInfo", adminUser);
        }
        return next();
    };
};

Puppeteer 启动

如果服务端是运行在 root 权限下,在启动 Puppeteer 时要添加 --no-sandbox 参数,否则 Chrome/Chromium 会启动失败。详情见 Running as root without — no-sandbox is not supported。这个权限问题在linux以root用户使用 Chrome 的时候同样适用。

this.browser = await puppeteer.launch({args:["--no-sandbox"]});

iframe 无法加载

声享支持页面内嵌入 iframe,在打印的时候碰到一个问题。如果同时在页面上插入 iframe 过多,后面的 iframe 会直接卡住不再加载。所以 iframe 最好分批插入或者一个一个插入,同时设定10秒来加载iframe。 如果想精确控制 iframe 也可以使用 API 等待 iframe 完全加载再执行后续操作。

for (let i = 0; i < pages.length; i++) {
    const page = pages[i];
    await this.page.$evaluate(content => {
        const divDom = document.createElement("div");
        divDom.innerHTML = content;
        document.body.appendChild(divDom.childNodes[0])
    }, page.domStr);
    if (page.iframe) await this.page.waitFor(10000 * page.iframe);
}

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/102645.html

相关文章

  • Node.js定时导出Highchart图表

    摘要:一背景需求因为数据包含机密信息,所以得自己搭建图表导出服务器在后台生成对应图表以图片的形式导出保存。图表个性化程度较高,如一些图列是没有的,但在前端可以利用实现。每周定时执行上述生成图表的任务,保存到指定位置。 一、背景需求 1、因为数据包含机密信息,所以得自己搭建图表导出服务器;在后台生成对应Highcharts图表、以图片的形式导出保存。2、图表个性化程度较高,如一些图列是High...

    miguel.jiang 评论0 收藏0
  • 用纯 DOM 的方式结合 Puppeteer 自动生成网页骨架屏

    摘要:可以通过的提供的直接控制模拟大部分用户操作来进行或者作为爬虫访问页面来收集数据。   骨架屏是在页面数据尚未加载完成前先给用户展示出页面的大致结构,直到请求数据返回后再显示真正的页面内容;随着单页应用( SPA )的越来越流行,单页应用的用户体验也越来越得到前端开发者的关注;为了优化用户体验,在数据到达用户之前,往往会在页面上加上 loading 的效果,而现在,越来越多的场景倾向于使...

    BlackHole1 评论0 收藏0
  • 手把手教你如何用Crawlab构建技术文章聚合平台(一)

    摘要:本文将介绍如何使用和抓取主流的技术博客文章,然后用搭建一个小型的技术文章聚合平台。是谷歌开源的基于和的自动化测试工具,可以很方便的让程序模拟用户的操作,对浏览器进行程序化控制。相对于,是新的开源项目,而且是谷歌开发,可以使用很多新的特性。 背景 说到爬虫,大多数程序员想到的是scrapy这样受人欢迎的框架。scrapy的确不错,而且有很强大的生态圈,有gerapy等优秀的可视化界面。但...

    LinkedME2016 评论0 收藏0
  • 手把手教你如何用Crawlab构建技术文章聚合平台(一)

    摘要:本文将介绍如何使用和抓取主流的技术博客文章,然后用搭建一个小型的技术文章聚合平台。是谷歌开源的基于和的自动化测试工具,可以很方便的让程序模拟用户的操作,对浏览器进行程序化控制。相对于,是新的开源项目,而且是谷歌开发,可以使用很多新的特性。 背景 说到爬虫,大多数程序员想到的是scrapy这样受人欢迎的框架。scrapy的确不错,而且有很强大的生态圈,有gerapy等优秀的可视化界面。但...

    Jeffrrey 评论0 收藏0
  • Puppeteer 初探

    摘要:获取获取上下文句柄执行计算销毁句柄除此之外,还可以使用意为在浏览器环境执行脚本,可传入第二个参数作为句柄,而则针对选中的一个元素执行操作。 我们日常使用浏览器或者说是有头浏览器时的步骤为:启动浏览器、打开一个网页、进行交互。 无头浏览器指的是我们使用脚本来执行以上过程的浏览器,能模拟真实的浏览器使用场景。 有了无头浏览器,我们就能做包括但不限于以下事情: 对网页进行截图保存为图片或 ...

    appetizerio 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<