使用python 3.5
我需要找到存储在1997-2003年老式Windows .doc文件中的特定文本,并将其转储到csv中。我的约束是:
a)文档文件位于压缩存档中:我无法写入磁盘/我需要在内存中工作
b)我需要使用正则表达式查找特定文本,因此需要将文档转换为.txt
理想情况下,我可以使用zipfile读取文件,将数据传递到某些doc-to-txt转换器(例如textract),然后在txt上使用正则表达式。可能看起来像
import zipfile
import textract
import re
with zipfile.ZipFile(zip_archive, 'r') as f:
for name in f.namelist():
data = f.read(name)
txt = textract.process(data).decode('utf-8')
#some regex on txt
这当然是行不通的,因为textract(以及任何其他doc-txt转换器)的参数是文件路径,而“数据”是字节。使用“名称”作为自变量会给出MissingFileError,这可能是因为zip归档文件没有目录结构,而只是文件名模拟了路径。
是否有任何方法可以仅在内存中通过压缩的doc文件进行正则表达式,而无需提取文件(并因此将它们写入磁盘)?
使用文件而不写入物理驱动器
[在大多数情况下,必须首先解压缩zip中的文件才能进行处理。但这可以在内存中完成。障碍是如何调用仅采用映射文件系统路径作为参数的实用程序来处理压缩文件中的文本,而无需写入物理驱动器。
内部textract
调用一个命令行实用程序(反字),该实用程序执行实际的文本提取。因此,解决此问题的方法通常可以应用于需要通过文件系统路径访问zip内容的其他命令行工具。
以下是解决文件限制的几种可能的解决方案:
sudo
,但是可以自动进行。fuse-zip
。tempfile
模块。 (最简单的)docx2txt
是另一个Python模块,但看起来它将仅处理.docx文件(顾名思义),而不处理旧的Word .doc文件。为什么我要做所有这些工作?我实际上发现这对我自己的项目之一很有用。
[1)RAM驱动器
如果tempfile
不满足文件约束目标,并且您想确保该工具使用的所有文件都在RAM中,那么创建RAM驱动器是一个不错的选择。该工具完成后应卸载驱动器,这将删除其存储的所有文件。
该选项的一个加号是Linux系统都本机支持此功能。它不会引起任何其他软件依赖关系;至少对于Linux,Windows可能需要ImDisk。
这些是Linux上相关的bash命令:
$ mkdir ./temp_drive
$ sudo mount -t tmpfs -o size=512m temp_drive ./temp_drive
$
$ mount | tail -n 1 # To see that it was mounted.
$ sudo umount ./temp_drive # To unmount.
在MacOS上:
$ diskutil erasevolume HFS+ 'RAM Disk' `hdiutil attach -nomount ram://1048576 `
$ # 512M drive created: 512 * 2048 == 1048576
在Windows上:
在Windows上,您可能必须使用第三方应用程序,例如ImDisk:
为了使过程自动化,此简短脚本会提示用户输入他们的sudo密码,然后调用mount
创建RAM驱动器:
import subprocess as sp
import tempfile
import platform
import getpass
ramdrv = tempfile.TemporaryDirectory()
if platform.system() == 'Linux':
sudo_pw = getpass.getpass("Enter sudo password: ")
# Mount RAM drive on Linux.
p = sp.Popen(['sudo', '-S', 'bash', '-c',
f"mount -t tmpfs -o size=512m tmpfs {ramdrv.name}"],
stderr=sp.STDOUT, stdout=sp.PIPE, stdin=sp.PIPE, bufsize=1,
encoding='utf-8')
print(sudo_pw, file=p.stdin)
del sudo_pw
print(p.stdout.readline())
elif platform.system() == 'Darwin':
# And so on...
您的应用程序使用的任何GUI软件包都可能具有密码对话框,但getpass
对于控制台应用程序效果很好。
要访问RAM驱动器,请像安装系统中的任何其他文件一样使用安装在其上的文件夹。向其中写入文件,从中读取文件,创建子文件夹等。
2)挂载Zip文件
如果Zip文件可以安装在OS文件系统上,则其文件将具有可传递到textract
的路径。这可能是最好的选择。
对于Linux,运行良好的实用程序是fuse-zip
。下面的几行内容将其安装,并安装一个zip文件。
$ sudo apt-get install fuse-zip
...
$ mkdir ~/archivedrive
$
$ fuse-zip ~/myarchive.zip ~/archivedrive
$ cd ~/archivedrive/myarchive # I'm inside the zip!
从Python创建临时安装点,安装zip,提取文本,然后卸载zip:
>>> import subprocess as sp, tempfile, textract
>>>
>>> zf_path = '/home/me/marine_life.zip'
>>> zipdisk = tempfile.TemporaryDirectory() # Temp mount point.
>>>
>>> cp = sp.run(['fuse-zip', zf_path, zipdisk.name]) # Mount.
>>> cp.returncode
0
>>> all_text = textract.process(f"{zipdisk.name}/marine_life/octopus.doc")
>>>
>>> cp = sp.run(['fusermount', '-u', zipdisk.name]) # Unmount.
>>> cp.returncode
0
>>> del zipdisk # Delete mount point.
>>> all_text[:88]
b'The quick Octopuses live in every ocean, and different species have\n
adapted to different'
>>>
>>> # Convert bytes to str if needed.
>>> as_string = all_text.decode('latin-1', errors='replace')
使用此方法的一大优点是,不需要使用sudo
来安装存档-无需提示输入密码。唯一的缺点是它为项目增加了依赖性。可能不是主要问题。使用subprocess.run()
可以轻松实现自动化的安装和卸载。
我相信Linux发行版的默认配置允许用户无需使用sudo
即可安装Fuse文件系统;但这需要针对支持的目标进行验证。
对于Windows,ImDisk也可以装载档案,并具有命令行界面。因此,可以自动支持Windows。 XML方法和这种方法都很好,因为它们直接从zip文件中获取信息,而无需执行将其写出到文件中的额外步骤。
关于字符编码:我在示例中假设,早于2006年的旧东欧Word文档可能会使用'utf-8'以外的其他编码(iso-8859-2,latin-1,windows-1250,西里尔字母,等等。)。您可能需要尝试一下,以确保将每个文件正确转换为字符串。
链接:
3)tempfile.NamedTemporaryFile
此方法不需要任何特殊权限。它应该工作。但是,不能保证它创建的文件仅在内存中。
如果担心的是您的工具会将文件过多地填充到用户的驱动器中,则此方法可以避免这种情况。临时文件会可靠地自动删除。
一些用于创建NamedTemporaryFile
,打开zip并将其提取到文件,然后将其路径传递到textract
的示例代码。
>>> zf = zipfile.ZipFile('/temp/example.docx')
>>> wf = zf.open('word/document.xml')
>>> tf = tempfile.NamedTemporaryFile()
>>>
>>> for line in wf:
... tf.file.write(line)
>>>
>>> tf.file.seek(0)
>>> textract.process(tf.name)
# Lines and lines of text dumped to screen - it worked!
>>> tf.close()
>>>
>>> # The file disappears.
您可以反复使用NamedTemporaryFile
重设相同的tf.seek(0)
对象来重置其位置。
在完成处理之前,请不要关闭文件。当您关闭它时,它将消失。 NamedTemporaryFile
的实例在关闭时会自动删除,其引用计数将变为0,或者您的程序退出。
如果您要拥有一个确保在程序完成后消失的临时文件夹,则为tempfile.TemporaryDirectory
。
在同一模块中,tempfile.SpooledTemporaryFile
是内存中存在的文件。但是,很难找到这些文件的路径(我们只知道这些文件的文件描述符)。而且,如果您确实找到了检索路径的好方法,则textract
无法使用该路径。
textract
在单独的进程中运行,但它继承了父级的文件句柄。这就是使得可以在两者之间共享这些临时文件的原因。
[4)通过XML提取Word.docx文本
[此方法试图通过在Python内完成工作或使用不需要FS路径的其他工具来消除对第三方工具的需求。
zip文件中的.docx文件也是包含XML的zip文件。 XML是文本,可以用正则表达式原始解析,也可以先传递给XML阅读器。
Python模块,docx2txt
与下面的第二个示例具有几乎相同的功能。我查看了它的源代码,它以zip格式打开Word文档,并使用XML解析器获取文本节点。出于与这种方法相同的原因,它无法正常工作。
下面的两个示例直接从.docx存档中读取文件-该文件未提取到磁盘。
如果要将原始XML文本转换为字典和列表,则可以使用xmltodict
:
import zipfile
import xmltodict
zf = zipfile.ZipFile('/temp/example.docx')
data = xmltodict.parse(zf.open('word/document.xml'))
some_text = data['w:document']['w:body']['w:p'][46]['w:r']['w:t']
print(some_text)
由于XML元素的复杂嵌套结构,我发现这种格式有点笨拙,它没有给您XML读取器在定位节点方面的优势。
使用xml.etree.ElementTree
,XPATH表达式可以一次提取所有文本节点。
import re
import xml.etree.ElementTree as ET
import zipfile
_NS_DICT = {'w': 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'}
def get_docx_text(docx_path):
"""
Opens the .docx file at 'docx_path', parses its internal document.xml
document, then returns its text as one (possibly large) string.
"""
with zipfile.ZipFile(docx_path) as zf:
tree = ET.parse(zf.open('word/document.xml'))
all_text = '\n'.join(n.text for n in tree.findall('.//w:t', _NS_DICT))
return all_text
如上所述使用xml.etree.ElementTree
模块,仅用几行代码即可提取文本。
在get_docx_text()
中,此行捕获所有文本:
all_text = '\n'.join(n.text for n in tree.findall('.//w:t', _NS_DICT))
字符串:'.//w:t'
是一个XPATH表达式,它告诉模块选择Word文档的所有t
(文本)节点。然后列表推导将所有文本连接起来。
一旦从get_docx_text()
返回了文本,就可以应用正则表达式,逐行对其进行迭代,或者执行任何所需的操作。示例re
表达式将获取所有带括号的短语。
链接
保险丝文件系统:https://github.com/libfuse/libfuse
zip-fuse手册页:https://linux.die.net/man/1/fuse-zip
MacOS保险丝:https://osxfuse.github.io/
ImDisk(Windows):http://www.ltr-data.se/opencode.html/#ImDisk
RAM驱动器软件列表:https://en.wikipedia.org/wiki/List_of_RAM_drive_software
MS docx文件格式:https://wiki.fileformat.com/word-processing/docx/
xml.ElementTree文档:https://docs.python.org/3/library/xml.etree.elementtree.html?highlight=xml%20etree#module-xml.etree.ElementTree
XPATH:https://docs.python.org/3/library/xml.etree.elementtree.html?highlight=xml%20etree#elementtree-xpath
XML示例从https://etienned.github.io/posts/extract-text-from-word-docx-simply/中借鉴了一些想法:>