使用 Java 携手 SpringBoot + PhantomJS + ECharts 在服务端生成图表并存为图片

目录

1、简介

 2、PhantomJS 下载安装

3、PhantomJS 测试脚本

4、Echarts 环境配置

脚本使用

5、SpringBoot 调用 PhantomJS

5.1、在 pom.xml 引入 freemarker,用于解析 ftl 模板文件。

 5.2、在 templates 目录下 创建 echarts 目录,并放入 EChartsLineOption.ftl 模板文件,可通过 ftl 模板调整参数完成自定义图片。示例为折线图,有需要别的图表类型自行更换 Option 内容即可。

 5.3、创建 EchartsUtil 工具类,编写 generateEChartsBase64() 方法,用于生成 base64 编码图片。

5.4、创建 RESTUtil 工具类,用于发送 Http 请求。

 5.5、创建 FreemarkerUtil 工具类,用于读取解析 ftl 模板文件。

5.6、创建 Base64Util 工具类,将生成的 bae64 转为 java.io.File 文件

 5.7、编写 EChartsService 服务层业务代码,调用工具类生成图片。


1、简介

PhantomJS 是一个不需要浏览器的富客户端

        官方介绍:PhantomJS是一个基于 WebKit 的服务器端JavaScript API。它全面支持web而不需浏览器支持,支持各种Web标准:DOM处理,CSS选择器, JSON,Canvas,和SVG。PhantomJS常用于页面自动化,网络监测,网页截屏,以及无界面测试等

        通常我们使用PhantomJS作为爬虫工具。传统的爬虫只能单纯地爬取html的代码,对于js渲染的页面,就无法爬取,如Echarts统计图。而PhantomJS正可以解决此类问题。

        我们可以这么理解 PhantomJS,PhantomJS是一个无界面、可运行脚本的谷歌浏览器。

 2、PhantomJS 下载安装

        PhantomJS安装非常简单,直接在官网 http://phantomjs.org/download.html 下载最新的安装包, 安装包有Windows,Mac OS X, Linux 64/32 bit,选择对应的版本下载解压即可使用,在下载包里有个example文件夹,里面对应了许多示例供参考。

        将下载后解压的文件夹放在 D:\Program Files\PhantomJS,为方便使用,我们将 PhantomJS 添加至环境变量中,并将下载到的安装包放在对应的目录下。

Windows:
右键我的电脑
->属性
->高级系统设置
->高级
->环境变量
->用户变量/系统变量
-> 在 Path 添加 D:\Program Files\PhantomJS\bin\

Linux:
vi /etc/profile
export PATH=$PATH:/usr/local/phantomjs/bin

3、PhantomJS 测试脚本

打开 CMD,进入 example 目录,运行命令 phantomjs hello.js, 输出 “Hello World” 则代表配置成功。

4、Echarts 环境配置

生成图片的核心脚本在于 echarts-convert.js ,同时结合 echarts.min.js、jquery.min.js、china.js 三个脚本来生成图片。

由于 js 源码内容过长,我已将 js 脚本及其项目源码放在 GitHub、Gitee、Coding 等代码开源平台,文末附带源码链接,有需要的可自行下载。

将脚本下载完后,放在 D:\Program Files\echartsconvert,以便于 PhantomJS 调用脚本生成图片。

脚本使用

在 `echarts-convert.js` 同级目录下,运行命令 ` phantomjs echarts-convert.js -s `,如果控制台出现"echarts-convert server start success. [pid]=xxxx"则表示启动成功,默认端口 9090,关闭 CMD 则关闭脚本程序。

Linux的脚本启动样例:

nohup phantomjs echarts-convert.js -s >> /opt/software/phantomjs/log/output.log 2>&1 &

5、SpringBoot 调用 PhantomJS

5.1、在 pom.xml 引入 freemarker,用于解析 ftl 模板文件。
<!-- 解析ftl模板文件 -->
<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
    <version>2.3.31</version>
</dependency>
 5.2、在 templates 目录下 创建 echarts 目录,并放入 EChartsLineOption.ftl 模板文件,可通过 ftl 模板调整参数完成自定义图片。示例为折线图,有需要别的图表类型自行更换 Option 内容即可。
{
backgroundColor: '#000000',
color: ['#FEE108', '#9e9e9e'],
title: {
text: '${title}',
left: 25,
top: 10,
textStyle: {
color: '#FFFFFF',
fontSize: 14,
fontWeight: 'normal'
}
},
grid: {
top: '60px',
left: '60px',
right: '80px',
bottom: '80px'
},
xAxis: {
type: 'category',
axisLine: {
onZero: false,
lineStyle: {
color: '#FFFFFF'
}
},
splitLine: {
show: false
},
axisTick: {
inside: true
},
axisLabel: {
color: '#FFFFFF'
},
data: ${categories}
},
yAxis: {
type: 'value',
position: 'right',
splitLine: {
show: false
},
axisLine: {
lineStyle: {
color: '#FFFFFF'
}
},
axisLabel: {
color: '#FFFFFF',
formatter:function (value, index) {
return value.toFixed(0);
}
},
min: function (value, index) {
return value.min - 1;
},
max: function (value, index) {
return value.max + 1;
}
},
series: [
{
type: 'line',
symbol: 'none',
data: ${values},
},{
type: 'line',
markLine: {
symbol: ['none', 'none'],
label: {
show: false,
fontSize: 0
},
data: [{
yAxis: 0,
lineStyle: {
color: '#9e9e9e'
}
}]
}
}],
graphic: [{
type: 'text',
right: '48',
top: '10',
style: {
fill: '#FFFFFF',
text: '雪雨天气',
font: '14px sans-serif',
}
},{
type: 'text',
right: '70',
bottom: '80',
style: {
fill: '#333333',
text: '天气为王',
font: '48px sans-serif',
}
}]
}
 5.3、创建 EchartsUtil 工具类,编写 generateEChartsBase64() 方法,用于生成 base64 编码图片。
package louis.echarts.util;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

/**
* @Description ECharts 工具类
* @Author Louis
* @Date 2022/07/10 16:36
*/
@Slf4j
public final class EChartsUtil {

    private static final String SUCCESS_CODE = "1";

    /**
    * @Description 生成ECharts图片的Base64编码
    * @Param [option]
    * @Return java.lang.String
    * @Author Louis
    * @Date 2022/07/10 16:40
    */
    public static String generateEChartsBase64(String phantomjsUrl, String option) {
        // 手动拼接option示例
        // String option = "{title:{text:'ECharts 示例'},tooltip:{},legend:{data:['销量']},xAxis:{data:['衬衫','羊毛衫','雪纺衫','裤子','高跟鞋','袜子']},yAxis:{},series:[{name:'销量',type:'bar',data:[5,20,36,10,10,20]}]}";
        if (!StringUtils.hasText(option)) {
            return null;
        }
        // 替换掉换行符,将双引号替换为单引号
        option = option.replaceAll("\\r\\n", "").replaceAll("\"", "'");
        // 将option字符串作为参数发送给echartsConvert服务器
        String result = RESTUtil.sendPostRequest(phantomjsUrl, "opt=" + option);
        // 解析echartsConvert响应
        JSONObject response = JSON.parseObject(result);
        // 如果echartsConvert正常返回
        if (SUCCESS_CODE.equals(response.getString("code"))) {
            return response.getString("data");
        } else {
            // 未正常返回
            log.error("ECharts Convert 服务器异常:{}", response);
        }
        return null;
    }

}
5.4、创建 RESTUtil 工具类,用于发送 Http 请求。
package louis.echarts.util;

import cn.hutool.http.HttpUtil;

/**
 * @ClassName RESTUtil
 * @Description 发送REST请求工具类
 * @Author Louis
 * @Date 2022/7/10 16:14
 */
public final class RESTUtil {

    public static String sendPostRequest(String url, String params) {
        return HttpUtil.createPost(url).body(params).execute().body();
    }

}
 5.5、创建 FreemarkerUtil 工具类,用于读取解析 ftl 模板文件。
package bots.util;

import freemarker.template.Configuration;
import freemarker.template.Template;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.util.Map;

/**
* @Description Freemarker 工具类
* @Author Louis
* @Date 2022/07/10 17:16
*/
@Slf4j
public final class FreemarkerUtil {

    // 类加载器,用于获取项目目录
    private static final ClassLoader CLASS_LOADER = FreemarkerUtil.class.getClassLoader();
    // 模板存放的目录
    private static final String BASE_PATH = "templates/echarts";

    /**
    * @Description 加载模板并生成ECharts的option数据字符串
    * @Param [templateFileName, data]
    * @Return java.lang.String
    * @Author Louis
    * @Date 2022/07/10 17:16
    */
    public static String generate(String templateFileName, Map<String, Object> data) {
        Configuration configuration = new Configuration(Configuration.VERSION_2_3_31);
        // 设置默认编码
        configuration.setDefaultEncoding("UTF-8");
        // 将 data 写入模板并返回
        try {
            StringWriter writer = new StringWriter();
            // 设置模板所在目录,设置目录打成jar包后无法读取,所以使用类加载器
            // configuration.setDirectoryForTemplateLoading(new File(BASE_PATH));
            configuration.setClassLoaderForTemplateLoading(CLASS_LOADER, BASE_PATH);
            // 生成模板对象
            Template template = configuration.getTemplate(templateFileName);
            template.process(data, writer);
            writer.flush();
            return writer.getBuffer().toString();
        } catch (Exception e) {
            log.error("解析模板异常:{}", e);
        }
        return null;
    }

}
5.6、创建 Base64Util 工具类,将生成的 bae64 转为 java.io.File 文件
package louis.echarts.util;

import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.Base64;

/**
* @Description Base64 工具类
* @Author Louis
* @Date 2022/07/10 17:24
*/
@Slf4j
public final class Base64Util {

    /**
    * @Description 将Base64字符串转为文件对象
    * @Param [base64]
    * @Return java.io.File
    * @Author Louis
    * @Date 2022/07/10 17:25
    */
    public static File base64ToFile(String base64) {
        try {
            // Base64解码
            byte[] b = Base64.getDecoder().decode(base64);
            for(int i = 0; i < b.length; ++i ){
                if(b[i] < 0){
                    //调整异常数据
                    b[i] += 256;
                }
            }
            // 对文件重命名,设定为当前系统时间的毫秒数加UUID
            String newFileName = System.currentTimeMillis() + "-" + CommonUtil.randomUUID() + ".png";
            // 放在本地临时文件目录
            String localFilePath = String.format("%stemp%s%s%s%s%s%s", File.separator, File.separator, DateUtil.currentYear(), File.separator, DateUtil.currentMonth(), File.separator, DateUtil.currentDay());
            File filePath = new File(localFilePath);
            if (!filePath.exists()) {
                // mkdirs(): 创建多层目录
                filePath.mkdirs();
            }
            // 文件全限定名
            String path = localFilePath + File.separator + newFileName;
            // 将数据通过流写入文件
            OutputStream out = new FileOutputStream(path);
            out.write(b);
            out.flush();
            out.close();
            return new File(path);
        } catch (Exception e) {
            log.error(e.toString());
        }
        return null;
    }

}
 5.7、编写 EChartsService 服务层业务代码,调用工具类生成图片。
package louis.echarts.service;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import louis.echarts.util.Base64Util;
import louis.echarts.util.EChartsUtil;
import louis.echarts.util.FreemarkerUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.File;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;

/**
 * @Description ECharts 图表服务层
 * @Author Louis
 * @Date 2022/07/10 17:14
 */
@Slf4j
@Service
public class EChartsService {

    // PhontomJS 服务网址
    @Value("${phantomjs.url}")
    private String phantomjsUrl;

    /**
    * @Description 生成图表
    * @Return java.io.File
    * @Author Louis
    * @Date 2022/07/10 17:30:19
    */
    public File generateEcharts(){
        // 数据参数,可以自己通过API查询json数据
        String title = "上海天气折线图";
        List<String> categories = Arrays.asList("2022-07-10", "2022-07-11", "2022-07-12", "2022-07-13", "2022-07-14", "2022-07-15", "2022-07-16", "2022-07-17", "2022-07-18", "2022-07-19", "2022-07-20", "2022-07-21", "2022-07-22");
        List<String> values = Arrays.asList("38", "33", "33", "31", "30", "32", "34", "37", "38", "37", "36", "38", "37");
        // 模板参数
        HashMap<String, Object> data = new HashMap<>();
        data.put("title", title);
        data.put("categories", JSON.toJSONString(categories));
        data.put("values", JSON.toJSONString(values));
        // 调用模板加载数据
        String option = FreemarkerUtil.generate("EChartsLineOption.ftl", data);
        // 生成图片的base64编码
        String base64 = EChartsUtil.generateEChartsBase64(phantomjsUrl, option);
        // 将base64转为文件
        return Base64Util.base64ToFile(base64);
    }

}

 5.8、运行 SpringBoot 核心启动类,注入 EChartsService, 调用 EChartsService 服务层的 generateEcharts() 方法。运行完毕后,打开系统文件资源管理器,发现在 D:\Temp\2022\7\10 目录下已经生成一张 .png 图片,可通过 ftl 模板调整参数完成自定义图片。generateEcharts() 方法返回的 java.io.File 对象可直接用于业务文件流操作使用。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Selenium是一个用于自动化浏览器操作的工具,而PhantomJS是一个无界面的浏览器。它们可以结合使用来进行爬虫操作。使用Selenium和PhantomJS,你可以模拟用户在浏览器中的操作,例如点击、填写表单等。这样就可以实现爬取网页数据的功能。 安装Selenium和PhantomJS的步骤如下: 1. 首先确保你已经安装了Python 3.5以及pip。 2. 使用pip安装Selenium,运行以下命令: ``` pip install selenium ``` 3. 下载PhantomJS并解压到Python的Scripts目录中。具体下载链接请参考中提供的系统环境下载说明。 安装完成后,你就可以开始使用Selenium和PhantomJS来实现爬虫功能了。你可以参考中提供的示例代码来学习如何使用Selenium和PhantomJS进行网页爬取。 此外,关于使用Selenium和PhantomJS进行数据爬取的方法,你可以按照以下步骤进行: 1. 使用Selenium和PhantomJS打开目标网页,模拟用户在浏览器中的操作来获取数据。 2. 如果网页使用了ajax接口来加载数据,你可以通过分析网页源代码或使用开发者工具来找到数据来源的接口,并发送请求获取数据。 3. 如果你只需要获取列表页的数据,可以在第一次爬取时只爬取列表页,将详情页的URL放到每条数据中心。这样可以避免增加请求数量。 4. 如果需要获取详情页的数据,可以在第二次爬取时从数据库中提取详情页URL,并使用Selenium和PhantomJS来爬取详情页的数据。 希望这些信息能对你有所帮助。如果你有其他问题,请随时提问。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值