使用Chrome來做一張長截圖

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庫,可以很方便的對圖片做處理。

“`python

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/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章