web-dev-qa-db-fra.com

Capture d'écran de la page complète avec Selenium Python avec chromedriver

Après avoir essayé diverses approches ... je suis tombé sur cette page pour prendre une capture d’écran de la page complète avec chromedriver, Selenium et python.

Le code original est ici . (et je copie le code dans cet article ci-dessous)

Il utilise PIL et cela fonctionne très bien! Cependant, il y a un problème ... qui consiste à capturer les en-têtes fixes et les répétitions pour la page entière, ainsi qu'à omettre certaines parties de la page lors du changement de page. exemple d'URL pour prendre une capture d'écran:

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

Comment éviter les en-têtes répétés avec ce code ... Ou existe-t-il une meilleure option qui utilise python seulement ... (Je ne sais pas Java et ne souhaite pas utiliser Java).

Veuillez consulter la capture d'écran du résultat actuel et du code exemple ci-dessous.

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
24
ihightower

Vous pouvez y parvenir en modifiant le CSS de l'en-tête avant la capture d'écran:

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

EDIT : Mettez cette ligne après le défilement de votre fenêtre:

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

Donc dans votre til.py ce sera:

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

Si le site utilise la balise header, vous pouvez le faire avec find_element_by_tag_name("header")

6
Moshisho
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)

Cela fonctionne pour moi. Il enregistre la page entière en tant que capture d'écran. Pour plus d'informations, vous pouvez lire les documents api: http://Selenium-python.readthedocs.io/api.html

14
Javed Karim

Les captures d'écran sont limitées à la fenêtre d'affichage, mais vous pouvez contourner ce problème en capturant l'élément body, car le WebDriver capturera l'élément entier, même s'il est plus grand que la fenêtre d'affichage. Cela vous évitera de devoir faire défiler et assembler des images, mais vous pourriez rencontrer des problèmes de position du pied de page (comme dans la capture d'écran ci-dessous).

Testé sous Windows 8 et Mac High Sierra avec le pilote Chrome.

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()

Retours: (taille réelle: https://i.stack.imgur.com/ppDiI.png )

SO_scrape

9
alexalex

Cette réponse améliore les réponses précédentes de am05mhz et Javed Karim .

Il suppose que le mode sans tête et qu'une option de taille de fenêtre n'a pas été définie initialement. Avant d'appeler cette fonction, assurez-vous que la page a été chargée complètement ou suffisamment.

Il tente de définir la largeur et la hauteur à la fois nécessaire. La capture d'écran de la page entière peut parfois inclure une barre de défilement verticale inutile. Une façon d'éviter généralement la barre de défilement consiste à prendre une capture d'écran de l'élément body. Après avoir enregistré une capture d'écran, il rétablit la taille d'origine, sans quoi la taille de la capture suivante risque de ne pas être définie correctement.

En fin de compte, cette technique peut ne pas fonctionner parfaitement pour certains exemples.

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'])

Si vous utilisez Python plus ancien que 3.6, supprimez les annotations de type de la définition de fonction.

9
Acumenus

Je ne sais pas si les gens ont toujours ce problème. J'ai fait un petit hack qui fonctionne plutôt bien et qui fonctionne bien avec les zones dynamiques. J'espère que ça aide

# 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)
7
jeremie

Après avoir connu l'approche de @Moshisho.

Mon script de travail autonome complet est ... (ajout de sommeil 0.2 après chaque défilement et chaque 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")
7
ihightower

J'ai changé de code pour Python 3.6, ce sera peut-être utile pour quelqu'un:

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
A.Minachev

Pourquoi ne pas simplement obtenir la largeur et la hauteur de la page, puis redimensionner le pilote? Donc sera quelque chose comme ça

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")

Cela va faire une capture d'écran de votre page entière sans la nécessité de fusionner différentes pièces.

3
Vali

La clé est d'activer le mode headless! Aucune couture requise et aucun besoin de charger la page deux fois.

Code de travail complet:

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()

C'est pratiquement le même code que posté par @ Acumenus avec de légères améliorations.

Résumé de mes conclusions

J'ai quand même décidé de poster ceci car je n'ai pas trouvé d'explication sur ce qui se passe lorsque le mode headless est désactivé (le navigateur est affiché) à des fins de prise de capture d'écran. Comme je l'ai testé (avec Chrome WebDriver), si le mode headless est activé, la capture d'écran est enregistrée comme vous le souhaitez. Toutefois, si le mode headless est défini sur désactivée, la capture d'écran enregistrée a approximativement la largeur et la hauteur correctes, mais le résultat varie au cas par cas. En général, la partie supérieure de la page visible par l'écran est enregistrée, mais le reste de l'image est tout simplement normal. Il y avait aussi un cas d’essayer de sauver ce fil Stack Overflow en utilisant le lien ci-dessus, même la partie supérieure n’a pas été sauvegardée, ce qui est intéressant, elle est maintenant transparente alors que le reste est toujours blanc. Le dernier cas que j’ai remarqué n’était qu’une fois avec le W3Schools lien, il n’y avait que des parties blanches mais la partie supérieure de la page se répétait jusqu’à la fin, en-tête compris.

J'espère que cela aidera beaucoup de ceux qui pour une raison quelconque n'obtiennent pas le résultat escompté car je n'ai vu personne expliquer explicitement l'exigence de headless mode avec cette approche simple. Ce n’est que lorsque j’ai découvert la solution à ce problème moi-même que j’ai trouvé un post par @ vc2279 mentionnant que la fenêtre d’un sans tête navigateur peut être réglé sur n’importe quelle taille (ce qui semble être vrai dans le cas contraire également). Bien que la solution proposée dans mon article améliore le fait qu’elle ne nécessite pas l’ouverture répétée du navigateur/pilote ou le rechargement de la page.

Autres suggestions

Si pour certaines pages cela ne fonctionne pas pour vous, je vous suggère d'essayer d'ajouter time.sleep(seconds) avant d'obtenir la taille de la page. Un autre cas serait si la page nécessite un défilement jusqu'au bas pour charger du contenu supplémentaire, ce qui peut être résolu par la méthode scheight à partir de cette post :

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

Notez également que pour certaines pages, le contenu peut ne pas figurer dans les balises HTML de niveau supérieur telles que <html> Ou <body>, Par exemple, YouTube utilise <ytd-app> Tag. En dernier lieu, j'ai trouvé une page qui "renvoyait" une capture d'écran toujours avec la barre de défilement horizontale, la taille de la fenêtre nécessitait un réglage manuel, c'est-à-dire que la largeur de l'image devait être augmentée de 18 pixels, comme suit: S('Width')+18.

2
Klaidonis

Vous pouvez utiliser Splinter
Splinter est une couche d’abstraction qui s’ajoute aux outils d’automatisation de navigateur existants, tels que Selenium.
Il y a une nouvelle fonctionnalité browser.screenshot(..., full=True) dans la nouvelle version 0.10.0.
L'option full=True Effectuera une capture plein écran pour vous.

0
Sergei

facile en python, mais lentement

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
0
Alex
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)

Il y avait une erreur dans le code suggéré plus tôt dans la ligne 2. Voici la corrigée. Être un noob ici, ne pas pouvoir éditer mon propre post pour l'instant.

Parfois, le baove n'obtient pas de meilleurs résultats. Vous pouvez donc utiliser une autre méthode pour obtenir la hauteur de tous les éléments et les additionner pour définir la hauteur de capture comme suit:

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, cela fonctionne sur FF.

0
Javed Karim

J'ai modifié réponse de jeremie-s pour qu'il ne reçoive qu'une seule fois l'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()
0
am05mhz

Je l'ai!!! fonctionne comme un charme

Pour NodeJS, mais le concept est le même:

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();
0
Moshe Quantz

Modifiez légèrement le code de @ihightower et @ A.Minachev, et faites-le fonctionner dans 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')
0
Charlie Chen