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.
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
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")
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
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 )
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.
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)
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")
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')
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.
La clé est d'activer le mode headless
! Aucune couture requise et aucun besoin de charger la page deux fois.
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.
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.
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
.
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.
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
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.
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()
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();
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')