使用Selenium在Chrome中拍摄整页屏幕截图

我知道以前这是不可能的,但现在有了以下更新:

https://developers.google.com/web/updates/2017/04/devtools-release-notes#screenshots

这似乎可以使用chrome dev工具。

现在可以在java中使用selenium吗?

使用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 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.所示,但现在使用Selenium API在Java中:

 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 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 params) throws IOException { Map 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 jsonValue(@Nonnull Object map, @Nonnull String path, @Nonnull Class type) { String[] segs = path.split("\\."); Object current = map; for(String name: segs) { Map cm = (Map) current; Object o = cm.get(name); if(null == o) return null; current = o; } return (T) current; } } 

这使您可以使用指定的命令,并在其中创建一个带有png格式图像的文件。 您当然也可以通过在字节上使用ImageIO.read()直接创建BufferedImage。

是的,从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://stackoverflow.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://stackoverflow.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 getFullScreenshotAs(OutputType 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)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)response).get("result"); return ((Map)result).get("value"); } }