さまざまなアプローチを試してみました...私はこのページを偶然見つけて、chromedriver、Selenium、およびpythonでフルスクリーンのスクリーンショットを撮りました。
元のコードは here です。 (そして、以下のこの投稿のコードをコピーします)
PILを使用しており、非常に効果的です!ただし、1つの問題があります。それは、固定ヘッダーをキャプチャし、ページ全体で繰り返し、ページの変更中にページの一部が欠落することです。スクリーンショットを撮るサンプルURL:
http://www.w3schools.com/js/default.asp
このコードで繰り返されるヘッダーを避ける方法...またはpython only ...(私はわからない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
これは、スクリーンショットの前にヘッダーの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;');")
_
til.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")
でそれを行うことができます
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
スクリーンショットはビューポートに限定されますが、body
要素をキャプチャすることでこれを回避できます。これは、Webドライバがビューポートよりも大きい場合でも要素全体をキャプチャするためです。これにより、画像のスクロールとステッチに対処する必要がなくなりますが、フッターの位置に問題がある場合があります(下のスクリーンショットのように)。
ChromeドライバーでWindows 8およびMac High Sierraでテスト済み。
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 )
この回答は、 am05mhz および Javed Karim によって以前の回答を改善します。
ヘッドレスモードを想定しており、ウィンドウサイズオプションが最初に設定されていなかった。この関数を呼び出す前に、ページが完全または十分にロードされていることを確認してください。
必要な幅と高さの両方を設定しようとします。ページ全体のスクリーンショットには、不要な垂直スクロールバーが含まれることがあります。一般的にスクロールバーを回避する1つの方法は、代わりにbody要素のスクリーンショットを撮ることです。スクリーンショットを保存すると、元のサイズに戻り、次のスクリーンショットのサイズが正しく設定されない可能性があります。
最終的には、この手法は一部の例では完全にうまく機能しない可能性があります。
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'])
Python 3.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)
@Moshishoのアプローチを知った後。
私の完全なスタンドアロン作業スクリプトは...(各スクロールおよび位置の後にスリープ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")
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')
ページの幅と高さを取得してからドライバーのサイズを変更しないのはなぜですか?こんな感じになります
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
モードをオンにすることです!ステッチは不要で、ページを2回読み込む必要はありません。
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
モードがオフになっている場合、保存されたスクリーンショットの幅と高さはほぼ正しいですが、結果はケースごとに異なります。通常、画面で表示されるページの上部は保存されますが、画像の残りの部分は単なる白です。上記のリンクを使用してこのスタックオーバーフロースレッドを保存しようとしたケースもありました。上部も保存されず、興味深いことに今は透明で、残りはまだ白です。 W3Schools link;ヘッダーを含め、ページの上部が最後まで繰り返される白い部分はありません。
このシンプルなアプローチでheadless
モードの要件について明示的に説明している人がいないので、これが何らかの理由で期待される結果を得ていない多くの人々に役立つことを願っています。自分でこの問題の解決策を見つけたときだけ、 post by @ vc2279を見つけました。これは、ヘッドレスブラウザーのウィンドウを任意のサイズに設定できることを示しています(反対の場合にも当てはまります)。ただし、私の投稿のソリューションは、ブラウザー/ドライバーを繰り返し開いたり、ページをリロードしたりする必要がないという点で改善されています。
一部のページで機能しない場合は、ページのサイズを取得する前に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>
や<body>
などのトップレベルHTMLタグのいずれにも含まれない場合があります。たとえば、 YouTube は<ytd-app>
タグを使用します。最後の注意として、水平スクロールバーを使用してスクリーンショットを「返す」ページを1つ見つけました。ウィンドウのサイズを手動で調整する必要がありました。つまり、画像幅を18ピクセル増やす必要があります。S('Width')+18
。
Splinter を使用できます
Splinterは、Seleniumなどの既存のブラウザ自動化ツールの上にある抽象化レイヤーです
新機能browser.screenshot(..., full=True)
が新バージョンにあります0.10.0
。full=True
オプションは、全画面キャプチャを作成します。
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
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で動作します。
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()
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();
@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')