Puppeteer学习笔记
调用方法
一些知识
页面transform.scale放大后截图
scale:2
大小:364KB
scale:5
大小:1225KB
截屏越大,速度越慢
比如手机页面,高宽分别放大5倍(实际截屏面积应该是放大了25倍),9秒的视频,截了近500秒,差不多是50倍的时间,太夸张了。
不过这可能也跟我笔记本没接电源有关系,还需要再测试一下。
录制
常用参数说明
--no-sandbox: 去沙箱运行
--disable-dev-shm-usage: 默认情况下,Docker运行一个/dev/shm共享内存空间为64MB 的容器。这通常对Chrome来说太小,并且会导致Chrome在渲染大页面时崩溃。要修复,必须运行容器 docker run --shm-size=1gb 以增加/dev/shm的容量。从Chrome 65开始,使用--disable-dev-shm-usage标志启动浏览器即可,这将会写入共享内存文件/tmp而不是/dev/shm.
常用操作
注入自定义JS
通过page.evaluate()实现,参考这个文章:
https://www.cnblogs.com/wuweiblogs/p/12918968.html
常见问题
选择jpeg还是png
https://www.zhihu.com/question/29758228
因为Chromium安装失败进而导致Puppeteer安装失败
https://blog.csdn.net/qq799028706/article/details/88602254
下载chromium是自动从官方过去下载的,我们在内网是无法安装Puppeteer的,需要跳过下载Chromium:
1 | |
注意,新的puppeteer版本中,这个环境变量变成了PUPPETEER_SKIP_DOWNLOAD,具体使用哪个环境变量,请根据报错提示信息来选择。
如果不是内网环境,而只是单纯因为墙的问题,那么你可以选择科学上网,或者切换下npm源:
1 | |
Browser is not downloaded
如果全局安装Puppeteer后,仍然提示这个信息,那一般是因为你后续安装的某个包依赖了puppeteer,这个包的node_modules目录下下载了puppeteer,程序使用了这个puppeteer,而没有使用全局puppeteer的缘故。
比如我遇到的一次,就是后续安装了timesnap这个包,项目启动后使用了timesnap/node_modules/puppeteer下的程序,没有使用全局的puppeteer。
解决方案:删除timesnap/node_modules/puppeteer 即可。
离线安装Chromium
比如内网就可以用这个方式安装:
1、先安装puppeteer:npm install puppeteer
2、查看node_modules/puppeteer/.local-chromium下面的chromium版本号
3、去 https://npm.taobao.org/mirrors/chromium-browser-snapshots/ 下载对应系统对应版本的chromium包
4、解压到node_modules/puppeteer/.local-chromium下,路径类似这样:
node_modules/puppeteer/.local-chromium/win64-722234/chrome-win/
如需全局安装Puppeteer,也可以采用和类似的操作。
文字模糊
这个多出现在页面文字颜色和背景出现渗透的情况。
另外和字体也有关系,比如雷达图中,同样是文字,雷达图上的就很清晰,但是legend的文字就很模糊,即使放大了也很模糊。
我们尝试了将页面通过scale放大,发现效果很明显,比如柱状图(4)和折线图(9)上的文字就很清晰了。但是出现了新的问题,就是失败率很高。
(TODO)scale较大时,失败率非常高
比如scale设置为5,绝大部分视频都生成失败了。
似乎和进程数有关系?我设置了进程数为3个,都成功了。
chrome进程数远大于程序设置的多进程数量
比如我程序设置了多进程数量最大为16,但是查看windows的chrome.exe进程的数量,有多达83个,且每个进程的PID都不一样。
无法播放mp4文件
https://github.com/tungs/timecut/issues/19
有人建议安装个Chrome浏览器来解决
这里列出了Chromium支持的几种视频格式:
https://www.chromium.org/audio-video
VP8
VP9
AV1 [Only Chrome OS, Linux, macOS, and Windows at present]
Theora [Except on Android variants]
H.264 [Google Chrome only]
MPEG-4 [Google Chrome OS only]
因为HTML中的video标签只支持mp4格式,因此即使将视频转为上述格式也不行。
CentOS安装Chrome浏览器:
https://blog.csdn.net/yelllowcong/article/details/80159963
PS:我没安装glib2,等验证下是否可行
#安装glib2 yum update glib2 -y
(精)preparePage无法等待到后续添加的页面元素
场景描述
我们采用了视频作为背景,加入到网页中去。但是视频文件比较大,加载是需要一定时间的,因此我们想通过preparePage()这个配置项,等视频加载完成后,再执行截屏操作。
我们是在视频加载完成后,给页面写上一个新的元素,然后在preparePage中判断,如果出现了这个元素,就开始截屏;但是我们发现preparePage的回调中,永远获取不到这个元素。
然后我们发现,单纯的puppeteer播放视频是没问题的,之所以出现这个问题,是因为加入了anime.js的原因,在timeline的回调里面,生成了这个元素;而anime.js是通过requestAnimationFrame()来实现的,因此猜测和timecut的requestAnimationFrame阻塞机制有关系。
然后我们不使用anime.js,直接通过timecut,在页面中的视频加载成功后,通过requestAnimationFrame追加元素,发现preparePage中也识别不了,因此确定肯定是和timecut的requestAnimationFrame阻塞机制有关系。
页面中追加元素的代码:
1 | |
timecut中preparePage做的处理:
1 | |
preparePage的原理
想要解决这个问题,得弄清楚2个事情:
1、Puppeteer的preparePage的原理,错了,应该是waitForSelector()的原理
我猜测一下,waitForSelector应该是每一帧里面去尝试读一下该元素的属性。
2、timesnap阻塞requestAnimationFrame的原理
到底阻塞了啥?应该就是截屏操作吧?也就是上一个截屏没完成,不播放下一帧。但是没等到元素,我肯定不会截屏啊,这样就导致出现死循环了。
也就是timesnap的阻塞机制和Puppeteer的waitForSelector在流程逻辑上冲突了。
timesnap压根没考虑waitForSelector,即页面元素后续添加的情况。这属于timesnap的设计缺陷?
经测试,一帧都不播放了,没有任何一个requestAnimationFrame被触发。
仔细调试timesnap的源码,我们发现它这样改写了requestAnimationFrame的机制:

我们通过requestAnimationFrame函数加入的每一帧的自定义逻辑,都会通过timesnap的_requestAnimationFrame,被加入_animationFrameBlocks这个队列中。但是注意只是能成功加入,执行是另外的逻辑触发的。
这些自定义逻辑被执行顺序大致是这样的:
preparePage->处理每一帧截屏的markers->goToTimeAndAnimateForCapture/goToTimeAndAnimate-> window._timesnap_runAnimationFrames->_runAnimationFrames

具体实现可以看一下timesnap/index.js里面的逻辑:
1 | |
1 | |
而在这个timesnap/index.js中,需要先等preparePage()执行完毕,才能执行后续的处理markers;而改写后的requestAnimationFrame,只有在markers中才会被触发执行。所以这就形成了这个现象:
preparePage()等不到页面元素,因此一直卡住
preparePage()一直没有resolve,因此无法触发markers的初始化
markers未执行初始化逻辑,因此不会触发runAnimationFrames
不触发runAnimationFrames,之前通过requestAnimationFrame注册的给页面添加元素的操作,一直不成功
了解了这个机制,那么就很容易制定解决方案了。我们可以将preparePage中判断元素是否存在的逻辑,移动到preparePageForScreenshot中,在第一帧截屏之前,做这个元素是否存在的判断:
1 | |
不过后面我们发现这种处理方式是有问题的:因为Vue中调用了nexttick,实际上生成video元素是在第二次requestAnimationFrame里,这种方案只执行了第一次requestAnimationFrame,进不去第二次requestAnimationFrame。
因此我们修改了解决方案:不在onBegin中生成#video标签,而是改为直接写在Vue的created方法里面:

其他问题
我发现可以获取到新加的元素,但是视频不播放了,经排查,是因为我selector写错了。
资料
测试用的代码
testPreparePage.js:
1 | |
index.html:
1 | |
参考资料
【精】浏览器工作原理讲解:
https://zhuanlan.zhihu.com/p/102149546
https://www.jianshu.com/p/bb85f89375ce
中文文档: