javascript - 使用 Python 使用 Selenium 将 CSS/Javascript 动画渲染为一系列图像文件

标签 javascript python selenium selenium-webdriver css-animations

我想在服务器端将一些定制的 CSS3/Javascript 动画序列渲染为一组 PNG 文件,然后将它们加入到单个视频文件中。

我看到了here PhantomJS 可以实现这一点。由于我对 Selenium 没有很大的背景,所以我不知道如何使其与 Selenium 相适应。我唯一知道的是如何make a single screenshot含 Selenium :

driver = webdriver.Chrome()
driver.get('mywebpage.com')
driver.save_screenshot('out.png')
driver.quit()

但它只执行单个屏幕截图。

请教如何通过 Selenium/Python 截取 CSS/Javascript 动画从头到尾的一组屏幕截图。

PS:我在 Vagrant VM 上使用 Python 3.5 和 chrome 作为 selenium webdriver

提前非常感谢

最佳答案

编辑4:

正如 @Arakis 指出的,如果不接管浏览器中的动画,您想要的东西(任何给定动画的足够的屏幕截图值得制作视频)是不可能的。如果您无法接管动画,这是您可以获得的最好的:

  1. 加载页面
  2. 在动画持续时,创建尽可能多的屏幕截图
  3. 保存数据
import os
import time

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

TARGET_FOLDER = r'C:\test\animated_you_not_wanted\{}.png'
WINDOW_SIZE = 1920, 1080

options = Options()
options.add_argument("--headless")
options.add_argument("--no-sandbox")
options.add_argument("start-maximized")
options.add_argument("disable-infobars")
options.add_argument("--disable-extensions")

shots = []
if not os.path.isdir(os.path.dirname(TARGET_FOLDER)):
    os.makedirs(os.path.dirname(TARGET_FOLDER))

driver = webdriver.Chrome(executable_path=r'c:\_\chromedriver.exe', options=options,
                          service_args=['--ignore-ssl-errors=true', '--ssl-protocol=any'])
# driver = webdriver.Chrome(executable_path=r'c:\_\chromedriver.exe')
driver.set_window_size(*WINDOW_SIZE)
driver.get('https://daneden.github.io/animate.css/')


# we know that the animation duration on this page is 1 sec so we will try to record as many frames as possible in 1 sec
start_time = time.time()
while time.time() <= start_time + 1:
    shots.append(driver.get_screenshot_as_png())

# dumping all captured frames
for i in range(len(shots)):
    with open(TARGET_FOLDER.format(i), "wb") as f:
        f.write(shots[i])

如果您运行上面的代码,您将获得大约 3 个屏幕截图,如果您有一台野兽机器,则可能获得 4 个屏幕截图。

为什么会发生这种情况?当selenium抓取屏幕截图时,浏览器中的动画不会停止并思考是否可以继续前进,它只是像平常一样独立运行。 Selenium 在此分辨率 (1920x1080) 下能够在一秒钟内抓取 3-4 个屏幕截图。如果将屏幕分辨率降低到 640x480,根据您的机器,您每秒将获得 5-7 个屏幕截图,但该帧速率仍然与您可能想要的相差非常非常远。 Selenium 通过 API 与浏览器交互,但不控制浏览器。截屏需要时间,当 selenium 将渲染的页面作为图像抓取时,动画会继续。

如果您想要具有高帧速率的流畅动画,您必须通过覆盖动画状态来控制动画。你必须:

  • animation-play-state设置为暂停
  • animation-duration设置为合理的长度
  • animation-delay设置为负值以选择给定的动画状态
  • for循环中调整animation-delay并抓取屏幕截图
  • 稍后保存屏幕截图数据

在给定页面 ( https://daneden.github.io/animate.css/ ) 的打开动画中,有 2 个使用相同动画进行动画处理的 Web 元素。因此,下面的代码所做的就是上面的列表,但有一点额外的内容:它“步进”两个元素的动画周期。

import os
import time

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

TARGET_FOLDER = r'C:\test\animated_you_wanted\{}.png'
WINDOW_SIZE = 1920, 1080
ANIM_DURATION = 2
FRAMES = 60
BASE_SCR = 'arguments[0].setAttribute("style", "display: block;animation-delay: {}s;animation-duration: {}s; animation-iteration-count: infinite;animation-play-state: paused;")'

options = Options()
options.add_argument("--headless")
options.add_argument("--no-sandbox")
options.add_argument("start-maximized")
options.add_argument("disable-infobars")
options.add_argument("--disable-extensions")

driver = webdriver.Chrome(executable_path=r'c:\_\chromedriver.exe', options=options,
                          service_args=['--ignore-ssl-errors=true', '--ssl-protocol=any'])
# driver = webdriver.Chrome(executable_path=r'c:\_\chromedriver.exe')
driver.set_window_size(*WINDOW_SIZE)
driver.get('https://daneden.github.io/animate.css/')

header = driver.find_element_by_xpath('//*[@class="site__header island"]')
content = driver.find_element_by_xpath('//*[@class="site__content island"]')

shots = []
for frame in range(FRAMES):
    for elem in (header, content):
        driver.execute_script(BASE_SCR.format((frame / FRAMES) * -ANIM_DURATION, ANIM_DURATION), elem)
    shots.append(driver.get_screenshot_as_png())

if not os.path.isdir(os.path.dirname(TARGET_FOLDER)):
    os.makedirs(os.path.dirname(TARGET_FOLDER))

# dumping all captured frames
for i in range(len(shots)):
    with open(TARGET_FOLDER.format(i), "wb") as f:
        f.write(shots[i])

这是使用 Selenium 可以获得的最好结果。

奖金:(或编辑5?)

我想知道是否真的不可能使用完全通用的解决方案(独立于网页)每秒挤出超过 5-6 帧。我在想这个:

  • 有一些 JS 库能够将 html 元素转换为图像
  • 可以使用selenium向页面注入(inject)JS
  • 由于没有 API 调用,纯 JS 截图逻辑可能优于纯 selenium 方法

所以我决定:

  • 将JS库注入(inject)到页面
  • 重新加载 document.body 以重置动画
  • 抓取屏幕截图并保存

这是代码,比 python 包含更多 JS :D :

import base64
import os
import time

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

TARGET_FOLDER = r'C:\test\animated_you_actually_wanted\{}.png'
WINDOW_SIZE = 800, 600
ANIM_DURATION = 1
FRAMES = 15
BASE_SCR = """
function load_script(){
    let ss = document.createElement("script");
    ss.src = "https://cdnjs.cloudflare.com/ajax/libs/html2canvas/0.4.1/html2canvas.js";
    ss.type = "text/javascript";
    document.getElementsByTagName("head")[0].appendChild(ss);
};

load_script();
shots = [];

window.take = function() {
  html2canvas(document.body, {
    onrendered: function (canvas) {
      shots.push(canvas);
    }
  })
};

window.record = function(times, sleep){
    for (let i=0; i<times; i++){
        setTimeout(window.take(), sleep*i)
        console.log("issued screenshot with sleep: " + sleep*i)
    }
};
""" + """ 
document.body.setAttribute("style", "width: {width}px; height: {height}px");
""".format(width=WINDOW_SIZE[1], height=WINDOW_SIZE[0])


RECORD_SCR = """
document.body.innerHTML = document.body.innerHTML
window.record({}, {})
"""

GRAB_SCR = """

function getShots(){
    let retval = []
    for (let i=0; i<shots.length; i++){
        retval.push(shots[i].toDataURL("image/png"))
    }
    return retval;
}

return getShots()
"""

options = Options()
options.add_argument("--headless")
options.add_argument("--no-sandbox")
options.add_argument("start-maximized")
options.add_argument("disable-infobars")
options.add_argument("--disable-extensions")

if not os.path.isdir(os.path.dirname(TARGET_FOLDER)):
    os.makedirs(os.path.dirname(TARGET_FOLDER))

driver = webdriver.Chrome(executable_path=r'c:\_\chromedriver.exe', options=options,
                          service_args=['--ignore-ssl-errors=true', '--ssl-protocol=any'])
# driver = webdriver.Chrome(executable_path=r'c:\_\chromedriver.exe')
driver.set_window_size(*WINDOW_SIZE)
driver.get('https://daneden.github.io/animate.css/')

driver.execute_script(BASE_SCR)
time.sleep(3)
driver.execute_script(RECORD_SCR.format(FRAMES, (ANIM_DURATION/FRAMES)*100))

shots = []
while len(shots) < FRAMES:
    shots = driver.execute_script(GRAB_SCR)


# dumping all captured frames
for i in range(len(shots)):
    with open(TARGET_FOLDER.format(i), "wb") as f:
        f.write(base64.urlsafe_b64decode(shots[i].split('base64,')[1]))

driver.quit()

html2canvas 实验的结果:

  • 上述页面上关于帧速率的最佳点约为 15 左右。更多帧会导致瓶颈,尤其是在前几帧中,文本 float 初始位置会增加 document.body 大小
  • 目标元素 (document.body) 尺寸严重影响性能
  • 瓶颈无处不在。我试图避免魔法数字,但这并不容易
  • 渲染的图像可能包含故障,例如容器外部的文本
  • 可能值得尝试不同的库,其他库可能表现更好
  • 这一结果距离使用非通用解决方案所能达到的 60fps 帧速率还有很长的路要走
  • 在 headless 模式下可以获得更多帧

关于javascript - 使用 Python 使用 Selenium 将 CSS/Javascript 动画渲染为一系列图像文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52753412/

相关文章:

python - Selenium 元素不可见异常

java - 实例化 RemoteWebDriver 时出错

javascript - Jquery 到 React js

javascript - 选择不转到 URL 栏的导航选项

python - 如何获取 Pandas 中数据框的移位索引值?

python - 如何有效地从 numpy 数组中提取由索引给出的元素列表?

python - 有没有办法使用 Selenium 和 Python 绑定(bind)执行鼠标悬停(悬停在元素上)?

javascript - 如何使 Pikaday 日期选择器始终可见

javascript - 如何创建元素的浅拷贝?

python - 值错误 : unconverted data remains: