使用Python提取文件名中包含无效字符的文件

5

我使用Python的zipfile模块来解压缩.zip文件(以http://img.dafont.com/dl/?f=akvaleir为例)。

f = zipfile.ZipFile('akvaleir.zip', 'r')
for fileinfo in f.infolist():
    print fileinfo.filename
    f.extract(fileinfo, '.')

它的输出:

Akval�ir_Normal_v2007.ttf
Akval�ir, La police - The Font - Fr - En.pdf

解压后,由于文件名中存在无效编码字符,两个文件均无法访问。问题在于zipfile模块没有指定输出文件名的选项。

然而,“unzip akvaleir.zip”可以很好地转义文件名:

root@host:~# unzip akvaleir.zip 
Archive:  akvaleir.zip
  inflating: AkvalВir_Normal_v2007.ttf  
  inflating: AkvalВir, La police - The Font - Fr - En.pdf  

我尝试在我的Python程序中捕获 "unzip -l akvaleir.zip" 的输出,这两个文件名是:

Akval\xd0\x92ir_Normal_v2007.ttf
Akval\xd0\x92ir, La police - The Font - Fr - En.pdf

如何在不捕获“unzip -l akvaleir.zip”的输出的情况下获得正确的文件名,就像unzip命令所做的那样?
3个回答

8

花了一些时间,但我认为我找到了答案。

我假设这个词应该是Akvaléir。我在法语页面上找到了相关描述。当我使用您的代码片段时,我得到了一个字符串,如下:

>>> fileinfo.filename
'Akval\x82ir, La police - The Font - Fr - En.pdf'
>>> 

在UTF8、Latin-1、CP-1251或CP-1252编码下都无法工作。然后我发现CP863是一种可能的加拿大编码,所以这可能来自于加拿大法语区。

>>> print unicode(fileinfo.filename, "cp863").encode("utf8")
Akvaléir, La police - The Font - Fr - En.pdf
>>> 

然而,我随后阅读了Zip文件格式规范,其中提到:

ZIP格式历史上仅支持原始IBM PC字符编码集,通常称为IBM代码页437。

...

如果设置了通用位11,则文件名和注释必须支持Unicode标准版本4.1.0或更高版本,并使用UTF-8存储规范定义的字符编码形式。

测试结果与加拿大代码页相同。

>>> print unicode(fileinfo.filename, "cp437").encode("utf8")
Akvaléir, La police - The Font - Fr - En.pdf
>>>

我没有Unicode编码的zip文件,也不打算创建一个来查找,因此我假设所有zip文件都具有cp437编码。
import shutil
import zipfile

f = zipfile.ZipFile('akvaleir.zip', 'r')
for fileinfo in f.infolist():
    filename = unicode(fileinfo.filename, "cp437")
    outputfile = open(filename, "wb")
    shutil.copyfileobj(f.open(fileinfo.filename), outputfile)

在我的Mac上,这样做

 109936 Nov 27 01:46 Akvale??ir_Normal_v2007.ttf
  25244 Nov 27 01:46 Akvale??ir, La police - The Font - Fr - En.pdf

自动补全为哪个选项

ls Akvale\314\201ir

并在我的文件浏览器中以漂亮的“é”显示。


2
是的,您必须事先了解源编码。ZIP格式绝对不包含任何信息,可以从中推断出文件名使用的编码方式。虽然Mac和大多数现代Linux系统明智地使用UTF-8作为其文件系统和ZIP内部的编码方式,但Windows机器使用系统代码页,这是与语言环境相关且永远不是UTF-8。这真是一个头疼的问题。 - bobince
@dalke,感谢您提供的信息。我该如何在Python程序中检测字符串使用的编码方式? - jack
1
阅读规范并发现ZIP使用cp437或utf-8。有一个标志(第11位)指定了使用哪种编码。我还没有编写代码来检查它。 - Andrew Dalke
filename = unicode(fileinfo.filename, "cp437") 在使用Python3时应该改为 filename = fileinfo.filename.encode('cp437').encode('???') - Alfred Huang

7

使用open方法代替extract方法,将生成的伪文件保存到磁盘上,可以选择任何名称,例如使用shutil.copyfileobj


@Alex,谢谢,它有效。你知道如何像解压缩一样在Python中转义无效的文件名吗? - jack
3
除非你已经彻底检查了所有的文件名,否则请勿使用extractextractall,因为它可能会在你的文件系统中的任何地方释放文件。 +1。 - bobince
@jack,看起来他们正在使用utf-8进行编码,但我不知道zip文件本身使用的编码方式——尝试在Python从zipfile中读取文件名时打印repr,我们将看看是否可以猜测出该编码方式(基本上,您将使用它所使用的任何编解码器将文件名解码为Unicode,然后将其编码为utf-8以保存文件)。 - Alex Martelli
或者使用read并将其写入以wb(二进制模式)打开的文件中,这似乎比shutil.copyfileobj更适用于特殊的二进制文件。 - gaborous

2

在使用Docker运行我的应用程序时,我遇到了类似的问题。将以下行添加到Dockerfile中,为我解决了所有问题:

RUN locale-gen en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8

所以,我猜如果你还没有使用Docker,请尝试一下,并确保区域设置已经正确生成并设置好。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接