不解压直接读取zip压缩包

我们知道Power Query能够连接很多不同格式的数据源,但同时还有很多是不能直接支持的,比如word文档、压缩包等等。
有这样一种场景:源数据来自于网络或者远程服务器,每天更新,文件格式是zip,压缩包内有csv。
如果不能直接读取zip,那么我们需要每天把zip下载下来,解压后再导入Power Query,非常麻烦,有什么办法可以解决这个问题呢?

方法一:转换二进制数据流

对于电脑来说,任何文件都是二进制的数字,而M语言中提供了大量的binary类函数,所以我们可以利用函数来间接实现不解压直接读取zip压缩包。
代码来源于Mark White,涉及非常复杂的二进制数据流知识,我也看不懂。所以我稍微改了一下,封装成unzip的自定义函数,新建一个名为unzip的空查询,把下面代码复制粘贴到高级编辑器,需要用的时候直接调用自定义函数=unzip("C:\路径.zip")就行了。

(path) =>
let
    Header = BinaryFormat.Record([
        MiscHeader = BinaryFormat.Binary(14),
        BinarySize = BinaryFormat.ByteOrder(BinaryFormat.UnsignedInteger32, ByteOrder.LittleEndian),
        FileSize   = BinaryFormat.ByteOrder(BinaryFormat.UnsignedInteger32, ByteOrder.LittleEndian),
        FileNameLen= BinaryFormat.ByteOrder(BinaryFormat.UnsignedInteger16, ByteOrder.LittleEndian),
        ExtrasLen  = BinaryFormat.ByteOrder(BinaryFormat.UnsignedInteger16, ByteOrder.LittleEndian)    
    ]),
 
    HeaderChoice = BinaryFormat.Choice(
        BinaryFormat.ByteOrder(BinaryFormat.UnsignedInteger32, ByteOrder.LittleEndian),
        each if _ <> 67324752
            then BinaryFormat.Record([IsValid = false, Filename=null, Content=null])
            else BinaryFormat.Choice(
                    BinaryFormat.Binary(26),
                    each BinaryFormat.Record([
                        IsValid  = true,
                        Filename = BinaryFormat.Text(Header(_)[FileNameLen]),
                        Extras   = BinaryFormat.Text(Header(_)[ExtrasLen]),
                        Content  = BinaryFormat.Transform(
                            BinaryFormat.Binary(Header(_)[BinarySize]),
                            (x) => try Binary.Buffer(Binary.Decompress(x, Compression.Deflate)) otherwise null
                        )
                        ]),
                        type binary
                )
    ),
 
    ZipFormat = BinaryFormat.List(HeaderChoice, each _[IsValid] = true),
 
    Entries = List.Transform(
        List.RemoveLastN( ZipFormat(File.Contents(path)), 1),
        (e) => [FileName = e[Filename], Content = e[Content] ]
    )
in
    Table.FromRecords(Entries)

效果如下图,测试文件中包含了xls,xlsx,csv三个文件,三种格式都可以支持,解析出来还是binary,然后再根据格式使用Excel.WorkbookCsv.Document解析成表即可:

不仅是zip,其他的文件如doc,ppt,pdf等,理论上也都可以通过二进制读出来。

方法二:在PBID中使用R语言

可以看到用Power Query的M语言,因为没有直接的函数支持,导致虽然能实现效果,但过程实在太复杂,反正我是看不懂。
而在Python和R中一两行代码就可以搞定,所以只要是R可以,就可以在Power BI Desktop中使用R.Execute来调用R。
首先得安装R,这个就不多说了。假设在桌面上有个test.zip,压缩包内有个test.csv,我们需要读取csv的数据。源数据长这样:

先来一个不依赖包的写法:
R语言代码:

unzip("C:/Users/rages/Desktop/test.zip")
read.csv("test.csv",header=F)

header=F表示不使用第一行作为标题,header=T则相反。

把R语言写到PBID的高级编辑器中就有两个地方需要注意,一个是文本引号的转义,另一个是数据需要以dataframe数据框的形式返回。

let
    unzip=R.Execute("
        unzip(""C:/Users/rages/Desktop/test.zip"")
        data<-data.frame(read.csv(""test.csv"",header=F))
        "){0}[Value]
in
    unzip

在R中还有一个叫rio的包,可以不解压直接读取压缩包,首次使用需要install.packages('rio')安装rio包。
R语言中代码为:

library(rio)
import("c:/Users/rages/Desktop/test.zip")

代码很简洁,第一步加载rio包,第二步直接读取文件路径。

改写到PBID中为:

let
    unzip=R.Execute("
        library(rio)
        data<-data.frame(import(""C:/Users/rages/Desktop/test.zip""))
        "){0}[Value]
in
    unzip

15 Replies to “不解压直接读取zip压缩包”

  1. 如果一个压缩包中含有多个压缩包,多个压缩包含有csv文件,应该怎么样调用r解压,thanks

    1. 解决中文乱码问题,方法如下:
      将 Filename = BinaryFormat.Text(Header(_)[FileNameLen]),
      替换为 Filename = BinaryFormat.Text(Header(_)[FileNameLen], TextEncoding.Windows),
      将 (e) => [FileName = e[Filename], Content = e[Content] ]
      替换为 (e) => [FileName = Text.FromBinary(Text.ToBinary(e[Filename], 1252), 936), Content = e[Content] ]

  2. 压缩包里面就只有一个文件的话,用第一个方法解压是不成功的。多个文件是可以解压的

    1. 确实,压缩包里只有一个文件,解压出来的binary是空的,0字节。
      求教,是怎么回事啊?

  3. 老师你好,通过这个方法,我将DOCX文件作为ZIP格式打开,成功在里面找到文档中的表格。然后解出对应的数据,也就是说,可以解决用Excel直接获取word文件的内表格数据的问题。非常感谢老师的代码。
    但是当我换到DOC文件进行这个操作的时候,就发现doc文件的编写并不一样,docx其实就是一个打包文件,里面包含有xml格式的表格。但doc中没有,根据本文中,老师提及,其实DOC文件还是可以以二进制编码的方式解读其中所包含的信息的,只要转码到可读就行。但是我对其中的几个BinaryFormat公式无法理解,不是很懂老师所写的含义,希望老师能补充一个将DOC文件转码到可读的代码,这将为我解决很大的一个难题。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注