Получите пути к файлам внутри Zip-архива, которые можно передать в утилиты для обработки. - python - PullRequest
1 голос
/ 19 марта 2020

Использование python 3.5

Мне нужно найти указанный c текст, который хранится в старых файлах 1997-2003 windows .do c, и выгрузить его в CSV-файл. Мои ограничения:

a) do c файлы находятся в заархивированном архиве: я не могу записать на диск / мне нужно работать в памяти

b) мне нужно найти конкретные c текст с регулярным выражением, поэтому do c необходимо преобразовать в .txt

В идеале я мог бы прочитать файлы с помощью zipfile, передать данные в какой-нибудь конвертер do c -to-txt (например, textract) и регулярное выражение в текстовом формате. Это может выглядеть как

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 (и любого другого конвертера do c -to-txt) является filepath, в то время как data является байтами , Использование «имени» в качестве аргумента дает MissingFileError , вероятно, потому что zip-архивы не имеют структуры каталогов, только имена файлов, имитирующие пути.

Есть ли какой-либо способ регулярного выражения через zip-файлы do c только в памяти, без извлечения файлов (и, следовательно, записи их на диск)?

1 Ответ

2 голосов
/ 19 марта 2020

Работа с файлами без записи на физический диск

В большинстве случаев файлы внутри zip-файла должны быть извлечены в первую очередь для обработки. Но это можно сделать в памяти. Дорожное препятствие заключается в том, как вызвать утилиту, которая использует только путь сопоставленной файловой системы в качестве аргумента для обработки текста в заархивированных файлах без записи на физический диск.

Внутренне textract вызывает утилиту командной строки (antiword ), который делает фактическое извлечение текста. Таким образом, подход, который решает эту проблему, может быть применен в целом к ​​другим инструментам командной строки, которым необходим доступ к zip-содержимому через путь файловой системы.

Ниже приведено несколько возможных решений, чтобы обойти это ограничение для файлов:

  1. Монтирование RAM-диска.
    • Это работает хорошо, но требует подсказки sudo, но это можно автоматизировать.
  2. Смонтируйте zip-файл в файловую систему. (хороший вариант)
    • Хорошим Linux инструментом для монтажа является fuse-zip.
  3. Используйте модуль tempfile. (самый простой)
    • Обеспечивает автоматическое удаление файлов.
    • Недостаток: файлы могут быть записаны на диск.
  4. Доступ к XML в. DOCX файлы.
    • Может выполнять регулярное выражение через raw XML или использовать читатель XML.
    • Хотя только небольшая часть ваших файлов является .docx.
  5. Найдите еще один экстрактор. (не охвачено)
    • Я посмотрел и ничего не смог найти.
    • docx2txt - это еще один модуль Python, но похоже, что он будет обрабатывать только файлы .docx (как его имя подразумевается), а не старые файлы Word .do c.

Почему я сделал всю эту работу, вы можете спросить. Я действительно нашел это полезным для одного из моих собственных проектов.


1) RAM Drive

Если tempfile не удовлетворяет файл ограничивающие цели, и вы хотите убедиться, что все файлы, используемые инструментом, находятся в ОЗУ, создание ОЗУ является отличным вариантом. После завершения работы инструмент должен отключить диск, что приведет к удалению всех сохраненных файлов.

Плюсом этой опции является то, что все системы Linux поддерживают это изначально. Это не влечет за собой никаких дополнительных программных зависимостей; по крайней мере для Linux, Windows, вероятно, потребуется ImDisk.

Это соответствующие команды bash для Linux:

$ 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 для создания ОЗУ:

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, использовать папку, в которой он смонтирован, как и любой другой файл в системе. Записывать в него файлы, читать из него файлы, создавать подпапки и т. Д. c.


2) Смонтировать Zip-файл

Если Zip-файл может быть смонтирован в файловой системе ОС, тогда его файлы будут иметь пути, которые можно передать в 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-файла без дополнительного этапа записи ее в файл.

Относительно кодировки символов: я сделал предположение в примере в старых документах Word в Восточной Европе, выпущенных до 2006 года, может использоваться некоторая кодировка, отличная от utf-8 (iso-8859-2, latin-1, windows -1250, cyrilli c, et c.). Возможно, вам придется немного поэкспериментировать, чтобы убедиться, что каждый из файлов правильно преобразован в строки.

Ссылки:


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) для сброса его положения.

Не закрывайте файл, пока не закончите с ним. Когда вы закроете его, оно исчезнет sh. Экземпляры NamedTemporaryFile автоматически удаляются при закрытии, их повторный счет становится равным 0 или ваша программа завершает работу.

Опция, если вы хотите иметь временную папку, которая гарантированно исчезнет после завершения вашей программы, будет tempfile.TemporaryDirectory.

В том же модуле tempfile.SpooledTemporaryFile - это файл, который существует в памяти. Однако, путь к ним трудно найти (мы знаем только их файловый дескриптор). И если вы найдете хороший способ получить путь, этот путь не будет использоваться textract.

textract выполняется в отдельном процессе, но он наследует файловые дескрипторы родительского объекта. Это то, что позволяет обмениваться этими временными файлами между двумя.


4) Извлечение текста Word.docx через XML

Этот подход пытается Удалите необходимость использования сторонней утилиты, выполнив работу в Python или воспользовавшись другим инструментом, для которого не требуются пути FS.

.Docx-файлы в zip-файлах также являются zip-файлами, содержащими XML. XML - это текст, который может быть проанализирован в необработанном виде с помощью регулярных выражений или передан читателю XML вначале.

Модуль Python, docx2txt делает почти то же самое, что и во втором примере ниже. Я посмотрел на его источники, и он открывает документ Word в виде zip-файла и использует синтаксический анализатор 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, которое указывает модулю выбрать все t (текст ) узлы документа Word. Затем понимание списка объединяет весь текст.

Получив текст, возвращенный из get_docx_text(), вы можете применять свои регулярные выражения, построчно перебирать его или делать все, что вам нужно. Пример выражения re захватывает все фразы в скобках.


Ссылки

Файловая система Fuse: https://github.com/libfuse/libfuse

Страница справочника на молнии: https://linux.die.net/man/1/fuse-zip

Предохранитель MacOS: https://osxfuse.github.io/

ImDisk (Windows): http://www.ltr-data.se/opencode.html/#ImDisk

Список программного обеспечения привода ОЗУ: https://en.wikipedia.org/wiki/List_of_RAM_drive_software

Формат файла MS docx: https://wiki.fileformat.com/word-processing/docx/

xml .ElementTree do c: 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/

...