Java实现HTML页面截图功能

概述

业务开发中,经常会有HTML页面截图,或打印另存为PDF文件的需求。本文即是HTML页面截图需求的技术调研过程的成文。不想看长篇大论的同学,可以直接看Selenium部分,本人最后也是采取此方案。

html2canvas

直接上代码:

const canvas = function () {
    html2canvas($("#chart"), {
        onrendered: function (canvas) {
            // 将id为“class11”部分的代码转换为canvas
            $("#class11").html(canvas);
            const type = 'png';
            // 将图片转换为png类型的流
            const imgData = canvas.toDataURL('png');

            const saveFile = function (data, filename) {
                const save_link = document.createElementNS('http://www.w3.org/1999/xhtml', 'a');
                save_link.href = data;
                save_link.download = filename;
                const event = document.createEvent('MouseEvents');
                event.initMouseEvent('click', true, false, window, 0, 0, 0,
                    0, 0, false, false, false, false, 0, null);
                save_link.dispatchEvent(event);
            };
            const filename = 'html2canvasSavePic' + (new Date()).getTime() + '.' + type;
            // 下载文件
            saveFile(imgData, filename);
        }
    });
};

优缺点

html2canvas可以将HTML代码块进行截取,并生成快照形式的canvas,然后利用html5的下载功能。
优点:前台技术,实现比较容易。
缺点:如果是使用H5,则只能在IE9+的版本上使用。需引用jQuery.js和html2canvas.js。

PhantomJS

PhantomJS下载地址
直接上代码:

/**
 * 图片保存目录
 */
private static final String TEMP_PATH = "E:\\tmp\\img";

private static final String BLANK = " ";

/**
 * 可执行文件路径
 */
private static final String BIN_PATH = "D:\\phantomjs-2.1.1-windows\\bin\\phantomjs.exe";

/**
 * js路径
 */
private static final String JS_PATH = "D:\\phantomjs-2.1.1-windows\\examples\\rasterize.js";

public static void main(String[] args) throws IOException, InterruptedException, SAXException {
    String url = "http://www.samr.gov.cn/ggjgs/sjdt/gzdt/202001/t20200115_310511.html";
//  String url = "http://www.cqn.com.cn/cj/content/2018-06/12/content_5907410.htm";
    printUrlScreen2jpg(url);
}

public static void printUrlScreen2jpg(String url) throws IOException, InterruptedException {
    // 图片路径
    String imagePath = TEMP_PATH + "/" + System.currentTimeMillis() + ".png";
    // Java中使用Runtime和Process类运行外部程序
    Process process = Runtime.getRuntime().exec(cmd(imagePath, url));
    Thread.sleep(1000);
    InputStream inputStream = process.getInputStream();
    BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
    while (reader.readLine() != null) {
        close(process, reader);
    }
}

/**
 * 执行cmd命令
 */
private static String cmd(String imagePath, String url) {
    return BIN_PATH + BLANK + JS_PATH + BLANK + url + BLANK + imagePath;
}

/**
 * 关闭命令
 */
private static void close(Process process, BufferedReader bufferedReader) throws IOException {
    if (bufferedReader != null) {
        bufferedReader.close();
    }
    if (process != null) {
        process.destroy();
    }
}

优缺点

优点:效率高,截图清晰,最重要的是能够截完整的页面,而不是当前可见页面。
缺点:对于运行环境有小小的要求,需要有PhantomJS可执行环境;很容易解决,算不上缺点。
但是,对于部分网站,如上面代码片段里面的http://www.samr.gov.cn/ggjgs/sjdt/gzdt/202001/t20200115_310511.html,截图失败:
在这里插入图片描述
很明显,被拦截。
解决思路:添加User-Agent信息,考虑在rasterize.js脚本追加,但是看不到相关的代码片段。有待进一步调研。

DjNativeSwing

原生Java实现,几乎不会被网站屏蔽,至少我没碰到过。
Maven引入依赖,第三个是64位操作系统需要添加:

<dependency>
   <groupId>com.hynnet</groupId>
   <artifactId>DJNativeSwing</artifactId>
   <version>1.0.0</version>
</dependency>
<dependency>
    <groupId>com.hynnet</groupId>
    <artifactId>DJNativeSwing-SWT</artifactId>
    <version>1.0.0</version>
</dependency>
<dependency>
    <groupId>org.eclipse.swt.org.eclipse.swt.win32.win32.x86_64.4.3.swt</groupId>
    <artifactId>org.eclipse.swt.win32.win32.x86_64</artifactId>
    <version>4.3</version>
</dependency>

直接上代码:

public class PicSnapshotUtils extends JPanel {
	/**
	 * 行分隔符
	 */
	private final static String LS = System.getProperty("line.separator", "/n");
	/**
	 * 当网页超出目标大小时 截取
	 */
	private static final int MAX_WIDTH = 2000;
	private static final int MAX_HEIGHT = 1400;

    public static void main(String[] args) throws IOException, InterruptedException, SAXException {
        String url = "http://www.samr.gov.cn/ggjgs/sjdt/gzdt/202001/t20200115_310511.html";
        printUrlScreen2jpg("1122.jpg", url, 1400, 900);
	}

	/**
	 * @param file 预生成的图片全路径
	 * @param url  网页地址
	 */
	public PicSnapshotUtils(final String file, final String url, final String withResult) {
	    super(new BorderLayout());
	    JPanel webBrowserPanel = new JPanel(new BorderLayout());
	    final JWebBrowser webBrowser = new JWebBrowser((NSOption) null);
	    webBrowser.setBarsVisible(false);
	    webBrowser.navigate(url);
	    webBrowserPanel.add(webBrowser, BorderLayout.CENTER);
	    add(webBrowserPanel, BorderLayout.CENTER);
	    JPanel panel = new JPanel(new FlowLayout());
	    webBrowser.addWebBrowserListener(new WebBrowserAdapter() {
	        // 监听加载进度
	        @Override
	        public void loadingProgressChanged(WebBrowserEvent e) {
	            // 当加载完毕时
	            if (e.getWebBrowser().getLoadingProgress() == 100) {
	                String result = (String) webBrowser.executeJavascriptWithResult(withResult);
	                if (StrUtil.isEmpty(result)) {
	                    return;
	                }
	                int index = result.indexOf(":");
	                NativeComponent nativeComponent = webBrowser.getNativeComponent();
	                Dimension originalSize = nativeComponent.getSize();
	                Dimension imageSize = new Dimension(Integer.parseInt(result.substring(0, index)),
	                        Integer.parseInt(result.substring(index + 1)));
	                imageSize.width = Math.max(originalSize.width, imageSize.width + 50);
	                imageSize.height = Math.max(originalSize.height, imageSize.height + 50);
	                nativeComponent.setSize(imageSize);
	                BufferedImage image = new BufferedImage(imageSize.width, imageSize.height, BufferedImage.TYPE_INT_RGB);
	                nativeComponent.paintComponent(image);
	                nativeComponent.setSize(originalSize);
	                // 当网页超出目标大小时
	                if (imageSize.width > MAX_WIDTH || imageSize.height > MAX_HEIGHT) {
	                    // 截图部分图形
	//                        image = image.getSubimage(0, 0, MAX_WIDTH, MAX_HEIGHT);
	                    // 此部分为使用缩略图
	                    int width = image.getWidth(), height = image.getHeight();
	                    AffineTransform tx = new AffineTransform();
	                    tx.scale((double) MAX_WIDTH / width, (double) MAX_HEIGHT / height);
	                    AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
	                    image = op.filter(image, null);
	                }
	                try {
	                    // 输出图像
	                    ImageIO.write(image, "jpg", new File(file));
	                } catch (IOException ex) {
	                    ex.printStackTrace();
	                }
	                // 退出操作
	                System.exit(0);
	            }
	        }
	    });
	    add(panel, BorderLayout.SOUTH);
	}
	
	/**
	 * 以javascript脚本获得网页全屏后大小
	 */
	private static String getScreenWidthHeight() {
	    return "var width = 0;" + LS +
	            "var height = 0;" + LS +
	            "if(document.documentElement) {" + LS +
	            "  width = Math.max(width, document.documentElement.scrollWidth);" + LS +
	            "  height = Math.max(height, document.documentElement.scrollHeight);" + LS +
	            "}" + LS +
	            "if(self.innerWidth) {" + LS +
	            "  width = Math.max(width, self.innerWidth);" + LS +
	            "  height = Math.max(height, self.innerHeight);" + LS +
	            "}" + LS +
	            "if(document.body.scrollWidth) {" + LS +
	            "  width = Math.max(width, document.body.scrollWidth);" + LS +
	            "  height = Math.max(height, document.body.scrollHeight);" + LS +
	            "}" + LS +
	            "return width + ':' + height;";
	}
	
	private static void printUrlScreen2jpg(final String file, final String url, final int width, final int height) {
	    NativeInterface.open();
	    SwingUtilities.invokeLater(() -> {
	        String withResult = "var width = " + width + ";var height = " + height + ";return width +':' + height;";
	        if (width == 0 || height == 0) {
	            withResult = getScreenWidthHeight();
	        }
	        JFrame frame = new JFrame("网页截图");
	        frame.getContentPane().add(new PicSnapshotUtils(file, url, withResult), BorderLayout.CENTER);
	        // TODO:加载指定页面,最大保存为640x480的截图
	        frame.setSize(640, 480);
	        // 仅初始化,但不显示
	        frame.invalidate();
	        frame.pack();
	        frame.setVisible(false);
	    });
	    NativeInterface.runEventPump();
	}

}

优缺点

优点:大概率不会被拦截
缺点:不能截完整的页面,只能截可见(不滑动滚轮)的当前页面。Linux系统兼容性暂未测试。

另外,程序运行时,会输出报错日志,虽然截图是成功的:

Exception "java.lang.ClassNotFoundException: com/intellij/codeInsight/editorActions/FoldingData"while constructing DataFlavor for: application/x-java-jvm-local-objectref; class=com.intellij.codeInsight.editorActions.FoldingData
Exception "java.lang.ClassNotFoundException: com/intellij/codeInsight/editorActions/FoldingData"while constructing DataFlavor for: application/x-java-jvm-local-objectref; class=com.intellij.codeInsight.editorActions.FoldingData
Exception "java.lang.ClassNotFoundException: com/intellij/codeInsight/editorActions/ReferenceData"while constructing DataFlavor for: application/x-java-jvm-local-objectref; class=com.intellij.codeInsight.editorActions.ReferenceData
Exception "java.lang.ClassNotFoundException: com/intellij/codeInsight/editorActions/ReferenceData"while constructing DataFlavor for: application/x-java-jvm-local-objectref; class=com.intellij.codeInsight.editorActions.ReferenceData

Selenium

核心,另起一篇:Java+Selenium实现网页截图

html2image

引用依赖:

<dependency>
  <groupId>com.github.xuwei-k</groupId>
  <artifactId>html2image</artifactId>
  <version>0.1.0</version>
</dependency>

优缺点

html2image是可以识别html标签并将html转换成图片的java项目。
优点:后台转换,故对浏览器的版本基本没有要求。
缺点:对样式的识别不是很好,转换出来的图片比较简单,基本没有可以兼容的样式。

Cssbox

依赖:

<dependency>
    <groupId>net.sf.cssbox</groupId>
    <artifactId>cssbox</artifactId>
    <version>5.0.0</version>
</dependency>

直接给代码:

public static void cssBox() throws IOException, InterruptedException, SAXException {
    ImageRenderer render = new ImageRenderer();
    render.setWindowSize(new Dimension(800, 1000), false);
    FileOutputStream out = new FileOutputStream("cssbox.png");
    Thread.sleep(10000);
    render.renderURL("http://www.samr.gov.cn/ggjgs/sjdt/gzdt/202001/t20200115_310511.html", out, ImageRenderer.Type.PNG);
}

优缺点

优点:无
缺点:不支持引用的外部js、css;其次,自定义设置的宽度有时候不起作用,代码内部有一个默认的宽度是2400。控制台很多报错:

10:34:46.475 [main] ERROR org.fit.cssbox.layout.ContentImage - Unable to get image from: http://www.samr.gov.cn/images/ico39.png

Robot

和Selenium比较类似。未做过多调研。

Cobra

maven搜索到一个看起来比较像能实现网页截图的依赖,并不是放在maven中央仓库的,如果可以下载jar包也行。依赖jar包下载地址为:Tidalwave,实际上已经打不开,404。另一方面,该jar包久未更新,故而未继续调研下去。

xhtmlrenderer

有依赖启动问题。

结论

Selenium牛逼。
一开始网上找到的Java版本Selenium代码也有截图不全的问题,和Python Selenium API还不是一一对应,考虑Java调用Python脚本。未免不是一种思路,但是太麻烦。

参考

Java实现网页截屏功能-PhantomJS
DjNativeSwing网页截图
使用Python+Selenium网页截图,解决截图不全问题
HTML转为图片-xhtmlrenderer
HTML转为图片-Cobra
HTML转为图片-Robot
Cssbox实现HTML转图片

  • 7
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
实现网页聊天功能,需要使用前端的技术如HTML、CSS、JavaScript,以及后端技术如Java Servlet或者JSP等。 以下是一个简单的网页聊天功能的界面代码示例: ```html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Web Chat</title> <style> #messages { height: 300px; overflow-y: scroll; } </style> </head> <body> <h1>Web Chat</h1> <div id="messages"></div> <form onsubmit="return sendMessage()"> <input type="text" id="messageInput" placeholder="Type your message here..."> <button>Send</button> </form> <script> var socket = new WebSocket("ws://localhost:8080/chat"); socket.onmessage = function(event) { var message = event.data; var messagesDiv = document.getElementById("messages"); messagesDiv.innerHTML += "<p>" + message + "</p>"; }; function sendMessage() { var messageInput = document.getElementById("messageInput"); socket.send(messageInput.value); messageInput.value = ""; return false; } </script> </body> </html> ``` 这个界面代码示例中包含一个聊天消息展示区域和一个消息发送表单。通过JavaScript中的WebSocket对象建立连接并监听接收到的消息,将接收到的消息显示在聊天消息展示区域中。同时,发送表单中的消息通过WebSocket对象发送到服务器端。 在服务器端,可以使用Java Servlet或JSP来处理WebSocket连接和消息的收发。这里给出一个简单的Servlet代码示例: ```java import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.websocket.OnMessage; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; @ServerEndpoint("/chat") @WebServlet("/chat") public class ChatServlet extends HttpServlet { private static final long serialVersionUID = 1L; private Session session; @OnMessage public void onMessage(String message) throws IOException { session.getBasicRemote().sendText(message); } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.getRequestDispatcher("/WEB-INF/chat.jsp").forward(request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // handle form submission } @Override public void onOpen(Session session) { this.session = session; } @Override public void onClose(Session session) { this.session = null; } } ``` 这个Servlet代码示例使用了Java WebSocket API,通过@ServerEndpoint注解声明WebSocket连接地址,并在onOpen、onClose和@OnMessage注解的方法中处理WebSocket连接和消息的收发。同时,doGet方法用于返回聊天界面,doPost方法用于处理表单提交,但这里并没有实现表单处理的相关代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

johnny233

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值