使用 Selenium Python 和 chromedriver 截取整页截图

Posted

技术标签:

【中文标题】使用 Selenium Python 和 chromedriver 截取整页截图【英文标题】:Take screenshot of full page with Selenium Python with chromedriver 【发布时间】:2017-06-02 23:14:11 【问题描述】:

在尝试了各种方法之后......我偶然发现了这个页面,用 chromedriver、selenium 和 python 截取了整页截图。

原代码为here。 (我复制下面这篇文章中的代码)

它使用 PIL 并且效果很好!但是,有一个问题......它会捕获固定的标题并在整个页面中重复,并且在页面更改期间还会丢失页面的某些部分。截图示例网址:

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

如何避免使用此代码重复标头...或者有没有更好的选择只使用 python... (我不知道 java 也不想使用 java)。

请看下面当前结果的截图和示例代码。

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

【问题讨论】:

我正在截取需要多次滚动/拼接的页面。不幸的是,它不是公共 URL(只有登录后才能看到该页面)。你知道为什么它也一直附加标题吗? res.cloudinary.com/mpyr-com/image/upload/v1551372542/… 无需拼接:***.com/a/57338909/2943191 我现在已将答案更改为@lizesong1988(如下)并将最长高度设置为 8000 像素。最长元素的 ele xpath 总是返回 1100px 左右的值,这不是很好。所以我只是硬编码为 8000。这对我来说是最好和最简单的答案。 @ihightower 感谢您编写了很棒的代码。我面临同样的问题。是否有可能让相同的代码也适用于 div?就我而言,滚动条存在于 div 上。 现在最简单的答案是使用playwright,请参阅下面接受的答案以及最新的更新信息。 @DeepakKumar 【参考方案1】:

此答案比 am05mhz 和 Javed Karim 先前的答案有所改进。

它采用无头模式,并且最初没有设置窗口大小选项。在调用此函数之前,请确保页面已完全加载或充分加载。

它尝试将宽度和高度都设置为必要的值。整个页面的屏幕截图有时会包含一个不必要的垂直滚动条。通常避免滚动条的一种方法是截取 body 元素的屏幕截图。保存屏幕截图后,它会将大小恢复为原来的大小,否则下一个屏幕截图的大小可能设置不正确。

对于某些示例,这种技术最终可能仍然不能很好地工作。

from selenium import webdriver

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

如果使用早于 3.6 的 Python,请从函数定义中删除类型注释。

【讨论】:

Firefox 中的窗口大小比视口高约 74 像素,所以 required_height + 74 现在对我有用。 更多解释见这篇文章***.com/a/57338909/2943191。 我需要 iframe 的完整截图。我尝试了上面的代码,但似乎没有截取完整的屏幕截图,是否需要对 iframe 进行任何更改? 代码的最后一行(截图后)在循环工作时也很重要,因为如果错过这一行,图像会越来越长。 我想补充一下。这对我来说非常有效,除了有时高度太大并且 Selenium 崩溃。如果其他人遇到崩溃问题,请尝试添加高度上限。将set_window_size 更改为driver.set_window_size(required_width, min(6000, required_height))【参考方案2】:

屏幕截图仅限于视口,但您可以通过捕获body 元素来解决这个问题,因为即使它大于视口,webdriver 也会捕获整个元素。这将使您不必处理滚动和拼接图像,但是您可能会看到页脚位置问题(如下面的屏幕截图所示)。

使用 Chrome 驱动程序在 Windows 8 和 Mac High Sierra 上测试。

from selenium import webdriver

url = 'https://***.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)

【讨论】:

这个主题的最佳答案,因为它基本上是硒的内置功能。无需过度设计解决方案。绝对的疯子。 必须使用headless模式;见:***.com/a/57338909/2943191 这种方法只能得到顶视图,其余的截图只是背景。 这个答案对我不起作用,有时会获取唯一正在呈现的屏幕(可滚动)。这是一个更合适的答案:***.com/a/52572919/14270189 谢谢,效果很好,我遇到了一些页面未完全呈现的问题,通过添加driver.implicitly_wait(10) 已解决【参考方案3】:

工作原理:将浏览器高度设置为尽可能长...

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

【讨论】:

这对我来说是迄今为止最简单和最好的解决方案。但是,我尝试过的最长的高度元素是各种但它们似乎都不起作用……大约 1100px 高度(对于这个问题中的网页)。但是,硬编码到8000px total_height 效果很好!如果有什么方法可以让您找到可以自动返回最长高度的好的 xpath,那就太好了! @ihightower 你可以尝试使用 driver.execute_script("return document.scrollingElement.scrollHeight;") 正如下面其他指出的,如果您使用headless 运行,这仅适用于整页【参考方案4】:
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)

这对我有用。它将整个页面保存为屏幕截图。 有关更多信息,您可以阅读 api 文档: http://selenium-python.readthedocs.io/api.html

【讨论】:

这项技术适用于我的一页,但不适用于另一页。我也等待页面完全加载。我有一个newer answer,它建立在这个答案的基础上,并且工作得更可靠。 这种方法对很多页面都失败了,例如:de.abbott/media-center/press-releases/05-10-2018.html【参考方案5】:

关键是开启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 的 posted 的代码相同,但略有改进。

我的发现总结

我还是决定发布这个,因为我没有找到关于关闭headless 模式(显示浏览器)以进行截图时发生的情况的解释。 正如我所测试的(使用 Chrome WebDriver),如果 headless 模式打开,屏幕截图会根据需要保存。但是,如果关闭headless 模式,则保存的屏幕截图具有大致正确的宽度和高度,但结果会因情况而异。通常,屏幕可见的页面上部被保存,但图像的其余部分只是纯白色。还有一个案例是尝试使用上面的链接来保存这个 Stack Overflow 线程;甚至上半部分也没有保存,有趣的是现在是透明的,而其余部分仍然是白色的。我注意到的最后一个案例只有一次使用给定的W3Schools 链接;那里没有白色部分,但页面的上部重复到最后,包括标题。

我希望这对许多由于某种原因没有得到预期结果的人有所帮助,因为我没有看到有人用这种简单的方法明确解释headless 模式的要求。 只有当我自己发现这个问题的解决方案时,我才发现 @vc2279 的 post 提到无头浏览器的窗口可以设置为任何大小(这似乎是相反的情况也是如此)。虽然,我的帖子中的解决方案改进了它不需要重复打开浏览器/驱动程序或重新加载页面。

进一步的建议

如果某些页面不适合您,我建议在获取页面大小之前尝试添加time.sleep(seconds)。另一种情况是页面需要滚动到底部才能加载更多内容,这可以通过post中的scheight方法解决:

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

另外,请注意,对于某些页面,内容可能不在任何*** HTML 标记中,例如 &lt;html&gt;&lt;body&gt;,例如,YouTube 使用 &lt;ytd-app&gt; 标记。 最后一点,我发现一个页面“返回”了一个仍然带有水平滚动条的屏幕截图,窗口大小需要手动调整,即图像宽度需要增加18像素,例如:S('Width')+18

【讨论】:

嗨。我尝试使用 Klaidonis 的方法进行整页屏幕截图,并使用 Bootstrap 模板“Creative”-link 如果我输入自定义宽度(不是检测到的正文宽度)-例如driver.set_window_size("1440",S('Height')),则带有类标头的元素(模板标题)获取整个屏幕截图 - 没有任何其他元素可见。在较低的自定义宽度和/或如果我使用带有driver.set_window_size(S('Width'),S('Height')) 的主体宽度,那么屏幕截图是正确的。这可能是什么原因? @Nelly 尝试编写不带引号的 1440,因为它应该是数字而不是文本。您也可以尝试以下方法 - S('Width')+100 或您需要的任何号码。 感谢@Klaidonis 的回复。但实际上帮助我解决问题的是通过在 Selenium 中使用 javascript 执行器将标头类的高度设置为 0 vh。 我收到异常:selenium.common.exceptions.WebDriverException: Message: unknown command: session/a76b70801d41bf2c49ffa76c4396eb3a/element/0.039130225415212572-1/screenshot @RhymeGuy 也许你在代码中拼错了什么?【参考方案6】:

知道@Moshisho的做法后。

我的完整独立工作脚本是...(在每次滚动和位置后添加 sleep 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")

【讨论】:

我来晚了,但我尝试使用它,它仅在第一次滚动之前隐藏topnav。我怎样才能在每个卷轴中重复这个? 它适用于 iframe 吗?我有很长的 iframe,我想在其中截屏。【参考方案7】:

不确定人们是否仍然遇到此问题。 我做了一个小技巧,效果很好,并且可以很好地与动态区域配合使用。希望对你有帮助

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

【讨论】:

这不必要地加载页面两次,并且根本无法定义宽度。我现在有一个 newer answer 可以纠正这些问题。【参考方案8】:

你可以通过改变截图前的头部CSS来实现:

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

编辑:将此行放在窗口滚动之后:

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

所以在你的 util.py 中它将是:

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

如果网站使用header 标签,您可以使用find_element_by_tag_name("header")

【讨论】:

嗨谢谢.. 只是将上面添加到脚本并不能解决问题.. 但是我理解意思.. 并且确实禁用了 topnav.. 通过使用检查器.. 并且需要挖掘找到修改css的javascript(不是css)..并将其更改为absolute..手动。它奏效了。 (但脚本截图仍然不起作用)。有没有办法改进你的脚本来禁用javascript css修改..对于任何新网站..我是否必须再次挖掘以找到标题的#id..并更改它。 您无法提前知道每个网站是如何实现其标题的。但你可以猜测一下。我将添加一个示例。 您的代码可以正常工作,但有一些小故障.. 它在某些页面上包含标题。所以,在添加睡眠 0.2 秒后.. 它工作得很好。我已经更新了代码并标记了您的答案。希望在您的答案中进行编辑对于***是正确的。【参考方案9】:

我更改了 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')

【讨论】:

知道为什么在一个很长的页面上它会在某个点停止向下滚动并再次反转吗?我以otto.de/technik/audio/kopfhoerer 为例,一切顺利,直到我们达到 5000 像素左右,然后滚动再次向上而不是向下。 我遇到了同样的问题,它停止滚动。有什么解决办法吗?【参考方案10】:

为什么不直接获取页面的宽度和高度,然后调整驱动程序的大小?所以会是这样的

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

这将制作整个页面的屏幕截图,而无需将不同的部分合并在一起。

【讨论】:

它应该向下滚动并截取很长的页面吗? 据我所知和测试,是的。 必须使用headless模式;见:***.com/a/57338909/2943191【参考方案11】:

来源: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.close()

driver.quit()

【讨论】:

为了让这个答案对这个问题的读者更有用,考虑添加一点散文来解释你在做什么。【参考方案12】:

我在 *** 上的第一个答案。我是新手。 其他专家编码员引用的其他答案很棒,我什至没有参加比赛。我只想引用以下链接中的步骤:pypi.org

请参阅整页截图部分。

打开命令提示符并导航到安装 Python 的目录

cd "enter the directory"

使用 pip 安装模块

pip install Selenium-Screenshot

上述模块适用于 python 3。 安装模块后,通过在 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()

【讨论】:

可以确认,不截图整页。 @Ezio 我可以看到这种情况。我试图弄清楚可以做些什么。 @Ezio 尝试将其更新到最新版本 1.6.0【参考方案13】:

python很容易,但是很慢

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

【讨论】:

【参考方案14】:

整页截图不是W3C spec 的一部分。但是,许多 Web 驱动程序实现了自己的自己的端点以获得真正的全页屏幕截图。我发现这种使用 geckodriver 的方法远远优于注入的“截屏、滚动、缝合”方法,并且 优于在无头模式下调整窗口大小。

例子:

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)

如果你使用 geckodriver,你可以点击这些函数:

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 

我已经测试并确认这些可以在 Selenium 4.07 上运行。我不相信这些功能包含在 Selenium 3 中。

我能在这些方面找到的最好的文档在 merge

chromedriver(铬)

看来chromedriver已经实现了自己的整页截图功能:

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

Selenium 团队似乎希望在 Selenium 4 中获得支持:

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

【讨论】:

【参考方案15】:

对于 Chrome,也可以使用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

这适用于无头和非无头模式。

【讨论】:

【参考方案16】:

我目前正在使用这种方法:

 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

【讨论】:

【参考方案17】:
element=driver.find_element_by_tag_name('body')
element_png = element.screenshot_as_png
with open("test2.png", "wb") as file:
    file.write(element_png)

前面第 2 行中建议的代码存在错误。这是更正后的代码。作为一个菜鸟,还不能编辑我自己的帖子。

有时 baove 无法获得最佳效果。因此可以使用另一种方法获取所有元素的高度并将它们相加以设置捕获高度,如下所示:

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

顺便说一句,它适用于 FF。

【讨论】:

【参考方案18】:

稍微修改@ihightower 和@A.Minachev 的代码,使其在macretina 中运行:

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

【讨论】:

【参考方案19】:

我修改了jeremie-s' answer,使它只获取一次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()

【讨论】:

这无法定义default_width 或它曾经是或应该是什么。我现在有一个 newer answer 可以纠正这个问题。【参考方案20】:

您可以使用Splinter Splinter 是现有浏览器自动化工具(如 Selenium)之上的抽象层 在新版本0.10.0 中有一个新功能browser.screenshot(..., full=True)full=True 选项将为您进行全屏捕获。

【讨论】:

【参考方案21】:

知道了!!!像魅力一样工作

对于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();

【讨论】:

【参考方案22】:

我已经修改了@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")

【讨论】:

以上是关于使用 Selenium Python 和 chromedriver 截取整页截图的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 python 处理 selenium 中嵌套 iframe 中的 cookie 接受按钮?

python_爬虫_Selenium_Error

Python3+Selenium3自动化测试

爬虫之selenium模块

selenium鼠标操作

逃避检测硒自动化