获取Zip归档文件中的文件路径,该文件路径可以传递给实用程序进行处理。 -蟒蛇

使用python 3.5

我需要找到存储在1997-2003年老式Windows .doc文件中的特定文本,并将其转储到csv中。我的约束是:

a)doc文件位于压缩存档中:我无法写入磁盘/我需要在内存中工作

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-to-txt转换器)的参数是文件路径,而“数据”是字节。使用“名称”作为参数会给出 MissingFileError ,这可能是因为zip归档文件没有目录结构,而只是文件名模拟路径。

有什么方法可以仅在内存中通过压缩的doc文件进行正则表达式,而无需提取文件(并因此将它们写入磁盘)?

vfshangjitiku 回答:获取Zip归档文件中的文件路径,该文件路径可以传递给实用程序进行处理。 -蟒蛇

在不写入物理驱动器的情况下处理文件

在大多数情况下,必须首先解压缩zip中的文件才能进行处理。但这可以在内存中完成。障碍是如何调用仅以映射文件系统路径作为参数的实用程序来处理压缩文件中的文本,而无需写入物理驱动器。

内部textract调用一个命令行实用程序(反字词)来进行实际的文本提取。因此,解决此问题的方法通常可以应用于需要通过文件系统路径访问zip内容的其他命令行工具。

以下是解决文件限制的几种可能的解决方案:

  1. 安装RAM驱动器。
    • 这很好用,但是需要sudo提示,但这可以自动完成。
  2. 将zip文件安装到文件系统。 (不错的选择)
    • fuse-zip是安装这些文件的良好Linux工具。
  3. 使用tempfile模块。 (最简单的)
    • 确保文件被自动删除。
    • 缺点,文件可能会写入磁盘。
  4. 访问.docx文件中的XML。
    • 可以通过原始XML进行正则表达式,也可以使用XML阅读器。
    • 尽管只有一小部分文件是.docx。
  5. 找到另一个提取器。 (不包括)
    • 我看了看,没找到任何东西。
    • 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发行版的默认配置允许用户挂载Fuse文件系统,而无需使用sudo。但这需要针对支持的目标进行验证。

对于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/

的一些想法
本文链接:https://www.f2er.com/2621953.html

大家都在问