Технология скриншотов фоновой веб-страницы Java на основе PhantomJs

задняя часть

Технология скриншотов фоновой веб-страницы Java на основе PhantomJs

Мобильное приложение, сделанное компанией ранее, имеет требование для построения графика в один клик (есть веб-страница со статистической информацией, вам нужно сделать скриншот веб-страницы в режиме реального времени, не открывая веб-страницу, и сохранить его на сервере), что можно посмотреть на мобильном телефоне. В начале сложно было получить требования, я ссылался на много статей и решения заглавными буквами следующие:

  • Robot
  • Использование JNI для вызова сторонних компонентов C/C++
  • Компонент DJNativeSwing

Справочная статья:blog.CSDN.net/Оценка 1982/Ах…

После того, как робот провалил эксперимент, компонент DJNativeSwing успешно сделал снимок экрана, но из-за сложности CSS веб-страницы искажение изображения было серьезным и ожидаемого эффекта достичь не удалось. Тогда продолжайте искать решение, PlantomJs — идеальное решение.

STONDOMJS - это JavaScript на основе WebKit WebKer Weader Browser, который не показывает интерфейсы, вы можете делать вещи на основе браузера WebKit на основе браузера WebKit. Stortomjs обеспечивает селектор CSS, операцию DOM, JSON, HTML5, Canvas, SVG и т. Д. Phantomjs широко используются, такие как мониторинг сети, захват веб-экрана, автоматизация доступа к странице, веб-тест без браузера, в то время как блоггеры нуждаются в небольшой функции - это веб-скриншот.


Реализовать идеи

Мобильный телефон отправляет запрос на сервер, сервер перехватывает веб-страницу в виде картинки и сохраняет ее на жесткий диск, генерирует доступный URL-адрес и возвращает его на мобильный телефон.Схема выглядит следующим образом:


скачать

Перейдите непосредственно на официальный сайт, чтобы загрузить http://phantomjs.org/download.html, В настоящее время официально поддерживаются три операционные системы, включая Windows\Mac OS\Linux, Сервер blogger основан на Windows, поэтому скачайте https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-windows.zip и после распаковки получите следующий каталог:


Написать скриншот JavaScript

Справочная статья http://www.cnblogs.com/jasondan/p/4108263.html

Ответственный за сценарий скриншотаscreenshot.jsследующее:

/**
 * phantomJs 脚本
 */
var page = require('webpage').create(), system = require('system'), address, output, size;

if (system.args.length < 3 || system.args.length > 5) {
	phantom.exit(1);
} else {
	address = system.args[1];
	output = system.args[2];
	//定义宽高
	page.viewportSize = {
		width : 800,
		height : 600
	};
	page.open(address, function(status) {
		var bb = page.evaluate(function() {
			return document.getElementsByTagName('html')[0].getBoundingClientRect();
		});
		page.clipRect = {
			top : bb.top,
			left : bb.left,
			width : bb.width,
			height : bb.height
		};
		window.setTimeout(function() {
			page.render(output);
			page.close();
			console.log('渲染成功...');
		}, 1000);
	});
}
address = system.args[1];//传入的URL地址
output = system.args[2];//保存的图片路径

Вышеscreenshot.jsсодержание скрипта


Написать Java-код сервера

	public static void main(String[] args) throws IOException {
		String BLANK = "  ";
		Process process = Runtime.getRuntime().exec(
				"D:/develop_software/phantomjs/bin/phantomjs.exe" + BLANK //你的phantomjs.exe路径
				+ "D:/screenshot.js" + BLANK //就是上文中那段javascript脚本的存放路径
				+ "http://www.baidu.com" + BLANK //你的目标url地址
				+ "D:/baidu.png");//你的图片输出路径

		InputStream inputStream = process.getInputStream();
		BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
		String tmp = "";
		while ((tmp = reader.readLine()) != null) {
			if (reader != null) {
				reader.close();
			}
			if (process != null) {
				process.destroy();
				process = null;
			}
			System.out.println("渲染成功...");
		}
	}

Запуск вышеуказанного кода Java будет генерировать скриншот Baidu.png под накопителем D, как показано ниже:

На данный момент демонстрация завершена!


Инкапсуляция кода (актуальный проект)

1. Обработка Screenshot.js

практическое применение аналогичноscreenshot.jsОбычно не размещается в фиксированном каталоге, обычно размещается в корневом каталоге приложения.

Положить в Tomcat Startscreenshot.jsКэш пути

/**
  * 获取【网页快照截图脚本】文件的路径
  * 
  * @return
  */
private String getFullJsPath() {
	return AppContext.getAbsPath() + "/apicture/js/screenshot.js";
}

2, обработка phantomjs.exe

Настройте путь к phantomjs.exe, а не записывайте его напрямую в программу, как в демо.Как правило, в веб-приложениях есть общий applicationConfig.xml для хранения таких вещей, поэтому добавьте следующий xml в узел applicationConfig.xml:

...
<phantomJs>
	<bin>D:/develop_software/phantomjs/bin/phantomjs.exe</bin>
	<imagePath>apicture/pub</imagePath><!--图片生成路径-->
</phantomJs>
...

Преобразование конфигурации в объект с помощью инструментария jaxb

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

/**
 * phantomJs 配置
 * 
 * @author Fhcj
 *         2016年8月26日
 * @since
 * @version
 */
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "phantomJs")
public class PhantomJsConifg {

	@XmlElement(name = "bin")
	private String bin;
	@XmlElement(name = "imagePath")
	private String imagePath;

	public String getBin() {
		return bin;
	}

	public void setBin(String bin) {
		this.bin = bin;
	}

	public String getImagePath() {
		return imagePath;
	}

	public void setImagePath(String imagePath) {
		this.imagePath = imagePath;
	}

}

3. Напишите действие

Блогер использует nutz mvc в качестве слоя действия, аналогично можно использовать servlet или spring mvc, struts2 и т. д.

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.rbp.rt.web.result.ObjectResult;
import com.*.eqma.AppContext;
import com.*.eqma.config.PhantomJsConifg;
import com.*.eqma.web.servlet.YingJiJspServlet;
import com.*.eqma.web.servlet.ZaiQingJspServlet;
import com.*.eqma.web.servlet.ZhenQingJspServlet;
import com.*.utils.DateUtils;
import com.*.utils.JsonUtils;
import com.*.utils.StringUtils;

/**
 * 
 * 一张图访问服务
 * 
 * @author Fhcj
 *         2016年9月2日
 * @since
 * @version
 */
@Ok("json")
public class APictureAction {

	private static final Logger LOG = LoggerFactory.getLogger(APictureAction.class);
	private static final String BLANK = " ";
	private static PhantomJsConifg pjc;

	private String BIN_PATH;
	private String IMAGE_PUB_PATH;

	/**
	 * 应急一张图
	 * 
	 * @param evt_id
	 *            事件id
	 * @return
	 */
	@SuppressWarnings("unused")
	@At("/apicture/yingJi")
	public String yingJiPicture(@Param("evt_id") String evt_id) {
		ObjectResult responseResult = new ObjectResult();

		if (StringUtils.isEmpty(evt_id)) {
			responseResult.setNote("地震事件Id为空,无法渲染图片");
			responseResult.setStatus(-1);
			return JsonUtils.obj2Json(responseResult);
		}

		
		String pictureName = evt_id + "_yingJi.png";
		try {
			String imgageFullPath = getFullImagePath(pictureName);// 得到图片完整路径
			// 如果该事件的一张图存在则不用渲染
			if (new File(imgageFullPath).exists()) {
				LOG.info("事件ID为【{}】的【应急一张图】已经存在,将不会重新渲染:{}", evt_id, imgageFullPath);
				responseResult.setValue(getPictureVisitURL(pictureName));
				responseResult.setStatus(1);
				return JsonUtils.obj2Json(responseResult);
			}

			String url = YingJiJspServlet.getURL() + "?id=" + evt_id;// 应急一张图访问接口URL

			Process process = Runtime.getRuntime().exec(cmd(imgageFullPath, url));

			InputStream inputStream = process.getInputStream();
			BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
			String tmp = "";
			while ((tmp = reader.readLine()) != null) {
				close(process, reader);

				LOG.info("事件ID为【{}】的【应急一张图】渲染成功:{}", evt_id, imgageFullPath);
				LOG.info("事件ID为【{}】的【应急一张图】访问路径为:{}", evt_id, getPictureVisitURL(pictureName));
				responseResult.setValue(getPictureVisitURL(pictureName));
				responseResult.setStatus(1);
				return JsonUtils.obj2Json(responseResult);
			}
		} catch (Exception e) {
			responseResult.setStatus(-1);
			responseResult.setNote("事件ID为【{}】的【应急一张图】渲染失败");
			LOG.error("事件ID为【{}】的【应急一张图】渲染失败:", e);
		}

		return JsonUtils.obj2Json(responseResult);
	}

	/**
	 * 灾情一张图
	 * 
	 * @param evt_id
	 *            事件id
	 * @return
	 */
	@SuppressWarnings("unused")
	@At("/apicture/zaiQing")
	public String zaiQingPicture(@Param("evt_id") String evt_id) {
		ObjectResult responseResult = new ObjectResult();

		if (StringUtils.isEmpty(evt_id)) {
			responseResult.setNote("地震事件Id为空,无法渲染图片");
			responseResult.setStatus(-1);
			return JsonUtils.obj2Json(responseResult);
		}

		String pictureName = evt_id + "_zaiQing.png";
		try {
			String imgageFullPath = getFullImagePath(pictureName);

			// 如果该事件的一张图存在则不用渲染
			if (new File(imgageFullPath).exists()) {
				LOG.info("事件ID为【{}】的【灾情一张图】已经存在,将不会重新渲染:{}", evt_id, imgageFullPath);
				responseResult.setValue(getPictureVisitURL(pictureName));
				responseResult.setStatus(1);
				return JsonUtils.obj2Json(responseResult);
			}

			String url = ZaiQingJspServlet.getURL() + "?id=" + evt_id;// 灾情一张图访问接口URL
			Process process = Runtime.getRuntime().exec(cmd(imgageFullPath, url));

			InputStream inputStream = process.getInputStream();
			BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
			String tmp = "";
			while ((tmp = reader.readLine()) != null) {
				close(process, reader);

				LOG.info("事件ID为【{}】的【灾情一张图】渲染成功:{}", evt_id, imgageFullPath);
				LOG.info("事件ID为【{}】的【灾情一张图】访问路径为:{}", evt_id, getPictureVisitURL(pictureName));
				responseResult.setValue(getPictureVisitURL(pictureName));
				responseResult.setStatus(1);
				return JsonUtils.obj2Json(responseResult);
			}
		} catch (Exception e) {
			responseResult.setStatus(-1);
			responseResult.setNote("事件ID为【{}】的【灾情一张图】渲染失败");
			LOG.error("事件ID为【{}】的【灾情一张图】渲染失败:", e);
		}

		return JsonUtils.obj2Json(responseResult);
	}
	/**
	 * 震情一张图
	 * 
	 * @param lng
	 *            经度
	 * @param lat
	 *            纬度
	 * @return
	 */
	@SuppressWarnings("unused")
	@At("/apicture/zhenQing")
	public String zhenQingPicture(@Param("lng") String lng, @Param("lat") String lat) {
		ObjectResult responseResult = new ObjectResult();
		String pictureName = DateUtils.formatCurrentDate("yyyyMMddHHmmssSSS") + "_zhenQing.png";
		try {

			String imgageFullPath = getFullImagePath(pictureName);

			String url = ZhenQingJspServlet.getURL() + "?lng=" + lng + "&lat=" + lat;// 震情一张图访问接口URL

			Process process = Runtime.getRuntime().exec(cmd(imgageFullPath, url));

			InputStream inputStream = process.getInputStream();
			BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
			String tmp = "";
			while ((tmp = reader.readLine()) != null) {
				close(process, reader);

				LOG.info("【震情一张图】渲染成功:{}", imgageFullPath);
				LOG.info("【震情一张图】访问路径为:{}", getPictureVisitURL(pictureName));
				responseResult.setValue(getPictureVisitURL(pictureName));
				responseResult.setStatus(1);
				return JsonUtils.obj2Json(responseResult);
			}
		} catch (Exception e) {
			responseResult.setStatus(-1);
			responseResult.setNote("【震情一张图】渲染失败");
			LOG.error("【震情一张图】渲染失败:", e);
		}

		return JsonUtils.obj2Json(responseResult);
	}

	/**
	 * 获取执行JS脚本的window cmd 命令
	 * 
	 * @param imgageFullPath
	 *            图片完整路径
	 * @param url
	 *            截图网页的URL
	 * @return
	 */
	private String cmd(String imgageFullPath, String url) {
		return getBinPath() + BLANK + getFullJsPath() + BLANK + url + BLANK + imgageFullPath;
	}

	/**
	 * 关闭进程
	 * 
	 * @param process
	 * @param bufferedReader
	 * @throws IOException
	 */
	private void close(Process process, BufferedReader bufferedReader) throws IOException {
		if (bufferedReader != null) {
			bufferedReader.close();
		}
		if (process != null) {
			process.destroy();
			process = null;
		}
	}

	/**
	 * 通过图片名获取最终【客户端】访问的URL
	 * 
	 * @param pictureName
	 * @return
	 */
	private String getPictureVisitURL(String pictureName) {
		return AppContext.getDomain() + "/" + pjc.getImagePath() + "/" + pictureName;
	}

	/**
	 * 通过图片名获取最终完整路径
	 * 
	 * @param pictureName
	 * @return
	 */
	private String getFullImagePath(String pictureName) {
		return getPictureRootPath() + "/" + pictureName;
	}

	/**
	 * 获取【网页快照截图脚本】文件的路径
	 * 
	 * @return
	 */
	private String getFullJsPath() {
		return AppContext.getAbsPath() + "/apicture/js/screenshot.js";
	}

	/**
	 * 获取图片生成的根路径
	 * 
	 * @return
	 */
	private String getPictureRootPath() {
		ensurePhantomJsConfig();
		IMAGE_PUB_PATH = AppContext.getAbsPath() + "/" + pjc.getImagePath();
		return IMAGE_PUB_PATH;
	}

	/**
	 * 获取phantomjs.exe所在路径
	 * 
	 * @return
	 */
	private String getBinPath() {
		ensurePhantomJsConfig();
		BIN_PATH = pjc.getBin();
		return BIN_PATH;
	}

	/**
	 * 确保配置存在
	 */
	private void ensurePhantomJsConfig() {
		if (pjc == null) {
			pjc = AppContext.getApplicationConfig().getPhantomJsConifg();
		}
	}
}

Таким образом, посещение http://localhost:8080/xxx/apicture/zhenQing вернет URL-адрес изображения, который можно просмотреть на мобильном телефоне и отобразить следующим образом:

Категории