通过Selenium使用无头ChromeDriver进行透明截图

10
据我所知,ChromeDriver本身并不设置背景颜色,而是由CSS设置。因此,如果背景是透明的,为什么我截图时没有透明的效果呢?
这是一个应该是透明的网站截图: Transparent background 同样的截图,但在背景中加了一个红色的div来显示透明度的位置: Red background 下面是我的代码:
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from datetime import datetime

options = webdriver.ChromeOptions()
options.add_argument('headless')
driver = webdriver.Chrome(chrome_options=options)

driver.set_window_size(2560, 1600)
driver.get('https://twitter.com/realDonaldTrump/status/516382177798680576')
# driver.execute_script("$('body').append(`<div style='background: red; width: 100%; height: 100%;'></div>`);")
driver.save_screenshot('screenshots/' + str(datetime.now()) + '.png')

driver.quit()

我该如何创建那个屏幕截图的透明版本?
*** 编辑 *** 我制作了一个代码片段来完成这个任务。接受的答案帮助我找到了解决方案,而这就是我想要的。然而,这个代码片段才是我问题的正确解决方案: https://gist.github.com/colecrtr/f58834ff09ab07e3c1164667b753e77a

那个你已经注释掉的代码行,你试过像这样改变吗:driver.execute_script("$('body').css('background-color', 'transparent');")?请注意,所有的div层都必须是background-color: transparent才能真正实现透明背景,如果只是更改主体背景颜色无法实现,你需要使用开发者工具进行一些测试和调整。另外请记住,页面必须有jQuery库,但我不确定twitter是否有该库。 - Christos Lytras
@ChristosLytras 我尝试将 background-color 设置为 transparent,但仍然出现白色背景。Twitter确实使用jQuery。我该如何调整开发者工具? - Cole
只需要在浏览 Twitter 时按 F12,切换到 元素 选项卡,然后检查内容中每个 DIV 子元素的样式以获取背景颜色属性。 - Christos Lytras
很遗憾,Gist的链接已经失效了。 - Brad Root
1
@BradRoot,我最近更改了我的用户名,导致链接失效。我已经更新为我的新用户名 :-) - Cole
4个回答

10

一种方法是通过将屏幕截图中的每个白色像素转换为透明像素,将 alpha 字节设置为 0:

from selenium import webdriver
from PIL import Image
from io import BytesIO # python 3
import numpy as np

def remove_color(img, rgba):
  data = np.array(img.convert('RGBA'))        # rgba array from image
  pixels = data.view(dtype=np.uint32)[...,0]  # pixels as rgba uint32
  data[...,3] = np.where(pixels == np.uint32(rgba), np.uint8(0), np.uint8(255))  # set alpha channel
  return Image.fromarray(data)

driver = webdriver.Chrome()
driver.get("http://www.bbc.co.uk/news")

# take screenshot with a transparent background
with Image.open(BytesIO(driver.get_screenshot_as_png())) as img :
  with remove_color(img, 0xffffffff) as img2:
    img2.save(r"C:\temp\screenshot.png")

但是,如果页面内容中有一些白色像素,则可能会出现一些意外的透明像素,并且抗锯齿可能会变得可见。

另一个解决方案是使用Chrome的DevTool API来从屏幕截图中排除背景:

from selenium import webdriver
import json

def send(cmd, params={}):
  resource = "/session/%s/chromium/send_command_and_get_result" % driver.session_id
  url = driver.command_executor._url + resource
  body = json.dumps({'cmd':cmd, 'params': params})
  response = driver.command_executor._request('POST', url, body)
  if response['status']: raise Exception(response.get('value'))
  return response.get('value')

options = webdriver.ChromeOptions()
options.add_argument("disable-gpu")
options.add_argument("disable-infobars")

driver = webdriver.Chrome(chrome_options=options)
driver.get("http://www.bbc.co.uk/news")

# take screenshot with a transparent background
send("Emulation.setDefaultBackgroundColorOverride", {'color': {'r': 0, 'g': 0, 'b': 0, 'a': 0}})
driver.get_screenshot_as_file(r"C:\temp\screenshot.png")
send("Emulation.setDefaultBackgroundColorOverride")  # restore

是的,第二个解决方案对我有效。我通过Tabun的评论找到了答案,但为了未来的发现,我会将其标记为正确答案。 - Cole
1
@Cole,我是导致此解决方案的评论的作者。 - Florent B.
我的错误,我现在看到了。非常感谢! - Cole

1
一个PNG可以有透明像素,但截图不行。每当你渲染一些东西时,你会混合层的透明度水平,但组合层总是会有一个背景。
截图是屏幕上已被渲染的内容,它永远不能是透明的。你如何在桌面上显示真正的透明图像?你无法做到这一点,因为桌面的背景或其他东西总是必须存在。
所以你所问的与Chrome、ChromeDriver没有任何关系。任何截图工具都不能拍摄透明的截图,除非你告诉它要遮盖什么。
即使像Photoshop这样的工具也使用特殊的方式(灰色带有小变化的颜色框)来显示透明背景,但如果你使用截图工具来捕捉该图像,结果将是一个实际像素而没有透明度的图像,就像下面这张图片一样: Transparent image

很好的观点,我理解了。但是使用PhantomJS驱动程序将会产生一个透明的图像,只要没有定义背景或者背景本身被定义为透明。因此我知道这是可能的,只是在ChromeDriver中如何实现,因为其他驱动程序无法正确地呈现页面。 - Cole
尝试使用Chrome无头模式并查看是否有所改变,因为所有实际呈现在屏幕上的都会给你一个背景颜色。PhantomJS作为内存渲染器不需要具有背景容器来进行渲染并且失去透明度。 - Tarun Lalwani
截图可以具有透明背景。例如,puppeteer 提供了此选项。使用 Selenium 可以通过从 PNG 中删除背景或通过使用 Chrome 的自定义命令调用 Emulation.setDefaultBackgroundColorOverride 来实现。 - Florent B.
https://dev59.com/81cO5IYBdhLWcg3wzEuf#45201692 - Florent B.
谢谢�这太棒了。我给你点��。 - Tarun Lalwani
显示剩余4条评论

0

对于那些试图在 Laravel Dusk 或使用 Facebook 的 Web Driver 在 PHP 中实现此操作的人,您必须调用 executeCustomCommand 函数:

$browser->visit($url)->script("document.getElementsByTagName('body')[0].style.backgroundColor = 'transparent'");

$browser->driver->executeCustomCommand('/session/:sessionId/chromium/send_command', 'POST', [
    'cmd' => 'Emulation.setDefaultBackgroundColorOverride',
    'params' => ['color' => ['r' => 0, 'g' => 0, 'b' => 0, 'a' => 0]],
]);

$browser->screenshot('screenshot.png');

0

我知道这个问题已经有答案了,但是使用被接受的答案在我的情况下并没有起作用,我不得不对它进行了一些小调整。扩展被接受的答案的代码后,结果发现,在我的情况下,必须先为body添加背景,然后才能替换它:

from selenium import webdriver
import json

def send(cmd, params={}):
  resource = "/session/%s/chromium/send_command_and_get_result" % driver.session_id
  url = driver.command_executor._url + resource
  body = json.dumps({'cmd':cmd, 'params': params})
  response = driver.command_executor._request('POST', url, body)
  return response.get('value')

options = webdriver.ChromeOptions()

options.add_argument('headless')
options.add_argument('disable-gpu')
options.add_argument('disable-infobars')

driver = webdriver.Chrome(chrome_options=options)
driver.get("http://www.bbc.co.uk/news")

# take screenshot with a transparent background
driver.execute_script("document.getElementsByTagName('body')[0].style.backgroundColor = 'transparent';") # I had to add this line
send("Emulation.setDefaultBackgroundColorOverride", {'color': {'r': 0, 'g': 0, 'b': 0, 'a': 0}})
driver.get_screenshot_as_file(r"screenshot.png")
send("Emulation.setDefaultBackgroundColorOverride")  # restore

希望这能帮助到那些遇到同样问题的人。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接