用Selenium + ChromeDriver 实现多屏滑动截取+拼接(有源码)

    功能来源需求。近期产品提出一个小需求,对自家的html截屏定期发送给用户,初到公司觉得小意思啊,之前做过htmltopdf的功能,然后在pdftopng不是就好了。本是其他组的活,那个组最近活比较多,本着相互帮助(zhuangbi)的原则,揽下活。想了想,应该直接html to png就搞定的,google下,果然有现成的轮子。找到一个靠谱的 轮子-webkit2png,下载包,啪啪啪敲代码搞定,收工。看看图片


纳尼,我的图表怎么少了。后来问过前端的同学,他说绘制图的时候使用了echart,目前只有Google chrome支持比较好。小意思嘛,指定浏览器不就好了,但是......webkit2png -h 后,悲剧了,它不支持指定浏览器的参数哇。

后面想想,只能用chrome,那不是用Selenium(用于操纵浏览器) + ChromeDriver(chrome浏览器驱动,用于模仿浏览器) 就可以了。继续啪啪啪低头敲代码,结果还可以。


就当我要提交代码时,发现有些图表是长的,就是需要下滑才能截取整个页面,普通的截取只能截当前页。不难嘛,google截取全页面不就好了,结果再次悲剧了,chromedriver的截图只有一页,而对于iedriver和firefox来说就没有这个必要,可以截取整个页面。这这这....

难不倒我,用JS滑动+多次截屏+合并,搞定(参考代码)(这里的JS滑动你要注意是浏览器的滑动还是DIV滑动,我在这里就被坑了。如果用网上的JS滑动不能生效的话,你就得去看DOM结构了,找到那个需要滑动的DIV)

但是上述分屏合并图片代码,还有两个比较致命的问题。 

第一是拼接的时候总是精确度不够。图片连接部分要么不全,要么重复。 

查明是以下两个原因导致: 

1.设置浏览器的高度并不是截图的高度,还需算上浏览器头部高度(宽度是一致的) 

2.用selenium打开的chrome 中,总是有安全提示,也会占用一定高度 (关闭它就好了,详见源码)

第二是最后一张图片处理的问题,我需要计算出最好一张图片和前面图片重复的面积, 

然后使用图片处理(PIL)对其进行裁剪后加入到前面的图片中。 

裁剪高度(remain_h)的是先算出总高度(h),然后对滑动高度(slide_h)取余,即remain_h=h%slide_h。 

裁剪crop(0, slide_h-remain_h, 图片长度,图片高度)。

以下是源码(需要安装Selenium+ChromeDriver+PIL)

import os
import time
from PIL import Image
from selenium import webdriver


def capture(base_url, pix_w, pix_h, filename):
    """chrome截屏     base_url- 要截屏的url    pix_w- 窗口宽     pix_h- 窗口高    filename-生成截图的文件名     """
    try:
        options = webdriver.ChromeOptions()
        # 去除安全提示
        options.add_argument('disable-infobars')
        driver = webdriver.Chrome('/Users/apple/Downloads/chromedriver', chrome_options=options)
        # 算上浏览器高度
        driver.set_window_size(pix_w, pix_h+98)
        driver.get(base_url)
        time.sleep(5)
        img_list = []
        i = 0
        while True:
            # 滚动高度
            scroll_h = str(i*pix_h)
            js = """document.getElementsByClassName("multiscreen-detail-page")[0].scrollTop=%s""" % (scroll_h)
            driver.execute_script(js)

            # 获取滚动高度和实际高度
            js1 = """return document.getElementsByClassName("multiscreen-detail-page")[0].scrollHeight.toString() +
            ',' + document.getElementsByClassName("multiscreen-detail-page")[0].scrollTop.toString()"""
            js1_result = driver.execute_script(js1)
            real_scroll_h, real_top = js1_result.split(',')[0], js1_result.split(',')[1]
            #real_scroll_h, real_top 是当前滚动条长度和当前滚动条的top,作为是否继续执行的依据,
            # 由于存在滚动条向下拉动后会加载新内容的情况,所以需要以下的判断
            #如果这次设置的top成功,则继续滚屏
            if real_top == str(i*pix_h):
                i += 1
                driver.save_screenshot('/Users/apple/static/test-'+filename + str(i)+'.png')
                img_list.append('/Users/apple/static/test-'+filename + str(i)+'.png')
                last_t = real_top
            else:
                #如果本次设置失败,看这次的top和上一次记录的top值是否相等,
                # 相等则说明没有新加载内容,且已到页面底,跳出循环
                if real_top != last_t:
                    if int(real_scroll_h) < pix_h:
                        break
                    last_t = real_top
                    # break
                else:
                    img_filename = '/Users/apple/static/test-' + filename + str(i+1)+'.png'
                    driver.save_screenshot(img_filename)
                    # img_list.append(img_filename)
                    # img_list.append('/Users/apple/static/test-' + filename + str(i+1)+'.png')
                    # 算出截取位置
                    try:
                        y = (i+1)*pix_h - int(real_scroll_h)
                        # region = (0, y*2, pix_w*2, pix_h*2)
                        region = (0, y, pix_w, pix_h)
                        img = Image.open(img_filename)
                        cropImg = img.crop(region)
                        cropImg.save(img_filename)
                        img_list.append(img_filename)
                    # 有可能会超出范围,如果存在一个图标有下拉的情况下就不截图了(也不做处理)
                    except Exception as e:
                        os.remove(img_filename)
                        print(e)
                    break
        image_merge(img_list, "/Users/apple/static", filename+'.png')
    except Exception as e:
        raise


def image_merge(images, output_dir, output_name='merge.jpg', restriction_max_width=None, restriction_max_height=None):
    """垂直合并多张图片
    images - 要合并的图片路径列表
    ouput_dir - 输出路径
    output_name - 输出文件名
    restriction_max_width - 限制合并后的图片最大宽度,如果超过将等比缩小
    restriction_max_height - 限制合并后的图片最大高度,如果超过将等比缩小
    """
    # def image_resize(img, size=(1500, 1100)):
    def image_resize(img, size=(1960, 1080)):
        """调整图片大小
        """
        try:
            if img.mode not in ('L', 'RGB'):
                img = img.convert('RGB')
            img = img.resize(size)
        except Exception as e:
            pass
        return img
    max_width = 0
    total_height = 0
    # 计算合成后图片的宽度(以最宽的为准)和高度
    for img_path in images:
        if os.path.exists(img_path):
            img = Image.open(img_path)
            width, height = img.size
            if width > max_width:
                max_width = width
            total_height += height

    # 产生一张空白图
    new_img = Image.new('RGB', (max_width, total_height), 255)
    # 合并
    x = y = 0
    for img_path in images:
        if os.path.exists(img_path):
            img = Image.open(img_path)
            width, height = img.size
            new_img.paste(img, (x, y))
            y += height

    if restriction_max_width and max_width >= restriction_max_width:
        # 如果宽带超过限制
        # 等比例缩小
        ratio = restriction_max_height / float(max_width)
        max_width = restriction_max_width
        total_height = int(total_height * ratio)
        new_img = image_resize(new_img, size=(max_width, total_height))

    if restriction_max_height and total_height >= restriction_max_height:
        # 如果高度超过限制
        # 等比例缩小
        ratio = restriction_max_height / float(total_height)
        max_width = int(max_width * ratio)
        total_height = restriction_max_height
        new_img = image_resize(new_img, size=(max_width, total_height))

    if not os.path.exists(output_dir):
        os.makedirs(output_dir)
    save_path = '%s/%s' % (output_dir, output_name)
    new_img.save(save_path)
    for img_path in images:
        os.remove(img_path)
    return save_path

url='http://www.baidu.com'
capture(url, 1920, 906, '5')

# 生产环境应该是1080的,mac是980的,去除了上下边。所以应该是1920,1010.

到此,基本解决问题了,下班回家带娃,此次事件证明简单需求可能会有很多你意想不到的坑在等着你,揽活需谨慎!!!


再说句,其实这个坑算是前端埋下的(先甩个锅),因为他用了只有Chrome支持效果好的echart(虽然效果好看,可能也找不到更好的库了),才导致了这么多的事。在此,还是提醒下各位码农,在能同样实现功能的情况下,尽可能的做到兼容哇!!!


评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值