使用 Selenium 在 Chrome 中截取整页屏幕截图
Posted
技术标签:
【中文标题】使用 Selenium 在 Chrome 中截取整页屏幕截图【英文标题】:Take full page screenshot in Chrome with Selenium 【发布时间】:2017-12-25 05:28:15 【问题描述】:我知道这是不可能的before,但现在有了以下更新:
https://developers.google.com/web/updates/2017/04/devtools-release-notes#screenshots
这似乎可以使用 Chrome 开发工具。
现在可以在 Java 中使用 Selenium 了吗?
【问题讨论】:
AFAIK 这在selenium
+PhantomJS
是可能的
是的,但我需要 Chrome 驱动程序
如果您使用的是 Java,看起来有人创建了一个不错的库来为您做这种事情。也许试试这个github.com/yandex-qatools/ashot
@stewartm 该库使用 javascript 滚动浏览页面并截取许多屏幕截图。我的代码中有类似的方法,但在有浮动窗口时效果不佳..
【参考方案1】:
是的,自 Chrome v59 起,可以使用 Selenium 截取整页屏幕截图。 Chrome 驱动程序有两个新端点可以直接调用 DevTools API:
/session/:sessionId/chromium/send_command_and_get_result
/session/:sessionId/chromium/send_command
Selenium API 没有实现这些命令,因此您必须直接通过底层执行器发送它们。这并不简单,但至少可以产生与 DevTools 完全相同的结果。
以下是 python 在本地或远程实例上工作的示例:
from selenium import webdriver
import json, base64
capabilities =
'browserName': 'chrome',
'chromeOptions':
'useAutomationExtension': False,
'args': ['--disable-infobars']
driver = webdriver.Chrome(desired_capabilities=capabilities)
driver.get("https://***.com/questions")
png = chrome_takeFullScreenshot(driver)
with open(r"C:\downloads\screenshot.png", 'wb') as f:
f.write(png)
,以及截取整页截图的代码:
def chrome_takeFullScreenshot(driver) :
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')
def evaluate(script):
response = send('Runtime.evaluate', 'returnByValue': True, 'expression': script)
return response['result']['value']
metrics = evaluate( \
"(" + \
"width: Math.max(window.innerWidth, document.body.scrollWidth, document.documentElement.scrollWidth)|0," + \
"height: Math.max(innerHeight, document.body.scrollHeight, document.documentElement.scrollHeight)|0," + \
"deviceScaleFactor: window.devicePixelRatio || 1," + \
"mobile: typeof window.orientation !== 'undefined'" + \
")")
send('Emulation.setDeviceMetricsOverride', metrics)
screenshot = send('Page.captureScreenshot', 'format': 'png', 'fromSurface': True)
send('Emulation.clearDeviceMetricsOverride', )
return base64.b64decode(screenshot['data'])
使用 Java:
public static void main(String[] args) throws Exception
ChromeOptions options = new ChromeOptions();
options.setExperimentalOption("useAutomationExtension", false);
options.addArguments("disable-infobars");
ChromeDriverEx driver = new ChromeDriverEx(options);
driver.get("https://***.com/questions");
File file = driver.getFullScreenshotAs(OutputType.FILE);
import java.lang.reflect.Method;
import java.util.Map;
import com.google.common.collect.ImmutableMap;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeDriverService;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.remote.CommandInfo;
import org.openqa.selenium.remote.HttpCommandExecutor;
import org.openqa.selenium.remote.http.HttpMethod;
public class ChromeDriverEx extends ChromeDriver
public ChromeDriverEx() throws Exception
this(new ChromeOptions());
public ChromeDriverEx(ChromeOptions options) throws Exception
this(ChromeDriverService.createDefaultService(), options);
public ChromeDriverEx(ChromeDriverService service, ChromeOptions options) throws Exception
super(service, options);
CommandInfo cmd = new CommandInfo("/session/:sessionId/chromium/send_command_and_get_result", HttpMethod.POST);
Method defineCommand = HttpCommandExecutor.class.getDeclaredMethod("defineCommand", String.class, CommandInfo.class);
defineCommand.setAccessible(true);
defineCommand.invoke(super.getCommandExecutor(), "sendCommand", cmd);
public <X> X getFullScreenshotAs(OutputType<X> outputType) throws Exception
Object metrics = sendEvaluate(
"(" +
"width: Math.max(window.innerWidth,document.body.scrollWidth,document.documentElement.scrollWidth)|0," +
"height: Math.max(window.innerHeight,document.body.scrollHeight,document.documentElement.scrollHeight)|0," +
"deviceScaleFactor: window.devicePixelRatio || 1," +
"mobile: typeof window.orientation !== 'undefined'" +
")");
sendCommand("Emulation.setDeviceMetricsOverride", metrics);
Object result = sendCommand("Page.captureScreenshot", ImmutableMap.of("format", "png", "fromSurface", true));
sendCommand("Emulation.clearDeviceMetricsOverride", ImmutableMap.of());
String base64EncodedPng = (String)((Map<String, ?>)result).get("data");
return outputType.convertFromBase64Png(base64EncodedPng);
protected Object sendCommand(String cmd, Object params)
return execute("sendCommand", ImmutableMap.of("cmd", cmd, "params", params)).getValue();
protected Object sendEvaluate(String script)
Object response = sendCommand("Runtime.evaluate", ImmutableMap.of("returnByValue", true, "expression", script));
Object result = ((Map<String, ?>)response).get("result");
return ((Map<String, ?>)result).get("value");
【讨论】:
关于如何在 java 中做到这一点的任何提示? 尝试扩展HttpCommandExecutor
并调用defineCommand
以使用新端点。然后使用扩展的HttpCommandExecutor
实例化一个新的 RemoteWebDriver。
"Emulation.resetViewport" 似乎在 chrome 61 上不起作用(在 chrome 60 上没问题)。有其他选择吗?
API 中不再存在“Emulation.forceViewport”和“Emulation.resetViewport”命令,Emulation.setVisibleSize
现在已弃用。被“Emulation.setDeviceMetricsOverride”和“Emulation.clearDeviceMetricsOverride”取代。似乎不再可能冻结视口的大小。所以如果根据视口的大小来设置内容的大小,那么屏幕截图可能会在底部被截断。我还添加了 Java 代码。
在截图之前只使用了 Emulation.setVisibleSize 并在完成之后将其重置为以前的值。至少在 chrome 61 中。感谢您的帮助。【参考方案2】:
要在 Java 中使用 Selenium Webdriver 执行此操作需要一些工作。正如 Florent B 所暗示的那样,我们需要更改默认 ChromeDriver 使用的一些类以使其工作。首先我们需要创建一个新的 DriverCommandExecutor 来添加新的 Chrome 命令:
import com.google.common.collect.ImmutableMap;
import org.openqa.selenium.remote.CommandInfo;
import org.openqa.selenium.remote.http.HttpMethod;
import org.openqa.selenium.remote.service.DriverCommandExecutor;
import org.openqa.selenium.remote.service.DriverService;
public class MyChromeDriverCommandExecutor extends DriverCommandExecutor
private static final ImmutableMap<String, CommandInfo> CHROME_COMMAND_NAME_TO_URL;
public MyChromeDriverCommandExecutor(DriverService service)
super(service, CHROME_COMMAND_NAME_TO_URL);
static
CHROME_COMMAND_NAME_TO_URL = ImmutableMap.of("launchApp", new CommandInfo("/session/:sessionId/chromium/launch_app", HttpMethod.POST)
, "sendCommandWithResult", new CommandInfo("/session/:sessionId/chromium/send_command_and_get_result", HttpMethod.POST)
);
之后我们需要创建一个新的 ChromeDriver 类来使用这个东西。我们需要创建类,因为原来没有构造函数可以让我们替换命令执行器......所以新类变成:
import com.google.common.collect.ImmutableMap;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.chrome.ChromeDriverService;
import org.openqa.selenium.html5.LocalStorage;
import org.openqa.selenium.html5.Location;
import org.openqa.selenium.html5.LocationContext;
import org.openqa.selenium.html5.SessionStorage;
import org.openqa.selenium.html5.WebStorage;
import org.openqa.selenium.interactions.HasTouchScreen;
import org.openqa.selenium.interactions.TouchScreen;
import org.openqa.selenium.mobile.NetworkConnection;
import org.openqa.selenium.remote.FileDetector;
import org.openqa.selenium.remote.RemoteTouchScreen;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.remote.html5.RemoteLocationContext;
import org.openqa.selenium.remote.html5.RemoteWebStorage;
import org.openqa.selenium.remote.mobile.RemoteNetworkConnection;
public class MyChromeDriver extends RemoteWebDriver implements LocationContext, WebStorage, HasTouchScreen, NetworkConnection
private RemoteLocationContext locationContext;
private RemoteWebStorage webStorage;
private TouchScreen touchScreen;
private RemoteNetworkConnection networkConnection;
//public MyChromeDriver()
// this(ChromeDriverService.createDefaultService(), new ChromeOptions());
//
//
//public MyChromeDriver(ChromeDriverService service)
// this(service, new ChromeOptions());
//
public MyChromeDriver(Capabilities capabilities)
this(ChromeDriverService.createDefaultService(), capabilities);
//public MyChromeDriver(ChromeOptions options)
// this(ChromeDriverService.createDefaultService(), options);
//
public MyChromeDriver(ChromeDriverService service, Capabilities capabilities)
super(new MyChromeDriverCommandExecutor(service), capabilities);
this.locationContext = new RemoteLocationContext(this.getExecuteMethod());
this.webStorage = new RemoteWebStorage(this.getExecuteMethod());
this.touchScreen = new RemoteTouchScreen(this.getExecuteMethod());
this.networkConnection = new RemoteNetworkConnection(this.getExecuteMethod());
@Override
public void setFileDetector(FileDetector detector)
throw new WebDriverException("Setting the file detector only works on remote webdriver instances obtained via RemoteWebDriver");
@Override
public LocalStorage getLocalStorage()
return this.webStorage.getLocalStorage();
@Override
public SessionStorage getSessionStorage()
return this.webStorage.getSessionStorage();
@Override
public Location location()
return this.locationContext.location();
@Override
public void setLocation(Location location)
this.locationContext.setLocation(location);
@Override
public TouchScreen getTouch()
return this.touchScreen;
@Override
public ConnectionType getNetworkConnection()
return this.networkConnection.getNetworkConnection();
@Override
public ConnectionType setNetworkConnection(ConnectionType type)
return this.networkConnection.setNetworkConnection(type);
public void launchApp(String id)
this.execute("launchApp", ImmutableMap.of("id", id));
这主要是原始类的副本,但禁用了一些构造函数(因为一些所需的代码是包私有的)。如果您需要这些构造函数,则必须将类放在包 org.openqa.selenium.chrome 中。
通过这些更改,您可以调用所需的代码,如 Florent B. 所示,但现在在 Java 中使用 Selenium API:
import com.google.common.collect.ImmutableMap;
import org.openqa.selenium.remote.Command;
import org.openqa.selenium.remote.Response;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
public class ChromeExtender
@Nonnull
private MyChromeDriver m_wd;
public ChromeExtender(@Nonnull MyChromeDriver wd)
m_wd = wd;
public void takeScreenshot(@Nonnull File output) throws Exception
Object visibleSize = evaluate("(x:0,y:0,width:window.innerWidth,height:window.innerHeight)");
Long visibleW = jsonValue(visibleSize, "result.value.width", Long.class);
Long visibleH = jsonValue(visibleSize, "result.value.height", Long.class);
Object contentSize = send("Page.getLayoutMetrics", new HashMap<>());
Long cw = jsonValue(contentSize, "contentSize.width", Long.class);
Long ch = jsonValue(contentSize, "contentSize.height", Long.class);
/*
* In chrome 61, delivered one day after I wrote this comment, the method forceViewport was removed.
* I commented it out here with the if(false), and hopefully wrote a working alternative in the else 8-/
*/
if(false)
send("Emulation.setVisibleSize", ImmutableMap.of("width", cw, "height", ch));
send("Emulation.forceViewport", ImmutableMap.of("x", Long.valueOf(0), "y", Long.valueOf(0), "scale", Long.valueOf(1)));
else
send("Emulation.setDeviceMetricsOverride",
ImmutableMap.of("width", cw, "height", ch, "deviceScaleFactor", Long.valueOf(1), "mobile", Boolean.FALSE, "fitWindow", Boolean.FALSE)
);
send("Emulation.setVisibleSize", ImmutableMap.of("width", cw, "height", ch));
Object value = send("Page.captureScreenshot", ImmutableMap.of("format", "png", "fromSurface", Boolean.TRUE));
// Since chrome 61 this call has disappeared too; it does not seem to be necessary anymore with the new code.
// send("Emulation.resetViewport", ImmutableMap.of());
send("Emulation.setVisibleSize", ImmutableMap.of("x", Long.valueOf(0), "y", Long.valueOf(0), "width", visibleW, "height", visibleH));
String image = jsonValue(value, "data", String.class);
byte[] bytes = Base64.getDecoder().decode(image);
try(FileOutputStream fos = new FileOutputStream(output))
fos.write(bytes);
@Nonnull
private Object evaluate(@Nonnull String script) throws IOException
Map<String, Object> param = new HashMap<>();
param.put("returnByValue", Boolean.TRUE);
param.put("expression", script);
return send("Runtime.evaluate", param);
@Nonnull
private Object send(@Nonnull String cmd, @Nonnull Map<String, Object> params) throws IOException
Map<String, Object> exe = ImmutableMap.of("cmd", cmd, "params", params);
Command xc = new Command(m_wd.getSessionId(), "sendCommandWithResult", exe);
Response response = m_wd.getCommandExecutor().execute(xc);
Object value = response.getValue();
if(response.getStatus() == null || response.getStatus().intValue() != 0)
//System.out.println("resp: " + response);
throw new MyChromeDriverException("Command '" + cmd + "' failed: " + value);
if(null == value)
throw new MyChromeDriverException("Null response value to command '" + cmd + "'");
//System.out.println("resp: " + value);
return value;
@Nullable
static private <T> T jsonValue(@Nonnull Object map, @Nonnull String path, @Nonnull Class<T> type)
String[] segs = path.split("\\.");
Object current = map;
for(String name: segs)
Map<String, Object> cm = (Map<String, Object>) current;
Object o = cm.get(name);
if(null == o)
return null;
current = o;
return (T) current;
这让您可以使用指定的命令,并创建一个包含 png 格式图像的文件。当然,您也可以通过对字节使用 ImageIO.read() 直接创建 BufferedImage。
【讨论】:
干得好!!!您有机会为 ChromeExtender 类添加导入吗?我猜了几个。 嗨@SiKing,我添加了导入。但请注意,我今天获得的 Chrome 61 删除了我原始示例中使用的 forceViewport 调用。我在 takeScreenshot() 的代码中添加了一个似乎对我有用的替代方法。 Emulation.resetViewport" 似乎在 chrome 61 上不起作用(在 chrome 60 上没问题)。有替代方案吗? 似乎不再需要该调用。我刚刚删除了它,这似乎可以吗? 它在截图之前只使用了 Emulation.setVisibleSize 并在完成之后将其重置为以前的值。至少在 chrome 61 中。感谢您的帮助。【参考方案3】:在 Selenium 4 中,FirefoxDriver
提供了一个处理垂直和水平滚动以及固定元素(例如导航栏)的 getFullPageScreenshotAs
方法。 ChromeDriver
可能会在以后的版本中实现这个方法。
System.setProperty("webdriver.gecko.driver", "path/to/geckodriver");
final FirefoxOptions options = new FirefoxOptions();
// set options...
final FirefoxDriver driver = new FirefoxDriver(options);
driver.get("https://***.com/");
File fullScreenshotFile = driver.getFullPageScreenshotAs(OutputType.FILE);
// File will be deleted once the JVM exits, so you should copy it
对于ChromeDriver
,可以使用selenium-shutterbug 库。
Shutterbug.shootPage(driver, Capture.FULL, true).save();
// Take screenshot of the whole page using Chrome DevTools
【讨论】:
以上是关于使用 Selenium 在 Chrome 中截取整页屏幕截图的主要内容,如果未能解决你的问题,请参考以下文章
CentOS7下python3 selenium3 使用Chrome的无头浏览器 截取网页全屏图片
Python+Selenium与Chrome如何进行完美结合
python+selenium启动firefox和chrome