Сделайте скриншот полной страницы с помощью Selenium Python с хромедрайвером
После опробования различных подходов... Я наткнулся на эту страницу, чтобы сделать полный снимок экрана с chromedriver, selenium и python.
Оригинальный код здесь. (и я копирую код в этой публикации ниже)
Он использует PIL и прекрасно работает! Однако есть одна проблема... она фиксирует фиксированные заголовки и повторяется для всей страницы, а также пропускает некоторые части страницы во время смены страницы. пример URL, чтобы сделать снимок экрана:
http://www.w3schools.com/js/default.asp
Как избежать повторяющихся заголовков с помощью этого кода... Или есть ли лучший вариант, который использует только Python... (яне знаю Java и не хочу использовать Java).
Смотрите скриншот текущего результата и пример кода ниже.
![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
Ответы
Ответ 1
Вы можете добиться этого, изменив CSS заголовка перед скриншотом:
topnav = driver.find_element_by_id("topnav")
driver.execute_script("arguments[0].setAttribute('style', 'position: absolute; top: 0px;')", topnav)
EDIT. Поместите эту строку после прокрутки окна:
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
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
Снимки экрана ограничены окном просмотра, но вы можете обойти это, захватив элемент body
, так как веб-драйвер захватит весь элемент, даже если он больше, чем окно просмотра. Это избавит вас от необходимости прокрутки и сшивания изображений, однако вы можете увидеть проблемы с положением нижнего колонтитула (как на скриншоте ниже).
Протестировано на Windows 8 и Mac High Sierra с драйвером 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()
Возвращает: (полный размер: https://i.stack.imgur.com/ppDiI.png)
![SO_scrape]()
Ответ 4
Этот ответ улучшен по сравнению с предыдущими ответами am05mhz и Джаведа Карима.
Предполагается, что безголовый режим и что опция размера окна изначально не была установлена. Перед вызовом этой функции убедитесь, что страница загружена полностью или достаточно.
Он пытается установить ширину и высоту как то, что необходимо. Скриншот всей страницы может иногда включать ненужную вертикальную полосу прокрутки. Один из способов вообще избежать полосы прокрутки - сделать скриншот элемента body. После сохранения снимка экрана он возвращает размер к первоначальному размеру, в противном случае размер следующего снимка экрана может быть задан неправильно.
В конечном счете, этот метод все еще может работать не совсем хорошо для некоторых примеров.
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'])
Если вы используете Python старше 3.6, удалите аннотации типов из определения функции.
Ответ 5
Узнав подход @Moshisho.
Моя полная автономная работа script - это... (добавлен сон 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")
Ответ 6
Не уверен, что люди все еще имеют эту проблему. Я сделал небольшой хак, который работает довольно хорошо и хорошо сочетается с динамическими зонами. Надеюсь, поможет
# 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
Я изменил код для 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')
Ответ 8
Почему бы просто не получить ширину и высоту страницы, а затем изменить размер драйвера? Так будет как то так
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")
Это сделает скриншот всей вашей страницы без необходимости объединять разные части.
Ответ 9
Ключ должен включить режим headless
!
Нет необходимости прошивать и не нужно загружать страницу дважды.
Полный рабочий код:
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()
Это практически тот же код, который опубликовал @Acumenus с небольшими улучшениями.
Краткое изложение моих выводов
Я решил опубликовать это в любом случае, потому что я не нашел объяснения тому, что происходит, когда режим headless
выключен (отображается браузер) для создания снимков экрана.
Как я тестировал (с Chrome WebDriver), если включен режим headless
, снимок экрана сохраняется по желанию. Однако, если режим headless
отключен, сохраненный снимок экрана имеет приблизительно правильную ширину и высоту, но результат варьируется в зависимости от конкретного случая. Обычно верхняя часть страницы, которая видна на экране, сохраняется, но остальная часть изображения просто белая. Был также случай с попыткой сохранить эту ветку Qaru, используя вышеуказанную ссылку; даже верхняя часть не была сохранена, что интересно теперь было прозрачным, в то время как остальное все еще белое. Последний случай, который я заметил, был только один раз с указанной ссылкой W3Schools; там, где нет белых частей, но верхняя часть страницы повторяется до конца, включая заголовок.
Я надеюсь, что это поможет многим из тех, кто по какой-то причине не получает ожидаемого результата, так как я не видел, чтобы кто-то явно объяснял требование режима headless
при таком простом подходе.
Только когда я сам нашел решение этой проблемы, я обнаружил сообщение by @vc2279, в котором говорится, что окно безголового браузера можно установить на любой размер (что, по-видимому, также верно для противоположного случая), Хотя решение в моем посте улучшает то, что оно не требует повторного открытия браузера/драйвера или перезагрузки страницы.
Дополнительные предложения
Если для некоторых страниц это не работает, я предлагаю попробовать добавить time.sleep(seconds)
, прежде чем получить размер страницы. Другой случай, если страница требует прокрутки до конца, чтобы загрузить дополнительный контент, что можно решить с помощью метода scheight
из этого сообщения:
scheight = .1
while scheight < 9.9:
driver.execute_script("window.scrollTo(0, document.body.scrollHeight/%s);" % scheight)
scheight += .01
Также обратите внимание, что для некоторых страниц контент может отсутствовать ни в одном из тегов HTML верхнего уровня, таких как <html>
или <body>
, например, YouTube использует тег <ytd-app>
.
В качестве последней заметки я нашел одну страницу, которая "возвращала" снимок экрана с горизонтальной полосой прокрутки, размер окна требовал ручной настройки, т.е. ширину изображения нужно было увеличить на 18 пикселей, например: S('Width')+18
.
Ответ 10
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. Вот исправленная. Будучи noob здесь, я еще не могу редактировать свой собственный пост.
Иногда 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)
Кстати, он работает на FF.
Ответ 11
Немного измените код @ihightower и @A.Minachev и сделайте так, чтобы он работал в 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')
Ответ 12
Я изменил ответ jeremie-s, чтобы он получал 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()
Ответ 13
Вы можете использовать Splinter
Splinter - это слой абстракции поверх существующих инструментов автоматизации браузера, таких как Selenium
В новой версии 0.10.0
появилась новая функция browser.screenshot(..., full=True)
.
full=True
опция сделает полный снимок экрана для вас.
Ответ 14
Понял!!! работает как шарм
Для NodeJS, но концепция та же:
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();
Ответ 15
легко питоном, но медленно
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
Ответ 16
Я изменил ответ, данный @ihightower, вместо того, чтобы сохранять скриншот в этой функции, возвращать общую высоту и общую ширину веб-страницы, а затем установить размер окна на общую высоту и общую ширину.
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")
Ответ 17
Как это работает: установите максимальную высоту браузера...
#coding=utf-8
import time
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
def test_fullpage_screenshot(self):
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)
#the element with longest height on page
ele=driver.find_element("xpath", '//div[@class="react-grid-layout layout"]')
total_height = ele.size["height"]+1000
driver.set_window_size(1920, total_height) #the trick
time.sleep(2)
driver.save_screenshot("screenshot1.png")
driver.quit()
if __name__ == "__main__":
test_fullpage_screenshot()