1. 程式人生 > 實用技巧 >Phantomjs實現後端生成圖片檔案

Phantomjs實現後端生成圖片檔案

目錄

今天,給大家分享一個Java後端利用Phantomjs實現生成圖片的功能,同學們使用的時候,可以參考下!

PhantomJS簡介

首先,什麼是PhantomJS?

根據官網介紹:

PhantomJS is a command-line tool. -- 其實就是一個命令列工具。

PhantomJS的下載地址:

Windows:phantomjs-2.1.1-windows.zip

Linux:phantomjs-2.1.1-linux-x86_64.tar.bz2;phantomjs-2.1.1-linux-i686.tar.bz2

MacOS:phantomjs-2.1.1-macosx.zip

下載下來後,我們看到bin目錄下就是可執行檔案phantomjs.exe,我們可以將它配置到環境變數中,方便命令使用!

還有一個examples目錄,它下面是很多js樣例,關於這些樣例作用,參考官網解釋,給大家做個簡單翻譯:

1. Basic examples

  • arguments.js:顯示傳遞給指令碼的引數
  • countdown.js:列印10秒倒計時
  • echoToFile.js:將命令列引數寫入檔案
  • fibo.js:列出了斐波那契數列中的前幾個數字
  • hello.js:顯示著名訊息
  • module.js:並universe.js演示模組系統的使用
  • outputEncoding.js:顯示各種編碼的字串
  • printenv.js:顯示系統的環境變數
  • scandir.js:列出目錄及其子目錄中的所有檔案
  • sleepsort.js:對整數進行排序並根據其值延遲顯示
  • version.js:打印出PhantomJS版本號
  • page_events.js:打印出頁面事件觸發:有助於更好地掌握page.on*回撥
    2. Rendering/rasterization
  • colorwheel.js:使用HTML5畫布建立色輪
  • rasterize.js:將網頁光柵化為影象或PDF
  • render_multi_url.js:將多個網頁渲染為影象
    3. Page automation
  • injectme.js:將自身注入到網頁上下文中
  • phantomwebintro.js:使用jQuery從phantomjs.org讀取.version元素文字
  • unrandomize.js:在頁面初始化時修改全域性物件
  • waitfor.js:等待直到測試條件為真或發生超時
    4. Network
  • detectniff.js:檢測網頁是否嗅探使用者代理
  • loadspeed.js:計算網站的載入速度
  • netlog.js:轉儲所有網路請求和響應
  • netsniff.js:以HAR格式捕獲網路流量
  • post.js:將HTTP POST請求傳送到測試伺服器
  • postserver.js:啟動Web伺服器並向其傳送HTTP POST請求
  • server.js:啟動Web伺服器並向其傳送HTTP GET請求
  • serverkeepalive.js:啟動Web伺服器,以純文字格式回答
  • simpleserver.js:啟動Web伺服器,以HTML格式回答
    5. Testing
  • run-jasmine.js:執行基於Jasmine的測試
  • run-qunit.js:執行基於QUnit的測試
    6. Browser
  • features.js:檢測瀏覽器功能使用modernizr.js
  • useragent.js:更改瀏覽器的使用者代理屬性

今天,我們根據網頁URL生成圖片,使用的就是rasterize.js:將網頁光柵化為影象或PDF

瞭解rasterize.js

我們來看一下rasterize.js的內容(原始檔對size的處理有錯誤,這裡已修正!):

"use strict";
var page = require('webpage').create(),
    system = require('system'),
    address, output, size;

if (system.args.length < 3 || system.args.length > 5) {
    console.log('Usage: rasterize.js URL filename [paperwidth*paperheight|paperformat] [zoom]');
    console.log('  paper (pdf output) examples: "5in*7.5in", "10cm*20cm", "A4", "Letter"');
    console.log('  image (png/jpg output) examples: "1920px" entire page, window width 1920px');
    console.log('                                   "800px*600px" window, clipped to 800x600');
    phantom.exit(1);
} else {
    address = system.args[1];
    output = system.args[2];
    page.viewportSize = { width: 800, height: 200 };
    if (system.args.length > 3 && system.args[2].substr(-4) === ".pdf") {
        size = system.args[3].split('*');
        page.paperSize = size.length === 2 ? { width: size[0], height: size[1], margin: '0px' }
                                           : { format: system.args[3], orientation: 'portrait', margin: '1cm' };
    } else if (system.args.length > 3 && system.args[3].substr(-2) === "px") {
        size = system.args[3].split('*');
        if (size.length === 2) {
            var pageWidth = parseInt(size[0].substr(0,size[0].indexOf("px")), 10);
            var pageHeight = parseInt(size[1].substr(0,size[1].indexOf("px")), 10);
            page.viewportSize = { width: pageWidth, height: pageHeight };
            page.clipRect = { top: 0, left: 0, width: pageWidth, height: pageHeight };
        } else {
            var pageWidth = parseInt(system.args[3].substr(0,system.args[3].indexOf("px")), 10);
            var pageHeight = parseInt(pageWidth * 3/4, 10); // it's as good an assumption as any
            page.viewportSize = { width: pageWidth, height: pageHeight };
        }
    }
    if (system.args.length > 4) {
        page.zoomFactor = system.args[4];
    }
    page.open(address, function (status) {
        if (status !== 'success') {
            console.log('Unable to load the address!');
            phantom.exit(1);
        } else {
            window.setTimeout(function () {
                page.render(output);
                phantom.exit();
            }, 200);
        }
    });
}

有過終端開發的人,對這段命令理解起來都不會太難,這裡我就不多說了,後面,我們重點介紹它的使用!

使用方法

首先,我們將Phantom的包引入工程,放在resources目錄下。因為我們要保證本地windows開發與伺服器linux環境開發打包後都能執行,所以,我們將windows和linux兩個包都引入。

然後,我們建立Phantom的使用工具類PhantomTools.class

package test;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.UUID;

/**
 * 網頁轉圖片處理類,使用外部CMD
 *
 * @author lekkoli
 */
@Slf4j
public class PhantomTools {

    /**
     * 可執行檔案phantomjs.exe路徑
     */
    private final String phantomjsPath;
    /**
     * 快照圖生成JS路徑
     */
    private final String rasterizePath;
    /**
     * 臨時圖片字首
     */
    private static final String FILE_PREFIX = "TIG-AE-";
    /**
     * 臨時圖片字尾
     */
    private static final String FILE_SUFFIX = ".jpg";

    /**
     * 構造引數
     * 獲取phantomjs路徑
     */
    public PhantomTools() {
        String bootPath = new File(this.getClass().getResource("/").getPath()).getPath();
        phantomjsPath = String.join(File.separator, bootPath, "phantomjs-2.1.1-windows", "bin", "phantomjs");
        rasterizePath = String.join(File.separator, bootPath, "phantomjs-2.1.1-windows", "examples", "rasterize.js");
    }

    /**
     * url 中需要轉義的字元
     * 1. +  URL 中+號表示空格 %2B
     * 2. 空格 URL中的空格可以用+號或者編碼 %20
     * 3. /  分隔目錄和子目錄 %2F
     * 4. ?  分隔實際的 URL 和引數 %3F
     * 5. % 指定特殊字元 %25
     * 6. # 表示書籤 %23
     * 7. & URL 中指定的引數間的分隔符 %26
     * 8. = URL 中指定引數的值 %3D
     *
     * @param url 需要轉義的URL
     * @return 轉義後的URL
     */
    public String parseUrl(String url) {
        String parsedUrl = StringUtils.replace(url, "&", "%26");
        log.info("[解析後的URL:{}]", parsedUrl);
        return parsedUrl;
    }

    /**
     * 根據URL生成指定fileName的位元組陣列
     *
     * @param url 請求URL
     * @return 圖片位元組陣列
     */
    public byte[] create(String url) {
        return create(url, null);
    }

    /**
     * 根據URL生成指定fileName的位元組陣列
     *
     * @param url  請求URL
     * @param size 指定圖片尺寸,例如:1000px*800px
     * @return 圖片位元組陣列
     */
    public byte[] create(String url, String size) {
        // 伺服器檔案存放地址
        String filePath = FileUtils.getTempDirectoryPath() + FILE_PREFIX + UUID.randomUUID().toString() + FILE_SUFFIX;
        try {
            // 執行快照命令
            String command = String.join(StringUtils.SPACE, phantomjsPath, rasterizePath, url, filePath, size);
            log.info("[執行命令:{}]", command);
            // 執行命令操作
            Process process = Runtime.getRuntime().exec(command);
            // 一直掛起,直到子程序執行結束,返回值0表示正常退出
            if (process.waitFor() != 0) {
                log.error("[執行本地Command命令失敗] [Command:{}]", command);
                return new byte[0];
            }
            // 判斷生成的圖片是否存在
            File file = FileUtils.getFile(filePath);
            if (!file.exists()) {
                log.error("[本地檔案\"{}\"不存在]", file.getName());
                return new byte[0];
            }
            // 將快照圖片生成位元組陣列
            byte[] bytes = IOUtils.toByteArray(new FileInputStream(file));
            log.info("[圖片生成結束] [圖片大小:{}KB]", bytes.length / 1024);
            return bytes;
        } catch (IOException | InterruptedException e) {
            log.error("[圖片生成失敗]", e);
        } finally {
            FileUtils.deleteQuietly(FileUtils.getFile(filePath));
        }
        return new byte[0];
    }
}

上面工具類,通過構造方法初始化了命令包路徑,呼叫parseUrl()方法對URL中含有的&符號做了替換,最核心的命令執行,採用Process物件完成,最後輸出到臨時目錄下的圖片檔案。這就是phantomjs對Web訪問頁的圖片生成流程。

其中,Process物件底層呼叫的其實就是ProcessBuilder

public Process exec(String[] cmdarray, String[] envp, File dir)
    throws IOException {
    return new ProcessBuilder(cmdarray)
        .environment(envp)
        .directory(dir)
        .start();
}

ProcessBuilder會呼叫ProcessImpl的許多底層native方法完成URL訪問與圖片生成。

測試方法:

public static void main(String[] arg) throws IOException {
    String url = "https://www.cnblogs.com/ason-wxs/";
    PhantomTools phantomTools = new PhantomTools();
    String parsedUrl = phantomTools.parseUrl(url);
    byte[] byteImg = phantomTools.create(parsedUrl);
    File descFile = new File(FileUtils.getTempDirectoryPath() + "test.png");
    FileUtils.touch(descFile);
    FileUtils.writeByteArrayToFile(descFile, byteImg);
}