我想在服务器端将一些定制的 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 指出的,如果不接管浏览器中的动画,您想要的东西(任何给定动画的足够的屏幕截图值得制作视频)是不可能的。如果您无法接管动画,这是您可以获得的最好的:
- 加载页面
- 在动画持续时,创建尽可能多的屏幕截图
- 保存数据
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/