前言
永远相信美好的事情即将发生
背景
自从上一次尝试使用 EasyExcel 对文件下载接口进行解析从而完成数据的爬取之后,我便放弃了最开始使用的数据接口解析爬取,转而投入到了 Excel 的怀抱(前提是页面要有下载文件的入口)。
然而在后续中的爬取中又出现了新的问题,那就是有些下载源因为数据量过大或其他原因,提供的是 ZIP 的下载,Excel 文件包含在了 ZIP 中,那么我们就不能在使用 EasyExcel 直接去解析,而是要采取新的方法。
思路
关于 ZIP 文件中 Excel 的解析,在我的一番深思熟虑后想到了两种方案
- 下载 ZIP 到某个位置,解压出 Excel 后读取
- 对下载 ZIP 的流进行处理,获取到其中的 Excel 流文件,使用 EasyExcel 进行读取
这两种方法各有各的优劣,前者相对来说实现难度较低(虽然我也不会),但是会占用额外的空间进行存储,就算进行删除操作也是会存在一定的风险;而后者则较为方便,无需下载,但是会比前者的难度稍微高点。这里我们采取的是第二种方式,直接解析ZIP中的流。
历程
Excel及ZIP的创建
对同一个文件来说,从接口获取到的流和从本地读取到的流基本一致,因此为了更直观的看到效果,我们采用了本地创建 ZIP 的方式,后期使用的时候可以更改为直接从接口获取流。
首先我们创建一个 Excel 文件,并将其压缩为 ZIP,Excel 数据如下
EasyExcel的配置
这里大家可以参照我以前的一篇博客 『Java』EasyExcel基础——读 ,只要大的版本号保持一致应该就没什么问题。
实体类及解析器的创建
首先我们需要根据 Excel 的表头创建实体类,这里使用的是 lombok,没有导入的可以先在 idea 中导入一下
/**
* @Description: 测试实体类
* @Author: Am0xil
* @Date: 2020/9/8
**/
@Data
public class TestEntity {
@ExcelProperty(value = "姓名")
private String userName;
@ExcelProperty(value = "性别")
private String gender;
@ExcelProperty(value = "年龄")
private String age;
@ExcelProperty(value = "籍贯")
private String address;
}
然后我们需要创建 **EasyExcel** 的解析器,因为这次只是演示,因此就不在 **Dao** 层添加数据写入的方法,需要的可以手搓一个,也可以参考以前的那篇博客
/**
* @Description: Excel解析器
* @Author: Am0xil
* @Date: 2020/9/9
**/
@Slf4j
public class TestExcelListner extends AnalysisEventListener {
@Override
public void invoke(Object o, AnalysisContext analysisContext) {
log.info(((TestEntity)o).toString());
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
log.info("解析完成");
}
Service层实现
重点就在于实现层,具体的实现方法和之前直接解析 Excel 的过程差不多,只不过多了一步对 ZIP 的解析,首先会将文件转化为 ZipInputStream ,这是一种专门用于读取压缩文件的流,然后会在这个 ZipInputStream 中找到存储我们目标 Excel 的流文件进行解析,从而获取数据,具体代码如下
/**
* @Description: 解析 ZIP 中的 Excel 文件
* @Author: Am0xil
* @Date: 2020/9/8
**/
@Service
public class TestServiceImpl implements TestService {
@Override
public void zipExcelTest() throws Exception {
// 使用 File 进行本地 ZIP 文件的读取
String fileName = "C:\\Users\\King\\Desktop\\测试.zip";
File file = new File(fileName);
// 将 file 文件转换为 ZipInputStream 流,ZipInputStream 为压缩文件输入流,专门用于读取压缩文件
// 如果 Excel 中包含特殊字符,需要手动去调整编码格式,这里我们暂且使用默认的 utf-8
ZipInputStream zipStream = new ZipInputStream(new FileInputStream(file));
// 因为流只可被读取一次的特性,因此我们需要创建一个 ZipEntry 去接收
ZipEntry zipEntry;
// 循环读取 ZIP 文件中的每一个子文件,若文件不为空且文件名为我们需要的那个("测试.xlsx"),则开始解析
while ((zipEntry = zipStream.getNextEntry()) != null){
if (Objects.equals(zipEntry.getName(),"测试.xlsx")){
// 逐字节读取 ZipInputStream 文件,并将结果存储至一个字节数组缓冲区(ByteArrayOutputStream)中
byte[] buffer = new byte[1024];
int len;
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
while ((len = zipStream.read(buffer)) != -1){
byteStream.write(buffer,0,len);
}
// 关闭流
byteStream.close();
// 创建 Excel 解析器对象
TestExcelListner testExcelListner = new TestExcelListner();
ExcelReader excelReader;
// 将之前的 ByteArrayOutputStream 对象转化为 byte数组,然后使用 EasyExcel 进行解析
excelReader = EasyExcel.read(new ByteArrayInputStream(byteStream.toByteArray()), TestEntity.class,testExcelListner).build();
ReadSheet readSheet = EasyExcel.readSheet().build();
excelReader.read(readSheet);
}
}
}
}
执行结果
执行结果如图所示
从图中我们可以看到,这样我们就获取到了 ZIP 中 Excel 的数据
总结
- 在解析过程中一定要对流有着很清晰的认知,在下就是因为对 IO 掌握的不是很熟,导致走了很多弯路
- EasyExcel 的报错不是很直观,一般都会报 “无法获取当前Excel的类型” 这个错误,使用的时候一定多加小心,报错了基本上都是流转换的时候所引发的