Take screenshot of full page with Selenium Python with chromedriver
Asked Answered
T

26

74

After trying out various approaches... I have stumbled upon this page to take full-page screenshot with chromedriver, selenium and python.

The original code is here. (and I copy the code in this posting below)

It uses PIL and it works great! However, there is one issue... which is it captures fixed headers and repeats for the whole page and also misses some parts of the page during page change. sample url to take a screenshot:

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

How to avoid the repeated headers with this code... Or is there any better option which uses python only... ( i don't know java and do not want to use java).

Please see the screenshot of the current result and sample code below.

full page screenshot with repeated headers

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
Tourist answered 18/1, 2017 at 14:14 Comment(6)
I'm taking a screenshot of a page that requires multiple scrolls/stitching. Unfortunately, it's not a public URL (you can only see the page if you're logged in). Do you know why it keeps appending the header as well? res.cloudinary.com/mpyr-com/image/upload/v1551372542/…Marjorymarjy
No stitching required: https://mcmap.net/q/75735/-take-screenshot-of-full-page-with-selenium-python-with-chromedriverBreakfront
i have now changed the answer to @lizesong1988 (below) and set the longest height to be 8000px. the ele xpath for longest element always returned values around 1100px which was not good.. so i just hardcode to 8000. this is the best and easiest answer for me.Tourist
@Tourist thanks for writing the awesome code. I am facing the same issue. Is it possible to get the same code working for a div as well? In my case the scrollbar exists on a div.Telson
the easiest answer is now using playwright please see accepted answer below with new latest update info. @DeepakKumarTourist
Another solution is proposed in this post: #77381731Llewellyn
C
35

How it works: set browser height as longest as you can...

#coding=utf-8
import time
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

def test_fullpage_screenshot(self):
    # please note that we MUST use headless mode
    chrome_options = Options()
    chrome_options.add_argument('--headless')
    chrome_options.add_argument('--start-maximized')

    driver = webdriver.Chrome(chrome_options=chrome_options)

    driver.get("yoururlxxx")
    time.sleep(2)

    height = driver.execute_script('return document.documentElement.scrollHeight')
    width  = driver.execute_script('return document.documentElement.scrollWidth')
    driver.set_window_size(width, height)  # the trick
    
    time.sleep(2)
    driver.save_screenshot("screenshot1.png")
    driver.quit()

if __name__ == "__main__":
    test_fullpage_screenshot()
Clammy answered 19/10, 2019 at 2:40 Comment(4)
This by far is the easiest and best solution for me. however, the longest height element i tried various but none of them seems to work... all about 1100px height (for the webpage in this question). However, hardcoding to 8000px total_height works great! if there is any way for you to find the good xpath that can return the longest height automatically then will be even great!Tourist
@Tourist you can try getting it with driver.execute_script("return document.scrollingElement.scrollHeight;")Disk
As other pointed out below, this will only work for full page if you run with headlessLueluebke
This works well but does not execute because chrome_options has now been deprecated. The below code should now be used: options = webdriver.ChromeOptions() options.add_argument("--headless") options.add_argument('--start-maximized') driver = webdriver.Chrome(options=options)Charismatic
A
55

This answer improves upon prior answers by am05mhz and Javed Karim.

It assumes headless mode, and that a window-size option was not initially set. Before calling this function, ensure the page has loaded fully or sufficiently.

It attempts to set the width and height both to what is necessary. The screenshot of the entire page can sometimes include a needless vertical scrollbar. One way to generally avoid the scrollbar is by taking a screenshot of the body element instead. After saving a screenshot, it reverts the size to what it was originally, failing which the size for the next screenshot may not set correctly.

Ultimately this technique may still not work perfectly well for some examples.

from selenium import webdriver

def save_screenshot(driver: webdriver.Chrome, path: str = '/tmp/screenshot.png') -> None:
    # 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'])

If using Python older than 3.6, remove the type annotations from the function definition.

Actinouranium answered 29/9, 2018 at 22:4 Comment(11)
The window size in Firefox is about 74px taller than the viewport, so required_height + 74 works for me for now.Lionfish
See this post https://mcmap.net/q/75735/-take-screenshot-of-full-page-with-selenium-python-with-chromedriver for additional explanations.Breakfront
I need full screenshot of iframe. I tried above code but seems not taking full screenshot, does it need any change for iframe?Brasilin
The last line of the code(after screenshot has been taken) is also important when working in loops, as the images will get longer and longer if the line is missed.Rant
I'd like to add on. This worked perfectly for me, except sometimes the height was too large and crashed Selenium. If anyone else has trouble with crashes, try adding an upper height limit. Change set_window_size to something like driver.set_window_size(required_width, min(6000, required_height))Stotinka
This is the answer that actually works as expected. The answer above grabs only the first screen which needs to be scrolled.Belief
the easiest answer is now using playwright please see accepted answer below with new latest update info.Tourist
This doesn't seem to actually record the entire page. Any resolution larger than the physical resolution of your windowing system will be ignored.Fewell
I love the line that helps to avoid scrollbar! Thanks a lot!Ingaborg
Tried and not working for me view itIngaborg
This works well, but note that later versions of Selenium deprecate find_element_by_tag_name() in favour of find_element(by=By.TAG_NAME, value=tagname), which will require importing By from selenium.webdriver.commonCantonese
C
42

Screenshots are limited to the viewport but you can get around this by capturing the body element, as the webdriver will capture the entire element even if it is larger than the viewport. This will save you having to deal with scrolling and stitching images, however you might see problems with footer position (like in the screenshot below).

Tested on Windows 8 and Mac High Sierra with Chrome Driver.

from selenium import webdriver

url = 'https://stackoverflow.com/'
path = '/path/to/save/in/scrape.png'

driver = webdriver.Chrome()
driver.get(url)
el = driver.find_element_by_tag_name('body')
el.screenshot(path)
driver.quit()

Returns: (full size: https://i.stack.imgur.com/ppDiI.png)

SO_scrape

Cordilleras answered 18/12, 2018 at 1:58 Comment(8)
best answer for this topic since it's basically a built in function of selenium. No need to over-engineer the solution. Absolute madlad.Spheroidicity
The headless mode must be used; see: https://mcmap.net/q/75735/-take-screenshot-of-full-page-with-selenium-python-with-chromedriverBreakfront
I can only get the top view and the rest of the screenshot is just background by this method.Bedtime
This answer did not work for me and at times fetched the only screen being rendered (scrollable). This is a more appropriate answer: https://mcmap.net/q/75735/-take-screenshot-of-full-page-with-selenium-python-with-chromedriverBelief
Thanks, this works great, I had a few issues where the page was not fully rendered, by adding driver.implicitly_wait(10) it was resolvedUnlikely
no longer works ;(Gunpaper
It doesn't work.Churchill
``` from selenium.webdriver.common.by import By el = driver.find_element(By.TAG_NAME, "body") ``` -- this works, it just produces a small unimpressive screenshotSwahili
C
35

How it works: set browser height as longest as you can...

#coding=utf-8
import time
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

def test_fullpage_screenshot(self):
    # please note that we MUST use headless mode
    chrome_options = Options()
    chrome_options.add_argument('--headless')
    chrome_options.add_argument('--start-maximized')

    driver = webdriver.Chrome(chrome_options=chrome_options)

    driver.get("yoururlxxx")
    time.sleep(2)

    height = driver.execute_script('return document.documentElement.scrollHeight')
    width  = driver.execute_script('return document.documentElement.scrollWidth')
    driver.set_window_size(width, height)  # the trick
    
    time.sleep(2)
    driver.save_screenshot("screenshot1.png")
    driver.quit()

if __name__ == "__main__":
    test_fullpage_screenshot()
Clammy answered 19/10, 2019 at 2:40 Comment(4)
This by far is the easiest and best solution for me. however, the longest height element i tried various but none of them seems to work... all about 1100px height (for the webpage in this question). However, hardcoding to 8000px total_height works great! if there is any way for you to find the good xpath that can return the longest height automatically then will be even great!Tourist
@Tourist you can try getting it with driver.execute_script("return document.scrollingElement.scrollHeight;")Disk
As other pointed out below, this will only work for full page if you run with headlessLueluebke
This works well but does not execute because chrome_options has now been deprecated. The below code should now be used: options = webdriver.ChromeOptions() options.add_argument("--headless") options.add_argument('--start-maximized') driver = webdriver.Chrome(options=options)Charismatic
H
16
from selenium import webdriver

driver = webdriver.Firefox()
driver.get('https://developer.mozilla.org/')
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)

This works for me. It saves the entire page as screenshot. For more information you can read up the api docs: http://selenium-python.readthedocs.io/api.html

Houseboy answered 13/12, 2017 at 5:13 Comment(2)
This technique worked for me for one page, but not for another. I waited for the page to load fully too. I have a newer answer which builds upon this answer and works a little more reliably.Actinouranium
This approach fails for many pages, example: de.abbott/media-center/press-releases/05-10-2018.htmlRattlesnake
B
16

The key is to turn on the headless mode! No stitching required and no need for loading the page twice.

Full working code:

URL = 'http://www.w3schools.com/js/default.asp'

options = webdriver.ChromeOptions()
options.headless = True

driver = webdriver.Chrome(options=options)
driver.get(URL)

S = lambda X: driver.execute_script('return document.body.parentNode.scroll'+X)
driver.set_window_size(S('Width'),S('Height')) # May need manual adjustment
driver.find_element_by_tag_name('body').screenshot('web_screenshot.png')

driver.quit()

This is practically the same code as posted by @Acumenus with slight improvements.

Summary of my findings

I decided to post this anyway because I did not find an explanation about what is happening when the headless mode is turned off (the browser is displayed) for screenshot taking purposes. As I tested (with Chrome WebDriver), if the headless mode is turned on, the screenshot is saved as desired. However, if the headless mode is turned off, the saved screenshot has approximately the correct width and height, but the outcome varies case-by-case. Usually, the upper part of the page which is visible by the screen is saved, but the rest of the image is just plain white. There was also a case with trying to save this Stack Overflow thread by using the above link; even the upper part was not saved which interestingly now was transparent while the rest still white. The last case I noticed was only once with the given W3Schools link; there where no white parts but the upper part of the page repeated until the end, including the header.

I hope this will help for many of those who for some reason are not getting the expected result as I did not see anyone explicitly explaining about the requirement of headless mode with this simple approach. Only when I discovered the solution to this problem myself, I found a post by @vc2279 mentioning that the window of a headless browser can be set to any size (which seems to be true for the opposite case too). Although, the solution in my post improves upon that that it does not require repeated browser/driver opening or page reloading.

Further suggestions

If for some pages it does not work for you, I suggest trying to add time.sleep(seconds) before getting the size of the page. Another case would be if the page requires scrolling until the bottom to load further content, which can be solved by the scheight method from this post:

scheight = .1
while scheight < 9.9:
    driver.execute_script("window.scrollTo(0, document.body.scrollHeight/%s);" % scheight)
    scheight += .01

Also, note that for some pages the content may not be in any of the top-level HTML tags like <html> or <body>, for example, YouTube uses <ytd-app> tag. As a last note, I found one page that "returned" a screenshot still with the horizontal scrollbar, the size of the window needed manual adjustment, i.e., the image width needed to be increased by 18 pixels, like so: S('Width')+18.

Breakfront answered 3/8, 2019 at 13:46 Comment(10)
HI. I have attempted to use Klaidonis's method for fullpage screenshot with the Bootstrap template "Creative" - link If I enter a custom width (not the detected width of the body) - for example driver.set_window_size("1440",S('Height')), then the element with class masthead (the template header) takes the entire screenshot - without any other elements visible. On lower custom widths and/or if I use the body's width with driver.set_window_size(S('Width'),S('Height')) then the screenshot is correct. What could be the reason for this?Gamete
@Gamete try writing 1440 without the quotes as it should be a number and not text. You can also try the following approach - S('Width')+100 or whatever number you need there.Breakfront
Thanks @Breakfront that you replied. But actually what helped me to resolve the problem was to set the height of the masthead class to 0 vh by using javascript executor in Selenium.Gamete
I'm receiving exception: selenium.common.exceptions.WebDriverException: Message: unknown command: session/a76b70801d41bf2c49ffa76c4396eb3a/element/0.039130225415212572-1/screenshotLilianaliliane
@Lilianaliliane perhaps you have misspelled something in the code?Breakfront
@Breakfront - this happens to me as well with selenium==3.141.0 and python 3.7.2Lueluebke
does not work for confluence pages which are longLeisure
@user3754136, What are confluence pages?Breakfront
Jira web pages for online documents.Leisure
Did you try other HTML tags that are on these pages besides 'body'?Breakfront
T
10

After knowing the approach of @Moshisho.

My full standalone working script is... (added sleep 0.2 after each scroll and position)

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")
Tourist answered 19/1, 2017 at 15:17 Comment(2)
I am a bit late here but I tried to use this and it hides the topnav only before the first scroll. How can i repeat this in every scroll ?Yttrium
Will it work for iframe? I have long iframe where I want to take screenshot.Brasilin
J
8

Not sure if people are still having this issue. I've done a small hack that works pretty well and that plays nicely with dynamic zones. Hope it helps

# 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)
Juniper answered 19/4, 2018 at 9:43 Comment(1)
This needlessly loads the page twice, and fails to define the width at all. I now have a newer answer which corrects these issues.Actinouranium
K
7

Why not just getting the width and height of the page and then resize the driver? So will be something like this

total_width = driver.execute_script("return document.body.offsetWidth")
total_height = driver.execute_script("return document.body.scrollHeight")
driver.set_window_size(total_width, total_height)
driver.save_screenshot("SomeName.png")

This is going to make a screenshot of your entire page without the need to merge together different pieces.

Kelwunn answered 26/11, 2018 at 13:31 Comment(3)
Is it supposed to scroll down and take screenshots of a very long page?Marjorymarjy
As far as I know and tested, yes.Kelwunn
The headless mode must be used; see: https://mcmap.net/q/75735/-take-screenshot-of-full-page-with-selenium-python-with-chromedriverBreakfront
L
6

You can achieve this by changing the CSS of the header before the screenshot:

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

EDIT: Put this line after your window scroll:

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

So in your util.py it will be:

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

If the site is using the header tag, you can do it with find_element_by_tag_name("header")

Loralyn answered 18/1, 2017 at 17:13 Comment(3)
hi thanks.. just adding above to script doesn't solve the problem.. however I understand the meaning.. and did disable the topnav.. by using inspector.. and need to dig around to find the javascript (not the css) that modifies the css.. and changed that to absolute.. manually. and it worked. (but the script screenshot still doesn't work though). Is there any way to improve ur script that disables the javascript css modification.. and for any new website.. do i have to dig around again to find the #id of header.. and change it.Tourist
You can't know in advance how every website implemented their header. But you can take a guess. I'll add an example.Loralyn
your code worked but with some minor glitch.. that is it included the header on some pages. So, after adding sleep 0.2 seconds.. it worked perfectly. i have updated the code and also marked your answer. Hope doing the edit in your answer is correct for stackoverflow.Tourist
K
6

I changed code for Python 3.6, maybe it will be useful for someone:

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')
Kathlyn answered 5/5, 2017 at 6:29 Comment(2)
Any idea why at a very long page it stops scrolling down at a certain point and goes reverse again? I used otto.de/technik/audio/kopfhoerer as an example, and all goes well until we are around 5000 pixels and then the scrolling goes up again instead of down.Shelton
I get the same issue that it stops scrolling. Any solution for this?Yttrium
A
6

Source : https://pypi.org/project/Selenium-Screenshot/

from Screenshot import Screenshot_Clipping
from selenium import webdriver
import time

ob = Screenshot_Clipping.Screenshot()

driver = webdriver.Chrome()
url = "https://www.bbc.com/news/world-asia-china-51108726"
driver.get(url)
time.sleep(1)

img_url = ob.full_Screenshot(driver, save_path=r'.', image_name='Myimage.png')

driver.quit()
Acerbity answered 14/1, 2020 at 20:20 Comment(1)
To make this answer more useful to readers of this question, consider adding a little prose to explain what you're doing.Greta
R
6

For Chrome, it's also possible to use the Chrome DevTools Protocol:

import base64
...
        page_rect = browser.driver.execute_cdp_cmd("Page.getLayoutMetrics", {})
        screenshot = browser.driver.execute_cdp_cmd(
            "Page.captureScreenshot",
            {
                "format": "png",
                "captureBeyondViewport": True,
                "clip": {
                    "width": page_rect["contentSize"]["width"],
                    "height": page_rect["contentSize"]["height"],
                    "x": 0,
                    "y": 0,
                    "scale": 1
                }
            })

        with open(path, "wb") as file:
            file.write(base64.urlsafe_b64decode(screenshot["data"]))

Credits

This works both in headless and non-headless mode.

Recife answered 17/3, 2021 at 15:14 Comment(1)
Bingo! for non-headless Chrome that it the ONLY method that has worked for me.Immoral
C
5

Full page screenshots are not a part of the W3C spec. However, many web drivers implement their own endpoints to get a real full page screenshot. I found this method using geckodriver to be superior to the injected "screenshot, scroll, stitch" method, and far better than resizing the window in headless mode.

Example:

from selenium import webdriver
from selenium.webdriver.firefox.service import Service
from selenium.webdriver.firefox.options import Options

options = Options()
options.headless = True
service = Service('/your/path/to/geckodriver')
driver = webdriver.Firefox(options=options, service=service)

driver.get('https://www.nytimes.com/')
driver.get_full_page_screenshot_as_file('example.png')

driver.close()

geckodriver (Firefox)

If you're using geckodriver, you can hit these methods:

driver.get_full_page_screenshot_as_file
driver.save_full_page_screenshot
driver.get_full_page_screenshot_as_png
driver.get_full_page_screenshot_as_base64 

I've tested and confirmed these to be working on Selenium 4.07. I don't believe these functions are included in Selenium 3.

The best documentation I could find on these is in this merge

chromedriver (Chromium)

It appears that chromedriver has implemented their own full page screenshot functionality:

https://chromium-review.googlesource.com/c/chromium/src/+/2300980

and the Selenium team appears to be aiming for support in Selenium 4:

https://github.com/SeleniumHQ/selenium/issues/8168

Cavalry answered 25/1, 2021 at 17:1 Comment(1)
thank you, I found this answer to be the best of the bunch.Unlikely
C
3

My first answer on StackOverflow. I'm a newbie. The other answers quoted by the fellow expert coders are awesome & I'm not even in the competition. I'd just like to quote the steps taken from the following link: pypi.org

Refer full-page screenshot section.

open your command prompt and navigate to the directory where Python is installed

cd "enter the directory"

install the module using pip

pip install Selenium-Screenshot

The above module works for python 3. once the module is installed, try the following code by creating a separate file in python IDLE

from Screenshot import Screenshot_Clipping
from selenium import webdriver

ob = Screenshot_Clipping.Screenshot()
driver = webdriver.Chrome()
url = "https://github.com/sam4u3/Selenium_Screenshot/tree/master/test"
driver.get(url)

# the line below makes taking & saving screenshots very easy.

img_url=ob.full_Screenshot(driver, save_path=r'.', image_name='Myimage.png')
print(img_url)
driver.close()

driver.quit()
Czech answered 13/12, 2019 at 13:16 Comment(3)
can confirm, not taking screenshots of full page.Inelegant
@Inelegant I can see that happening. I try to figure out what can be done.Czech
@Inelegant Try updating it to the latest version 1.6.0Czech
J
2

Slightly modify @ihightower and @A.Minachev's code, and make it work in mac retina:

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')
Jerricajerrie answered 3/6, 2018 at 5:51 Comment(0)
B
2

For Python using Selenium 4 and Chrome Driver

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.by import By
import time
import shutil

           
def take_full_page_screenshot():

    #Install chrome driver
    chrome_driver_path = ChromeDriverManager().install()
    service = Service(chrome_driver_path)
    service.start() 

    #setup chrome options
    options = webdriver.ChromeOptions()
    options.add_argument('--headless')
    options.add_argument('--incognito')
    options.add_argument('--start-maximized')  
    options.add_argument('--disable-gpu')
    driver = webdriver.Chrome(chrome_driver_path, options=options)

    #open url and wait for the page to load
    driver.get('https://www.stackoverflow.com')
    time.sleep(2)
        
    #find the element with longest height on page
    element = driver.find_element(By.TAG_NAME, 'body')
    total_height = element.size["height"]+1000
    #set the window dimensions
    driver.set_window_size(1920, total_height)  

    #save screenshot
    driver.save_screenshot("screenshot.png")

    #quit driver
    driver.quit()

if __name__ == '__main__':
    take_full_page_screenshot()
Bugeye answered 18/3, 2022 at 13:29 Comment(0)
H
1
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)

There was an error in the code suggested earlier in line 2. Here is the corrected one. Being a noob here, not able to edit my own post as yet.

Sometimes the baove doesn't get best results. So can use another method to get height of all elements and sum them to set the capture height as below:

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, it works on FF.

Houseboy answered 14/12, 2017 at 1:32 Comment(0)
S
1

You can use Splinter
Splinter is an abstraction layer on top of existing browser automation tools such as Selenium
There is a new feature browser.screenshot(..., full=True) in new version 0.10.0.
full=True option will make full screen capture for you.

Storeroom answered 10/1, 2019 at 14:52 Comment(0)
T
1

easy by python, but slowly

import os

from selenium import webdriver
from PIL import Image


def full_screenshot(driver: webdriver):
    driver.execute_script(f"window.scrollTo({0}, {0})")
    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")
    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
            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]))
        file_name = "part_{0}.png".format(part)
        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])
        stitched_image.paste(screenshot, offset)
        del screenshot
        os.remove(file_name)
        part = part + 1
        previous = rectangle
    return stitched_image
Telegenic answered 22/8, 2019 at 11:24 Comment(0)
H
1

I have modified the answer given by @ihightower, instead of saving the screenshot in that function, return the total height and total width of the webpage and then set the window size to total height and total width.

from PIL import Image
from io import BytesIO

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

def open_url(url):
    options = Options()

    options.headless = True

    driver = webdriver.Chrome(chrome_options=options)

    driver.maximize_window()
    driver.get(url)
    save_screenshot(driver, 'screen.png')

def save_screenshot(driver, file_name):
    height, width = scroll_down(driver)
    driver.set_window_size(width, height)
    img_binary = driver.get_screenshot_as_png()
    img = Image.open(BytesIO(img_binary))
    img.save(file_name)
    # print(file_name)
    print(" screenshot saved ")


def scroll_down(driver):
    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")

    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

            rectangles.append((ii, i, top_width, top_height))

            ii = ii + viewport_width

        i = i + viewport_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.5)
        # time.sleep(0.2)

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

        previous = rectangle

    return (total_height, total_width)

open_url("https://www.medium.com")
Howenstein answered 23/9, 2019 at 19:41 Comment(0)
C
1

I'm currently using this approach:

 def take_screenshot(self, driver, screenshot_name = "debug.png"):
    elem = driver.find_element_by_tag_name('body')
    total_height = elem.size["height"] + 1000
    driver.set_window_size(1920, total_height)
    time.sleep(2)
    driver.save_screenshot(screenshot_name)
    return driver
Cribwork answered 11/8, 2021 at 7:4 Comment(0)
U
1

If you are trying to do this post ~2021, you need to edit the find element command from:

element = driver.find_element_by_tag('body')

to:

from selenium.webdriver.common.by import By

...

element = driver.find_element(By.TAG_NAME, "body")
Unavailing answered 18/9, 2022 at 21:14 Comment(0)
A
0

I have modified jeremie-s' answer so that it only get the url once.

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()
Anagram answered 11/7, 2018 at 4:56 Comment(1)
This fails to define default_width or what it was or should've been. I now have a newer answer which corrects this issue.Actinouranium
B
0

Got it!!! works like a charm

For NodeJS, but the concept is the same:

await driver.executeScript(`
      document.documentElement.style.display = "table";
      document.documentElement.style.width = "100%";
      document.body.style.display = "table-row";
`);

await driver.findElement(By.css('body')).takeScreenshot();
Biffin answered 10/5, 2019 at 20:28 Comment(0)
B
0

This works for me

    s = Service("/opt/homebrew/bin/chromedriver")
    chrome_options = Options()
    chrome_options.add_argument('--headless')
    chrome_options.add_argument('--start-maximized')
    driver = webdriver.Chrome(chrome_options=chrome_options, service=s)

    highest_ele = driver.find_element(By.XPATH, '//*[@id="react-app"]/div[3]/div[3]/span/span/span[2]')
    total_height = highest_ele.location['y']
    driver.set_window_size(height=total_height, width=1920)

    time.sleep(1)
    driver.save_screenshot('~/shot.png') # replace your path
Barrault answered 12/1, 2023 at 5:18 Comment(0)
T
0

On latest release of the Selenium version 4.16 you need to configure the headless mode differently. as:

options.add_argument("--headless=new")

I have left the full code for taking full-size screenshots on my GitHub page here:

Full size web page screenshot automation using Selenium

Tip answered 18/1 at 5:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.