Puppeteer 系列踩坑日志—1—批量截图变白

在平时使用puppeteer最多的就是截图了(每日批量自动截图),我的目标是X宝的一些店铺首页,这次遇到的问题就是在一些无线端,商家的页面非常长,甚至多的达到了6-10万像素。经常出现白屏或者残缺现象。

第一坑:加载问题?
  • 遇到类似问题。首先我想到的就是页面的资源加载问题,是不是puppeteer截图的时候没有加载出来呢?因此我根据页面的滚动查看了下,的确有类似的情况。
  • 首先无线端的页面如果在无状态的登录下(没有设置userdata,cookie等),会弹出登录,简单看了下dom,其实就是一个悬浮层,清理掉就好了。
  • 第二步,截图的时间久了,发现有时候会出现页面正在loading,那么这种类型的,我让promise直接reject掉(还能怎么办,放在错误队列里面,晚点再看试咯!)
  • 第三步,当然作为一位强迫症患者,本人及其不习惯乱七八糟的字体,因此统一了一下,全部设置为微软雅黑,于是有了style的样式。
image-20200317183752809.png
  • 第四步,在做滚动的时候,发现pc端其实很简单,body.scrollBy直接可以滚动,但是到了无线端,坑来了!这XXX的,怎么动都动不了,打开调试看了下,X宝的无线端DOM全都是DIVDIVDIVDIVDIV(写个选择器都很麻烦,等会后面会讲到怎么快速去找到自己想要的元素,道高一尺魔高一丈)。好在后来看到了曙光,其实关键的滚动就在于.rax-scrollview这个div身上,因此解决了该滚动问题。

  • 好了,说了那么多,不如来一份代码全面些:

    async function scrollToBottom(page) {
        return await page.evaluate(() => {
            return new Promise((res, rej) => {
                if (document.querySelector('body > div > span')) {
                    if (document.querySelector('body > div > span').innerText === '加载中...') {
                        rej('页面截图太快,天猫loading不出来')
                    }
                }
                var style = document.createElement('style');
                style.innerHTML = '*{font-family: "微软雅黑" !important;}';
                document.body.appendChild(style);
                var totalH = 0;
                var distance = 250;
                var clientWidth = document.body.clientWidth;
                var selectorEle = '.rax-scrollview'; //淘宝专属滑动dom
                var scrollEle = document.querySelector(selectorEle) != null ? document.querySelector(selectorEle) : document.body;
                var scrollEleS = document.querySelector(selectorEle) != null ? document.querySelector(selectorEle) : window;
                var timer = setInterval(() => {
                    var scrollHeight = scrollEle.scrollHeight;
                    scrollEleS.scrollBy(0, distance);
                    totalH += distance;
                    if (document.querySelector('body > div.J_MIDDLEWARE_FRAME_WIDGET > div > a')) {
                        document.querySelector('body > div.J_MIDDLEWARE_FRAME_WIDGET > div > a').click();
                    }
                    if (totalH >= scrollHeight) {
                        clearInterval(timer);
                        scrollEleS.scrollTo(0, 0); //返回到顶部,然后再截图。防止从底部开始截图,部分活动页面不回去就会出现问题
                        res({
                            w: clientWidth,
                            h: scrollHeight
                        })
                    }
                }, 250)
    
            })
        })
    }
    
第二坑:截图问题?
  • 在服务器跑了一遍,发现又出现了空白,这次的空白明显比上次还多...

  • 考虑了方方面面的问题后(不喜欢绕太多弯子),最后我把问题锁定在了page.screenshot这个环节上,因为headless关掉,我直接看着页面加载完完毕的!居然还出现了空白,不是你这个api,还能是谁?

  • 在一顿操作(搜索引擎+github+官网),发现了一个有趣的代码块:https://github.com/ChromeDevTools/devtools-frontend/blob/f5d825ac1bb6eca13782947e30e5c5c78b9de1f0/front_end/emulation/DeviceModeModel.js#L728 其实就是可能是限制了截图高度。如果过高就可能出现类似问题。

  • 当然,咱们搞代码的,也要注意严谨,为了证明这个观点,我用了一些1000-2000像素高度的网站来测试。100个左右的网站,截图都是非常完美的。这也差不多证明了是这里的问题。

  • 既然问题找到了,那么就来解决问题吧。首先祭出代码,我们根据代码来“事后诸葛亮”:

        let ImageHeight = 5000; //pc限制一次截图高度
        if (!!!pageInfo.type) {
            ImageHeight = 1000; //mob限制一次截图高度
        }
        let tempLength = Math.ceil(data.h / ImageHeight)//计算下得截图多少次
        let imagesArr = []//储存一下我们截图的序列
        for (let i = 0, j = 1; i < tempLength; i++) {//for直接搞起(async await大法好!!!!)
            await page.screenshot({
                path: 你的路径,
                clip: {
                    x: 0,
                    y: i * ImageHeight,
                    width: _w,
                    height: j * ImageHeight
                }
            }).then(res => {
                console.log('截图了', i, '张');
                imagesArr.push(`你的路径`)
            }).catch(err => {
                console.log('截图失败!');
            })
        }
    
    • 逻辑就是 一片一片的截图,这样的话,问题基本上就解决了!

    • 代码非常简单,简单到我都觉得不需要解释,但是还是唠叨两句,我先判断下pc还是无线(pc没必要一点点截图,因为pc大概率是没啥问题的,除非特别特别长,可以根据实际情况修改,我觉得5000差不多了)。

    • 另外需要提醒一点:我这里5000和1000的阈值,需要根据你的计算机/服务器情况决定,如果配置不行的话,可能还需要降低,之前遇到过在一台很老的笔记本上测试,发现1000都会=-=白屏...(都0202年了,你问我,为什么要拿一台很老的笔记本跑这个?因为写文章要严谨啊!得多测试啊!)

    • 我们再进行一次储存到数组里(方便等会进行合并)。因为涉及到多任务(总不能真的就开一个进程吧),如果不进行储存,那么之后合并完删除这些小碎片,还要区分不同进程的碎片截图,那就怕有点脑壳疼。所以每个进程自己留个“小本本”~ 咱们等会就知道该删哪些碎片截图了。

  • 这里需要注意的是,合并图片,使用的是GM这个图像库(解决这个问题才花了我1小时不到,本来想图省事找个轻量的,结果发现都没GM简单粗暴,唉,花了我整整一下午。最后换回GM库,5分钟搞定。。)

  • 代码非常简单,但是安装GM,需要提前安装它的软件,百度一下哪里都有。windows就下一步一下步,linux也基本上差不多,wget一下需要的安装包,然后tar解压下XXXXX一顿常规操作,差不多就能搞定,这里不多讲。

  • 示范一下GM部分代码:

let deleteFile = [...imagesArr];
await gmMerge(foldname,pathName,imagesArr)


function gmMerge(foldname,pathName,imagesArr) {
    return new Promise((res, rej) => { gm(imagesArr.shift()).append(...imagesArr).write(`./screenshot/${foldname}/${pathName}.png`, function (err) {
            if (err) {
                console.log('截图合并失败!')
                rej('截图合并失败!')
            } else {
                console.log('完整截图生成成功!');
                res('完整截图生成成功!');

            }
        });

    })
}

简单解释下,要删除的复制个小本本出来,因为我们需要将第一张截图作为初始画布,然后后续的全部加进来,我这里偷懒使用了shift方法,这样的话等会再去删除 就会漏掉,因此我这样做了。你有更简单的办法也可以,这里没什么要讲。主要是我放入了promise里面,这样的话 await gm合并函数即可。

  • 这里需要提醒下,一定要封promise,否则直接在async里面执行,很有可能任务队列结束了,这些碎片截图没删掉,还留在里面,因为没有等待操作执行完就结束了。
其他坑:
  • 除了对屏幕截图puppeteer也提供了元素截图(部分dom),当页面真的非常复杂且冗余的时候,可以采用这个做法。具体可以参考以下代码:
let footer = await page.$('#footer');    //获取到DOM对象,身上都有screenshot方法
footer.screenshot({path:'demo.png'});
  • 其实关于puppeteer的截图空白问题,其实是由来已久的,偶尔你也会发现 pc明明正常的截图也会出现类似问题,如果真的出现了意外情况(screenshot抽了),建议还是从viewport分辨率/截图大小入手去考虑,几乎大部分问题都出在这些方面。

如有遇到其他问题,评论下方可以联系我,共同学习排坑。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容

  • 100个常用的javascript函数 1、原生JavaScript实现字符串长度截取 复制代码代码如下: fun...
    老头子_d0ec阅读 353评论 0 0
  • 1.puppeteer简介 puppeteer是一个node库,是Google chrome团队官方的无界面(he...
    伊人风采_690d阅读 7,614评论 0 11
  • 简述JavaScript起源起源于美国的Netscape公司,原名为LiveScript,后改为JavaScrip...
    3ab670b99521阅读 2,986评论 0 0
  • 单例模式 适用场景:可能会在场景中使用到对象,但只有一个实例,加载时并不主动创建,需要时才创建 最常见的单例模式,...
    Obeing阅读 2,054评论 1 10
  • 以下是常用的代码收集,学习用。转自豪情博客园 1. PC - js 返回指定范围的随机数(m-n之间)的公式 re...
    自由加咖啡阅读 992评论 0 1