복붙노트

[PYTHON] chromedriver가있는 Selenium Python으로 전체 페이지의 스크린 샷 찍기

PYTHON

chromedriver가있는 Selenium Python으로 전체 페이지의 스크린 샷 찍기

다양한 접근법을 시도한 후에 ... chromedriver, selenium 및 python으로 전체 페이지 스크린 샷을 찍는이 페이지를 우연히 발견했습니다.

원본 코드 : http://seleniumpythonqa.blogspot.com/2015/08/generate-full-page-screenshot-in-chrome.html (이 게시물의 코드는 아래에 복사되어 있습니다)

그것은 PIL을 사용하고 멋지게 작동합니다 !!!!! 그러나 한 가지 문제가 있습니다 ... 그것은 고정 된 헤더를 캡처하고 전체 페이지에 대해 반복하며 페이지가 변경되는 동안 페이지의 일부분을 누락시킵니다. 스크린 샷을 찍을 샘플 URL :

http://www.w3schools.com/js/default.asp

이 코드로 반복되는 헤더를 피하는 방법 ... 아니면 파이썬 만 사용하는 더 좋은 옵션이 있습니까? (나는 자바를 모르며 자바를 사용하고 싶지 않습니다).

아래의 현재 결과 및 샘플 코드의 스크린 샷을 참조하십시오.

test.py

"""
This script uses a simplified version of the one here:
https://snipt.net/restrada/python-selenium-workaround-for-full-page-screenshot-using-chromedriver-2x/

It contains the *crucial* correction added in the comments by Jason Coutu.
"""

import sys

from selenium import webdriver
import unittest

import util

class Test(unittest.TestCase):
    """ Demonstration: Get Chrome to generate fullscreen screenshot """

    def setUp(self):
        self.driver = webdriver.Chrome()

    def tearDown(self):
        self.driver.quit()

    def test_fullpage_screenshot(self):
        ''' Generate document-height screenshot '''
        #url = "http://effbot.org/imagingbook/introduction.htm"
        url = "http://www.w3schools.com/js/default.asp"
        self.driver.get(url)
        util.fullpage_screenshot(self.driver, "test.png")


if __name__ == "__main__":
    unittest.main(argv=[sys.argv[0]])

util.py

import os
import time

from PIL import Image

def fullpage_screenshot(driver, file):

        print("Starting chrome full page screenshot workaround ...")

        total_width = driver.execute_script("return document.body.offsetWidth")
        total_height = driver.execute_script("return document.body.parentNode.scrollHeight")
        viewport_width = driver.execute_script("return document.body.clientWidth")
        viewport_height = driver.execute_script("return window.innerHeight")
        print("Total: ({0}, {1}), Viewport: ({2},{3})".format(total_width, total_height,viewport_width,viewport_height))
        rectangles = []

        i = 0
        while i < total_height:
            ii = 0
            top_height = i + viewport_height

            if top_height > total_height:
                top_height = total_height

            while ii < total_width:
                top_width = ii + viewport_width

                if top_width > total_width:
                    top_width = total_width

                print("Appending rectangle ({0},{1},{2},{3})".format(ii, i, top_width, top_height))
                rectangles.append((ii, i, top_width,top_height))

                ii = ii + viewport_width

            i = i + viewport_height

        stitched_image = Image.new('RGB', (total_width, total_height))
        previous = None
        part = 0

        for rectangle in rectangles:
            if not previous is None:
                driver.execute_script("window.scrollTo({0}, {1})".format(rectangle[0], rectangle[1]))
                print("Scrolled To ({0},{1})".format(rectangle[0], rectangle[1]))
                time.sleep(0.2)

            file_name = "part_{0}.png".format(part)
            print("Capturing {0} ...".format(file_name))

            driver.get_screenshot_as_file(file_name)
            screenshot = Image.open(file_name)

            if rectangle[1] + viewport_height > total_height:
                offset = (rectangle[0], total_height - viewport_height)
            else:
                offset = (rectangle[0], rectangle[1])

            print("Adding to stitched image with offset ({0}, {1})".format(offset[0],offset[1]))
            stitched_image.paste(screenshot, offset)

            del screenshot
            os.remove(file_name)
            part = part + 1
            previous = rectangle

        stitched_image.save(file)
        print("Finishing chrome full page screenshot workaround...")
        return True

해결법

  1. ==============================

    1.스크린 샷 이전에 헤더의 CSS를 변경하면됩니다.

    스크린 샷 이전에 헤더의 CSS를 변경하면됩니다.

    topnav = driver.find_element_by_id("topnav")
    driver.execute_script("arguments[0].setAttribute('style', 'position: absolute; top: 0px;')", topnav) 
    

    편집 : 창 스크롤 후이 줄을 넣어 :

    driver.execute_script("document.getElementById('topnav').setAttribute('style', 'position: absolute; top: 0px;');")
    

    따라서 util.py에서는 다음과 같이됩니다.

    driver.execute_script("window.scrollTo({0}, {1})".format(rectangle[0], rectangle[1]))
    driver.execute_script("document.getElementById('topnav').setAttribute('style', 'position: absolute; top: 0px;');")
    

    사이트에서 header 태그를 사용하는 경우 find_element_by_tag_name ( "header")을 사용하여이를 수행 할 수 있습니다.

  2. ==============================

    2.

    element = driver.find_element_by_tag_name('body')
    element_png = element.screenshot_as_png
    with open("test2.png", "wb") as file:
        file.write(element_png)
    

    이것은 나를 위해 작동합니다. 전체 페이지를 스크린 샷으로 저장합니다. 자세한 내용은 api 문서를 참조하십시오. http://selenium-python.readthedocs.io/api.html

  3. ==============================

    3.@Moshisho의 접근 방식을 알고 난 후에.

    @Moshisho의 접근 방식을 알고 난 후에.

    내 독립 실행 형 작업 스크립트는 ... (각 스크롤 및 위치 후에 절전 0.2 추가됨)

    import sys
    from selenium import webdriver
    import util
    import os
    import time
    from PIL import Image
    
    def fullpage_screenshot(driver, file):
    
            print("Starting chrome full page screenshot workaround ...")
    
            total_width = driver.execute_script("return document.body.offsetWidth")
            total_height = driver.execute_script("return document.body.parentNode.scrollHeight")
            viewport_width = driver.execute_script("return document.body.clientWidth")
            viewport_height = driver.execute_script("return window.innerHeight")
            print("Total: ({0}, {1}), Viewport: ({2},{3})".format(total_width, total_height,viewport_width,viewport_height))
            rectangles = []
    
            i = 0
            while i < total_height:
                ii = 0
                top_height = i + viewport_height
    
                if top_height > total_height:
                    top_height = total_height
    
                while ii < total_width:
                    top_width = ii + viewport_width
    
                    if top_width > total_width:
                        top_width = total_width
    
                    print("Appending rectangle ({0},{1},{2},{3})".format(ii, i, top_width, top_height))
                    rectangles.append((ii, i, top_width,top_height))
    
                    ii = ii + viewport_width
    
                i = i + viewport_height
    
            stitched_image = Image.new('RGB', (total_width, total_height))
            previous = None
            part = 0
    
            for rectangle in rectangles:
                if not previous is None:
                    driver.execute_script("window.scrollTo({0}, {1})".format(rectangle[0], rectangle[1]))
                    time.sleep(0.2)
                    driver.execute_script("document.getElementById('topnav').setAttribute('style', 'position: absolute; top: 0px;');")
                    time.sleep(0.2)
                    print("Scrolled To ({0},{1})".format(rectangle[0], rectangle[1]))
                    time.sleep(0.2)
    
                file_name = "part_{0}.png".format(part)
                print("Capturing {0} ...".format(file_name))
    
                driver.get_screenshot_as_file(file_name)
                screenshot = Image.open(file_name)
    
                if rectangle[1] + viewport_height > total_height:
                    offset = (rectangle[0], total_height - viewport_height)
                else:
                    offset = (rectangle[0], rectangle[1])
    
                print("Adding to stitched image with offset ({0}, {1})".format(offset[0],offset[1]))
                stitched_image.paste(screenshot, offset)
    
                del screenshot
                os.remove(file_name)
                part = part + 1
                previous = rectangle
    
            stitched_image.save(file)
            print("Finishing chrome full page screenshot workaround...")
            return True
    
    
    driver = webdriver.Chrome()
    
    ''' Generate document-height screenshot '''
    url = "http://effbot.org/imagingbook/introduction.htm"
    url = "http://www.w3schools.com/js/default.asp"
    driver.get(url)
    fullpage_screenshot(driver, "test1236.png")
    
  4. ==============================

    4.사람들이 여전히이 문제를 겪고 있는지 확실하지 않습니다. 나는 꽤 잘 작동하는 작은 해킹을 해왔고 동적 영역과 잘 맞습니다. 희망이 도움이된다.

    사람들이 여전히이 문제를 겪고 있는지 확실하지 않습니다. 나는 꽤 잘 작동하는 작은 해킹을 해왔고 동적 영역과 잘 맞습니다. 희망이 도움이된다.

    # 1. get dimensions
    browser = webdriver.Chrome(chrome_options=options)
    browser.set_window_size(default_width, default_height)
    browser.get(url)
    time.sleep(sometime)
    total_height = browser.execute_script("return document.body.parentNode.scrollHeight")
    browser.quit()
    
    # 2. get screenshot
    browser = webdriver.Chrome(chrome_options=options)
    browser.set_window_size(default_width, total_height)
    browser.get(url)  
    browser.save_screenshot(screenshot_path)
    
  5. ==============================

    5.나는 Python 3.6을위한 코드를 변경했다. 아마 누군가에게 유용 할 것이다.

    나는 Python 3.6을위한 코드를 변경했다. 아마 누군가에게 유용 할 것이다.

    from selenium import webdriver
    from sys import stdout
    from selenium.webdriver.common.by import By
    from selenium.webdriver.common.keys import Keys
    from selenium.webdriver.support.ui import WebDriverWait
    from selenium.webdriver.support import expected_conditions as EC
    from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
    import unittest
    #from Login_Page import Login_Page
    from selenium.webdriver.firefox.firefox_binary import FirefoxBinary
    from io import BytesIO
    from PIL import Image
    
    def testdenovoUIavailable(self):
            binary = FirefoxBinary("C:\\Mozilla Firefox\\firefox.exe") 
            self.driver  = webdriver.Firefox(firefox_binary=binary)
            verbose = 0
    
            #open page
            self.driver.get("http://yandex.ru")
    
            #hide fixed header        
            #js_hide_header=' var x = document.getElementsByClassName("topnavbar-wrapper ng-scope")[0];x[\'style\'] = \'display:none\';'
            #self.driver.execute_script(js_hide_header)
    
            #get total height of page
            js = 'return Math.max( document.body.scrollHeight, document.body.offsetHeight,  document.documentElement.clientHeight,  document.documentElement.scrollHeight,  document.documentElement.offsetHeight);'
    
            scrollheight = self.driver.execute_script(js)
            if verbose > 0:
                print(scrollheight)
    
            slices = []
            offset = 0
            offset_arr=[]
    
            #separate full screen in parts and make printscreens
            while offset < scrollheight:
                if verbose > 0: 
                    print(offset)
    
                #scroll to size of page 
                if (scrollheight-offset)<offset:
                    #if part of screen is the last one, we need to scroll just on rest of page
                    self.driver.execute_script("window.scrollTo(0, %s);" % (scrollheight-offset))
                    offset_arr.append(scrollheight-offset)
                else:
                    self.driver.execute_script("window.scrollTo(0, %s);" % offset)
                    offset_arr.append(offset)
    
                #create image (in Python 3.6 use BytesIO)
                img = Image.open(BytesIO(self.driver.get_screenshot_as_png()))
    
    
                offset += img.size[1]
                #append new printscreen to array
                slices.append(img)
    
    
                if verbose > 0:
                    self.driver.get_screenshot_as_file('screen_%s.jpg' % (offset))
                    print(scrollheight)
    
            #create image with 
            screenshot = Image.new('RGB', (slices[0].size[0], scrollheight))
            offset = 0
            offset2= 0
            #now glue all images together
            for img in slices:
                screenshot.paste(img, (0, offset_arr[offset2])) 
                offset += img.size[1]
                offset2+= 1      
    
            screenshot.save('test.png')
    
  6. ==============================

    6.이 대답은 am05mhz와 Javed Karim의 사전 답변을 향상시킵니다.

    이 대답은 am05mhz와 Javed Karim의 사전 답변을 향상시킵니다.

    헤드리스 모드를 가정하고 창 크기 옵션이 초기에 설정되지 않았습니다. 이 함수를 호출하기 전에 페이지가 완전히 또는 충분히로드되었는지 확인하십시오.

    너비와 높이를 모두 필요한 것으로 설정하려고 시도합니다. 전체 페이지의 스크린 샷에는 때때로 불필요한 세로 스크롤 막대가 포함될 수 있습니다. 일반적으로 스크롤바를 피하는 한 가지 방법은 body 요소의 스크린 샷을 대신 찍는 것입니다. 스크린 샷을 저장하면 원래의 크기로 되돌아 가고 다음 스크린 샷의 크기가 올바르게 설정되지 않을 수 있습니다.

    궁극적으로이 기술은 일부 예제에서는 여전히 완벽하게 작동하지 않을 수 있습니다.

    def save_screenshot(driver: webdriver.Chrome, path: str = '/tmp/screenshot.png'):
        # Ref: https://stackoverflow.com/a/52572919/
        original_size = driver.get_window_size()
        required_width = driver.execute_script('return document.body.parentNode.scrollWidth')
        required_height = driver.execute_script('return document.body.parentNode.scrollHeight')
        driver.set_window_size(required_width, required_height)
        # driver.save_screenshot(path)  # has scrollbar
        driver.find_element_by_tag_name('body').screenshot(path)  # avoids scrollbar
        driver.set_window_size(original_size['width'], original_size['height'])
    

    3.6 이전의 Python을 사용하는 경우 함수 정의에서 유형 주석을 제거하십시오.

  7. ==============================

    7.

    element=driver.find_element_by_tag_name('body')
    element_png = element.screenshot_as_png
    with open("test2.png", "wb") as file:
        file.write(element_png)
    

    앞의 2 행에서 제안한 코드에 오류가 있습니다. 수정 된 오류가 있습니다. 여기 멍청한 놈이기 때문에 아직 내 자신의 소식을 수정할 수 없습니다.

    때때로 baove는 최상의 결과를 얻지 못합니다. 따라서 다른 방법을 사용하여 모든 요소의 높이를 얻고 합계하여 캡처 높이를 아래와 같이 설정합니다.

    element=driver.find_elements_by_xpath("/html/child::*/child::*")
        eheight=set()
        for e in element:
            eheight.add(round(e.size["height"]))
        print (eheight)
        total_height = sum(eheight)
        driver.execute_script("document.getElementsByTagName('html')[0].setAttribute('style', 'height:"+str(total_height)+"px')")
        element=driver.find_element_by_tag_name('body')
        element_png = element.screenshot_as_png
        with open(fname, "wb") as file:
            file.write(element_png)
    

    BTW, 그것은 FF로 작동합니다.

  8. ==============================

    8.@ihightower 및 @ A.Minachev의 코드를 약간 수정하고 Mac 망막에서 작동하게하십시오.

    @ihightower 및 @ A.Minachev의 코드를 약간 수정하고 Mac 망막에서 작동하게하십시오.

    import time
    from PIL import Image
    from io import BytesIO
    
    def fullpage_screenshot(driver, file, scroll_delay=0.3):
        device_pixel_ratio = driver.execute_script('return window.devicePixelRatio')
    
        total_height = driver.execute_script('return document.body.parentNode.scrollHeight')
        viewport_height = driver.execute_script('return window.innerHeight')
        total_width = driver.execute_script('return document.body.offsetWidth')
        viewport_width = driver.execute_script("return document.body.clientWidth")
    
        # this implementation assume (viewport_width == total_width)
        assert(viewport_width == total_width)
    
        # scroll the page, take screenshots and save screenshots to slices
        offset = 0  # height
        slices = {}
        while offset < total_height:
            if offset + viewport_height > total_height:
                offset = total_height - viewport_height
    
            driver.execute_script('window.scrollTo({0}, {1})'.format(0, offset))
            time.sleep(scroll_delay)
    
            img = Image.open(BytesIO(driver.get_screenshot_as_png()))
            slices[offset] = img
    
            offset = offset + viewport_height
    
        # combine image slices
        stitched_image = Image.new('RGB', (total_width * device_pixel_ratio, total_height * device_pixel_ratio))
        for offset, image in slices.items():
            stitched_image.paste(image, (0, offset * device_pixel_ratio))
        stitched_image.save(file)
    
    fullpage_screenshot(driver, 'test.png')
    
  9. ==============================

    9.한 번만 URL을 얻을 수 있도록 제레미의 대답을 수정했습니다.

    한 번만 URL을 얻을 수 있도록 제레미의 대답을 수정했습니다.

    browser = webdriver.Chrome(chrome_options=options)
    browser.set_window_size(default_width, default_height)
    browser.get(url)
    height = browser.execute_script("return document.body.parentNode.scrollHeight")
    
    # 2. get screenshot
    browser.set_window_size(default_width, height)
    browser.save_screenshot(screenshot_path)
    
    browser.quit()
    
  10. from https://stackoverflow.com/questions/41721734/take-screenshot-of-full-page-with-selenium-python-with-chromedriver by cc-by-sa and MIT license