美文网首页
使用Chrome来做一张长截图

使用Chrome来做一张长截图

作者: EvinK | 来源:发表于2018-07-09 10:06 被阅读0次
    Chrome

    遇见一篇有意思的文章,或者是在购物时发现了好东西,需要分享。

    不想发送链接,也许搞一个长截图是个很好的选择。

    Selenium

    Selenium是一种自动化测试工具,它支持各种浏览器,包括 Chrome,Safari,Firefox 等主流界面式浏览器,如果你在这些浏览器里面安装一个 Selenium 的插件,那么便可以方便地实现Web界面的测试。换句话说叫 Selenium 支持这些浏览器驱动。Selenium支持多种语言开发,比如 Java,C,Ruby, Python等等。

    在这个案例中,我们使用基于Python3的运行环境进行演示。

    使用pip3安装selenium

    
    $ pip3 install selenium
    
    

    获取chrome-driver

    在淘宝npm镜像中可以找到Chrome版本对应的驱动: http://npm.taobao.org/mirrors/chromedriver/

    初始化浏览器

    
        def init_browser():
            chrome_options = Options()
            # --headless参数表示,Chrome将不会有一个可视化的图形界面
            # chrome_options.add_argument("--headless")
            chrome_options.add_argument("--disable-gpu")
            chrome_options.add_argument("--disable-web-security")
            # 以iPhone 6的屏幕宽度作为基准
            mobile_emulation = { "deviceName": "iPhone 6" }
            chrome_options.add_experimental_option("mobileEmulation", mobile_emulation)
            return webdriver.Chrome("./chromedriver",
                                    chrome_options=chrome_options)
    
        browser = inti_browser()
    
    

    调用Webdriver提供的API, 获取网页基本信息

    
        url = "https://code.evink.me"
    
        # browser是我们刚刚初始化的浏览器实例
        # 设置页面渲染超时
        browser.set_page_load_timeout(30)
        # 设置脚本执行超时
        browser.set_script_timeout(100)
        # 获取网页资源
        browser.get(url)
        # 给浏览器设置一个默认的初始宽高(非必要)
        browser.set_window_size(375, 1000)
    
    

    Chrome在iPhone 6的模拟环境下进行网页渲染,其宽是一定的,所以可以通过调用运行于webdriver内部的javascript代码,获得我们想要的内容。

    webdrive提供了丰富的接口供我们全方位操控这个小小的浏览器。

    
        # browser是我们刚刚初始化的浏览器实例
        body_height = browser.body.get_attribute("clientHeight")
    
    

    或者,这里还有一个更加可操作的方法,直接在webdriver里跑js代码。Selenium提供了execute_script(str)接口,可以让用户通过自己更为熟悉的方式,得到自己想要的内容。

    
        # browser是我们刚刚初始化的浏览器实例
        body_height = browser.execute_script(get_script("body_height")))
    
        # get_script(str) -> str
        def get_script(_type):
            if _type == "body_height":
                return """
                        // get body_height
                        return document.getElementsByTagName("body")[0].clientHeight;
                """
    
    

    滚屏截图

    webdriver提供了一个保存当前浏览器窗口截图的接口save_screenshot(path)

    我们只需要让网页沿着一个预设定的高度滚动就好。

    这里,这个高度是667(基于我们以iPhone 6的虚拟环境初始化的浏览器)。设定大于667的高度,每次也并不会截取到更多的页面内容,而小于667的高度,会让你最后进行图片处理的时候非常头疼。

    
        dir_path = "tmp_screenshots"
        filename = "evink" + int(datetime.now().timestamp())
        paging_list = []
    
        def take_screenshot():
            # loop_times由网页高度和单屏高度计算而来
            for i in range(loop_times):
                browser.execute_script(get_script("scroll_window") % (i + 1))
                # 截取屏幕
                path = "%s/%s_%s.png"%(dir_path, filename, i)
                browser.save_screenshot(path)
                paging_list.append(path)
            return paging_list
    
    
        def get_script(_type):
    
            if _type == "scroll_window":
                return """
                        // 滚动的次数
                        var m = %s;
                        // 屏幕range
                        var begin = 0;
                        var end = 667;
                        // 滚屏
                        window.scrollTo(begin, end * m);
                    """
            if _type == "scroll_y":
                return """
                    return window.scrollY;
                """
    
    

    合成图片

    上一步结束之后,我们在/tmp_screenshots目录下会发现若干png格式的图片,利用Python中的PIL Image库,可以很方便的对图片做处理。

    
       def paste_imgs(self):
           # loop_times由网页高度和单屏高度计算而来
           for i in range(loop_times):
               path = "%s/%s_%s.png"%(dir_path, filename, i)
               if os.path.exists(path):
                   continue
               else:
                   # 记录存在的最大图片张数
                   max_screen = i
           # 根据总图片张数计算合成的单张图片高度
           page_total_height = 667 * 2 * loop_times
           # 声明一张空白的图片实例
           image = Image.new(
               'RGB', (375 * 2, page_total_height), (255, 255, 255))
    
           for i in range(loop_times):
               path = "%s/%s_%s.png"%(dir_path, filename, i)
               from_img = Image.open(path)
               # 粘贴图片
               image.paste(from_img, (0, 1334 * ( i + 1 )))
    
           path = "%s/%s_part_%s.jpg"%(dir_path, filename, part+1)
           # 保存图片 以JPEG格式,60质量
           image.save(path,format='JPEG', quality=60)
           return "%s/%s.jpg"%(dir_path, filename)
    
    

    处理细节

    上一步结束之后,我们获得了一张完整的图片,但是,你一定会发现很多小细节没有处理。

    图片未被加载

    如果你要截取的网页采用了图片懒加载模式(可以提升访问速度),你会发现所截取的网页的图片都被灰色的色块所替代。

    我们可以分析网页中,未被加载的图片是否有什么共同点。比如说,是否含有特定的class,是否有自定义的属性值。

    
        # 假设图片未加载时,此网页图片的class会有 "img_loading"
    
        # 获取所有的图片元素
        imgs = browser.find_elements_by_tag_name("img")
        img_load_start = datetime.now().timestamp()
        while True:
            # 已加载的图片数
            img_loadeds = 0
            for img in imgs:
                # 拿到class属性
                clz = img.get_attribute("class")
                if clz.find("img_loading") == -1:
                    img_loadeds += 1
            print("已经加载完毕的图像数:%s"%img_loadeds)
            if img_loadeds == len(wait_load_imgs):
                print("all imgs loaded")
                break
            # 给他设置一个超时
            if datetime.now().timestamp() - img_load_start > 60:
                print("imgs loading timeout")
                break
            sleep(0.1)
    
    

    图片底部 显示不完全 / 留有大量的空白 / 拼接不完美

    造成这个原因,无非是高度和留余问题。

    网页高度

    某些网页上,在滚屏完毕(即所有图片都被加载后)的高度和网页初始化后的高度并不一致。

    高度的错误会导致生成图片时抛出异常

    
        # 妈妈让我再滚一次
        def scroll_window(need_renew_height=False):
    
            for pre_scroll in range(loop_times + 1):
                self.browser.execute_script(self.get_script("scroll_window")%pre_scroll)
                sleep(0.5)
    
            print("-- 已经滚完 --")
    
            if need_renew_height:
                # 重新录入高度
                body = browser.find_element_by_id('activity-detail')
                body_height = int(body.get_attribute("clientHeight"))
                loop_times = math.ceil(body_height / height)
    
        def get_script(self, _type):
    
            if _type == "scroll_window":
                return """
                        // 滚动的次数
                        var m = %s;
                        // 取出所有图片
                        var imgs = document.querySelectorAll('img');
                        // 屏幕range
                        var begin = 0;
                        var end = 667;
                        // 滚屏
                        window.scrollTo(begin, end * m);
                        // 图片的相对距离
                        for(var i = 0;i < imgs.length;i++){
                            var y = imgs[i].getBoundingClientRect()["y"];
                            if(y >= begin && y <= end){
                                imgs[i].setAttribute("type", "wait_load");
                            }
                        }
                """
    
    

    处理好收尾工作

    在生成滚屏时,最后一张图片含有两种状态。

    • 完美占据一屏的空间 (375 * 667)
    • 只占据部分空间,底部含有留白

    处理好最后一屏图片和倒数第二屏的关系,就可以避免出现图片拼接不完美的情况。

    图片 黑屏 / 损坏

    图片过长,尝试按照固定的屏幕数,将一张图切成几张小图。


    原文地址: https://code.evink.me/2018/07/post/python-use-chrome-to-make-a-long-page-screenshot/

    相关文章

      网友评论

          本文标题:使用Chrome来做一张长截图

          本文链接:https://www.haomeiwen.com/subject/qxjzuftx.html