欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【PhantomJs】——利用phantomjs实现网页快照的两种方式

程序员文章站 2022-07-03 17:09:16
...

前言

今天遇到一个需求,是前端给后台一个网址,后台需要返回这个网址的快照回去,刚接手的时候一脸懵,了解了一下,是项目中有个播放视频或者直播的区域,需要一张图片来作为封面图,但是如果专门去存的话不方便,也不灵活。

既然有需求,那就只能找方法了,网上各种翻阅,方法不多,也不算少,但是很杂,很多工具及代码都有不足之处,有的比较慢,有的是会出现可视化的工具框,比如IFrame,显然不好,最后确定了用phantomjs,也应该是用的最多的。

准备

由于phantomjs是一个工具,所以对于不同的操作系统是不通用的,所以要下载各个系统的工具包来做适配,在phantomjs官网镜像下载phantomjs工具包。

【PhantomJs】——利用phantomjs实现网页快照的两种方式

我这里选择了2.1.1的版本。

下载好之后解压并对名字做修改先暂时放在F盘

【PhantomJs】——利用phantomjs实现网页快照的两种方式

这里项目用的是springboot项目,其他框架下面内容请自行调整

application.yml

由于phantomjs工具包比较大,四个加起来也有将近200M,放在项目中显然是不合适的,所以我们选择在yml文件中配置路径,将工具包放在服务器本地路径中

phantomJs:
  windows: F:/
  mac: /opt/
  linux: /opt/

这里就是配置了windows,mac,linux环境下phantomjs工具包所需要放置的路径。

ResultBean.java

接口放回结果还是通过的ResultBean类,这里简化只有code和data两个属性:

public class ResultBean<T> {
    private Integer code;
    private T data;

    public Integer getCode() {return code;}

    public void setCode(Integer code) {this.code = code;}

    public T getData() {return data;}

    public void setData(T data) {this.data = data;}

    public ResultBean() {}

    public ResultBean(Integer code, T data) {
        this.code = code;
        this.data = data;
    }

    @Override
    public String toString() {
        return "ResultBean{" +"code='" + code + '\'' +", data=" + data +'}';
    }
}

PlatformUtils.java

这里写一个工具类来区分不同的操作系统,来进行不同系统phantomjs的选择

public class PlatformUtils {
    private static final Logger logger = LoggerFactory.getLogger(PlatformUtils.class);
    private static final String OS_NAME = System.getProperty("os.name").toLowerCase();
    private static final String OS_ARCH = System.getProperty("os.arch").toLowerCase();
    private static final String OSARCH = "64";

    public static boolean isWindows() {
        return OS_NAME.contains("windows");
    }

    public static boolean isMac() {
        return OS_NAME.contains("mac");
    }

    public static boolean isLinux() {
        return OS_NAME.contains("linux");
    }

    public static boolean is64OsArch() {
        return OS_ARCH.contains(OSARCH);
    }
}

下面我们开始这两种方式的演示与实现:

第一种方式

        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>2.45.0</version>
        </dependency>

        <dependency>
            <groupId>com.codeborne</groupId>
            <artifactId>phantomjsdriver</artifactId>
            <version>1.2.1</version>
        </dependency>
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.phantomjs.PhantomJSDriver;
import org.openqa.selenium.phantomjs.PhantomJSDriverService;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.TimeUnit;

/**
 * @description: 快照工具类
 * @author: WangZhiJun
 * @create: 2019-10-25 14:36
 **/
public class SnapShotUtils {
    private static Logger logger = LoggerFactory.getLogger(SnapShotUtils.class);
    private static final String EQUAL = "=";


    public static String getDriverPath(String path) {
        if (PlatformUtils.isWindows()) {
            logger.info("当前系统:Windows");
            return path + "phantomjs/windows/bin/phantomjs.exe";
        }else {
            if (PlatformUtils.isLinux()) {
                logger.info("当前系统:Linux");
                if (PlatformUtils.is64OsArch()) {
                    logger.info("当前系统:64位");
                    return path + "phantomjs/linux-x86_64/bin/phantomjs";
                } else {
                    return path + "phantomjs/linux-i686/bin/phantomjs";
                }
            } else {
                logger.info("当前系统:Mac");
                return path + "phantomjs/macosx/bin/phantomjs";
            }
        }
    }

    public static String getBase64(String url, String path) {
        //设置必要参数
        DesiredCapabilities dcaps = new DesiredCapabilities();
        //ssl证书支持
        dcaps.setCapability("acceptSslCerts", true);
        //截屏支持
        dcaps.setCapability("takesScreenshot", true);
        //css搜索支持
        dcaps.setCapability("cssSelectorsEnabled", true);
        //js支持
        dcaps.setJavascriptEnabled(true);
        //驱动支持(第二参数表明的是你的phantomjs引擎所在的路径)
        dcaps.setCapability(PhantomJSDriverService.PHANTOMJS_EXECUTABLE_PATH_PROPERTY,
                getDriverPath(path));
        //创建*面浏览器对象
        PhantomJSDriver driver = new PhantomJSDriver(dcaps);

        //设置隐性等待(作用于全局)
        driver.manage().timeouts().implicitlyWait(1, TimeUnit.SECONDS);
        //打开页面
        driver.get(url);
        String base64  = ((TakesScreenshot)driver).getScreenshotAs(OutputType.BASE64);
        logger.info("成功获取URL快照,快照大小:"+imageSize(base64));
        return base64;
    }

    /**
     * 通过图片base64流判断图片等于多少字节
     * image 图片流
     */
    private static Integer imageSize(String image) {
        // 1.需要计算文件流大小,首先把头部的data:image/png;base64,(注意有逗号)去掉。
        String str = image.substring(22);
        //2.找到等号,把等号也去掉
        int equalIndex = str.indexOf("=");
        if (str.indexOf(EQUAL) > 0) {
            str = str.substring(0, equalIndex);
        }
        //3.原来的字符流大小,单位为字节
        int strLength = str.length();
        //4.计算后得到的文件流大小,单位为字节
        return (strLength - (strLength / 8) * 2);
    }

    public static void main(String[] args) {
        System.out.println(getBase64("https://www.baidu.com","F:/"));
    }
}

第二种方式

        <dependency>
            <groupId>net.coobird</groupId>
            <artifactId>thumbnailator</artifactId>
            <version>0.4.8</version>
        </dependency>
import net.coobird.thumbnailator.Thumbnails;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.charset.StandardCharsets;

/**
 * @description: 快照工具类
 * @author: WangZhiJun
 * @create: 2019-10-25 14:36
 **/
public class PhantomUtils {
    private static Logger logger = LoggerFactory.getLogger(PhantomUtils.class);
    private static final Integer BITNUM = 64;
    private static final String BLANK = " ";
    private static final String EQUAL = "=";

    /**
     * 功能描述  关闭命令
     *
     * @param process 1
     * @param bufferedReader 1
     * @author CosmosRay
     * @date 2019/4/13
     */
    public static void close(Process process, BufferedReader bufferedReader) throws IOException {
        if (bufferedReader != null) {
            bufferedReader.close();
        }
        if (process != null) {
            process.destroy();
        }
    }
    /**
     * 功能描述  通过URL获取该URL快照base64
     * @param url 连接
     * @return  java.lang.String
     * @author  CosmosRay
     * @date  2019/4/13
    */
    public static String getBase64(String url, String path) throws IOException {
        if(StringUtils.isBlank(url)){
            return null;
        }
        String[] pathArray;
        if (PlatformUtils.isWindows()) {
            pathArray = getWindowsPath(path);
        } else {
            if (PlatformUtils.isLinux()) {
                if (PlatformUtils.is64OsArch()) {
                    pathArray = getLinuxPath(64, path);
                } else {
                    pathArray = getLinuxPath(32, path);
                }
            } else {
                pathArray = getMacPath(path);
            }
        }
        return printUrlScreen(url, pathArray);
    }

    /**
     * 功能描述 windows系统服务器路径地址
     *
     * @return java.lang.String[]
     * @author CosmosRay
     * @date 2019/4/13
     */
    private static String[] getWindowsPath(String path) {
        String[] pathArray = new String[3];
        //图片临时保存路径
        pathArray[0] = path + "phantomjs/windows";
        //phantomjs主程序路径
        pathArray[1] = path + "phantomjs/windows/bin/phantomjs";
        //phantomjs参数js路径
        pathArray[2] = path + "phantomjs/windows/examples/rasterize.js";

        return pathArray;
    }

    /**
     * 功能描述
     *
     * @return java.lang.String[]
     * @author CosmosRay
     * @date 2019/4/13
     */
    private static String[] getLinuxPath(int osArch, String path) {
        String[] pathArray = new String[3];

        if (osArch == BITNUM) {
            //图片临时保存路径
            pathArray[0] = path + "phantomjs/linux-x86_64";
            //phantomjs主程序路径
            pathArray[1] = path + "phantomjs/linux-x86_64/bin/phantomjs";
            //phantomjs参数js路径
            pathArray[2] = path + "phantomjs/linux-x86_64/examples/rasterize.js";
        } else {
            //图片临时保存路径
            pathArray[0] = path + "phantomjs/linux-i686";
            //phantomjs主程序路径
            pathArray[1] = path + "phantomjs/linux-i686/bin/phantomjs";
            //phantomjs参数js路径
            pathArray[2] = path + "phantomjs/linux-i686/examples/rasterize.js";
        }
        return pathArray;
    }
    /**
     * 功能描述   MAC系统
     * @return  java.lang.String[]
     * @author  CosmosRay
     * @date  2019/4/13
    */
    private static String[] getMacPath(String path) {
        String[] pathArray = new String[3];
        //图片临时保存路径
        pathArray[0] = path + "phantomjs/macosx";
        //phantomjs主程序路径
        pathArray[1] = path + "phantomjs/macosx/bin/phantomjs";
        //phantomjs参数js路径
        pathArray[2] = path + "phantomjs/macosx/examples/rasterize.js";
        return pathArray;
    }

    /**
     * 通过图片base64流判断图片等于多少字节
     * image 图片流
     */
    private static Integer imageSize(String image) {
        // 1.需要计算文件流大小,首先把头部的data:image/png;base64,(注意有逗号)去掉。
        String str = image.substring(22);
        //2.找到等号,把等号也去掉
        int equalIndex = str.indexOf("=");
        if (str.indexOf(EQUAL) > 0) {
            str = str.substring(0, equalIndex);
        }
        //3.原来的字符流大小,单位为字节
        int strLength = str.length();
        //4.计算后得到的文件流大小,单位为字节
        return (strLength - (strLength / 8) * 2);
    }

    /**
     * 功能描述   执行cmd命令
     *
     * @param imagePath 图片名称绝对路径
     * @param url       URL地址
     * @return java.lang.String
     * @author CosmosRay
     * @date 2019/4/13
     */
    private String cmd(String imagePath, String url, String[] pathArray) {
        return pathArray[1] + BLANK + pathArray[2] + BLANK + url + BLANK + imagePath;
    }

    /**
     * 功能描述
     *
     * @param url 连接URL
     * @return void 无
     * @author CosmosRay
     * @date 2019/4/13
     */
    private static String printUrlScreen(String url, String[] pathArray) throws IOException {
        if(StringUtils.isBlank(url)){
            return null;
        }
        //图片路径
        String imagePath = pathArray[0] + "/menu.png";
        //Java中使用Runtime和Process类运行外部程序
        PhantomUtils phantomTools2 = new PhantomUtils();
        Process process = Runtime.getRuntime().exec(phantomTools2.cmd(imagePath, url, pathArray));
        InputStream inputStream = process.getInputStream();

        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
        String rs;
        while ((rs = reader.readLine()) != null) {
            close(process, reader);
        }

        File file;
        BufferedInputStream in;
        byte[] ret = null;
        file = new File(imagePath);
        if (file.exists()) {
            in = new BufferedInputStream(new FileInputStream(file));
            Thumbnails.Builder<? extends InputStream> builder = Thumbnails.of(in).size(256, 256);
            BufferedImage bufferedImage = builder.asBufferedImage();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ImageIO.write(bufferedImage, "png", baos);
            ret = baos.toByteArray();
        }
        String base64 = null;
        if (ret != null) {
            //转换成base64串
            String pngBase64 = Base64.encodeBase64String(ret).trim();
            //删除 \r\n
            pngBase64 = pngBase64.replaceAll("\n", "").replaceAll("\r", "");
            base64 = "data:image/png;base64," + pngBase64;
            logger.info("成功获取URL快照,快照大小:"+imageSize(base64));
        }
        return base64;
    }

    public static void main(String[] args) throws IOException {
        System.out.println(getBase64("https://baidu.com", "F:/"));
    }
}

演示

@RestController
@RequestMapping()
public class ScreenshotController {

    @Autowired
    private ScreenshotService screenshotService;

    @GetMapping("/snap")
    public ResultBean<String> getScreenshot(@RequestParam(value = "url") String url){
        ResultBean resultBean = new ResultBean();
        try {
            resultBean.setCode(1);
            resultBean.setData(screenshotService.getScreenshot(url));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return resultBean;
    }
}
public interface ScreenshotService {
    String getScreenshot(String url) throws Exception;
}
@Service
public class ScreenshotServiceImpl implements ScreenshotService {

    @Value("${phantomJs.mac}")
    private String phantomJsMac;

    @Value("${phantomJs.windows}")
    private String phantomJsWin;

    @Value("${phantomJs.linux}")
    private String phantomJsLinux;

    @Override
    public String getScreenshot(String url) throws IOException {
        String diverPath;
        if (PlatformUtils.isMac()) {
            diverPath = phantomJsMac;
        } else if (PlatformUtils.isWindows()) {
            diverPath = phantomJsWin;
        } else {
            diverPath = phantomJsLinux;
        }
        //第一种
        //return SnapShotUtils.getBase64(url, diverPath);
        //第二种
        return PhantomUtils.getBase64(url, diverPath);
    }
}

结果

【PhantomJs】——利用phantomjs实现网页快照的两种方式