python
February 22

Простой способ извлечения изображений из документов MS и Libre Office

Стандарт Open Document стал де-факто уже давно. И если раньше формат документов Microsoft Office был проприетарным, то теперь он представляет собой «.zip»-файл в котором храниться множество «.xml», а также изображения и прочие файлы. Конечно же, самым простым способом извлечь документы является изменение расширения документа на «.zip» и последующее извлечение файлов любым архиватором. Но, если вам нужно сделать это не с одним документом, это может быть достаточно продолжительно по времени. Поэтому, давайте рассмотрим несколько способов, с помощью которых можно извлечь изображения из файлов формата ".docx", ".xlsx", ".pptx", ".odp", ".ods", ".odt".

Способ №1: используем стандартные библиотеки python

Для данного способа не требуется установка сторонних библиотек. Достаточно тех, что поставляются в комплекте с самим интерпретатором.Создадим файл extract.py.

Импортируем нужные нам в процессе работы библиотеки:

import shutil
import zipfile
from pathlib import Path

Создадим функцию image_extract(in_zip: zipfile, out_dir: Path), в которую будем передавать zip-файл, а также путь к директории в которую будут распакованы изображения. В данном случае путь у пользователя запрашиваться не будет.Объявим переменную cnt, которая будет счетчиком распакованных изображений. Затем в цикле пробежимся по списку содержимого архива. Проверим расширения файлов в архиве. И если они совпадают с расширениями из списка, будем читать текущий файл с изображением и сохранять в байтовом режиме в директорию, которая передается в функцию. Также, данная директория создается автоматически и называется именем исходного документа. После того, как все изображения будут сохранены, возвращаем из функции наш счетчик.

def image_extract(in_zip: zipfile, out_dir: Path) -> int:
    cnt = 0
    for info in in_zip.infolist():
        if Path(info.filename).suffix in [".png", ".jpg", ".jpeg", ".gif"]:
            Path(out_dir).mkdir(exist_ok=True)
            content = in_zip.read(info)
            with open(out_dir / Path(info.filename).name, "wb") as img:
                img.write(content)
            cnt += 1
    return cnt

Осталось дело за малым, запросить у пользователя путь к файлу, проверить, является ли он файлом и имеет ли нужный формат. После чего скопировать файл с расширением «.zip», открыть с помощью zipfile и передать в функцию для поиска изображений и их распаковки. Ну и напоследок удалить скопированный zip-файл.После окончания извлечения изображений вывести сообщение пользователю о том, сколько файлов было извлечено и в какую директорию.

def main():
    out_dir = ""
    path_file = input("Enter file path: ")
    if Path(path_file).is_file() and Path(path_file).suffix in [".docx", ".xlsx", ".pptx", ".odp", ".ods", ".odt"]:
        out_dir = Path.cwd() / Path(path_file).name.removesuffix(Path(path_file).suffix)
        path_zip = Path(path_file).parent / f"{Path(path_file).name.removesuffix(Path(path_file).suffix)}.zip"
        if not path_zip.exists():
            shutil.copy2(path_file, path_zip)
        with zipfile.ZipFile(path_zip) as in_zip:
            cnt = image_extract(in_zip, out_dir)
        Path(path_zip).unlink()
    else:
        print("[~] The path specified is not a file path or is not a MS Office document.")

    if cnt > 0:
        print(f"[!] Extract images: [{cnt}] in directory: [{out_dir}]")
    else:
        print("[~] There are no images to extract.")


if __name__ == "__main__":
    main()

Полный код скрипта:

import shutil
import zipfile
from pathlib import Path


def image_extract(in_zip: zipfile, out_dir: Path) -> int:
    cnt = 0
    for info in in_zip.infolist():
        if Path(info.filename).suffix in [".png", ".jpg", ".jpeg", ".gif"]:
            Path(out_dir).mkdir(exist_ok=True)
            content = in_zip.read(info)
            with open(out_dir / Path(info.filename).name, "wb") as img:
                img.write(content)
            cnt += 1
    return cnt


def main():
    out_dir = ""
    path_file = input("Enter file path: ")
    if Path(path_file).is_file() and Path(path_file).suffix in [".docx", ".xlsx", ".pptx", ".odp", ".ods", ".odt"]:
        out_dir = Path.cwd() / Path(path_file).name.removesuffix(Path(path_file).suffix)
        path_zip = Path(path_file).parent / f"{Path(path_file).name.removesuffix(Path(path_file).suffix)}.zip"
        if not path_zip.exists():
            shutil.copy2(path_file, path_zip)
        with zipfile.ZipFile(path_zip) as in_zip:
            cnt = image_extract(in_zip, out_dir)
        Path(path_zip).unlink()
    else:
        print("[~] The path specified is not a file path or is not a MS Office document.")

    if cnt > 0:
        print(f"[!] Extract images: [{cnt}] in directory: [{out_dir}]")
    else:
        print("[~] There are no images to extract.")


if __name__ == "__main__":
    main()

Протестируем созданный скрипт. У нас есть, для примера документ «CustomTkinter.docx». В нем содержаться несколько изображений. Вот их мы и попробуем извлечь.Запускаем скрипт, указываем путь к документу и получаем папку с названием документа, в которой содержаться изображения.

Способ №2: используем библиотеку docx2txt

Создадим файл docx2im.py. В данном случае мы будем использовать стороннюю библиотеку docx2txt. Для ее установки пишем в терминале или командной строке:

pip install docx2txt

Теперь импортируем нужные библиотеки в скрипт.

"""
pip install docx2txt
"""
from pathlib import Path

import docx2txt

В данном случае нам не понадобиться создавать дополнительных функций. Сделаем все в одной. Хотя, в теории, можно было бы разделить извлечение изображений и текста. Да, дополнительным бонусом, при том, что извлекаются изображения только из документов «.docx», является извлечение текста.

Для начала запросим у пользователя путь к документу и путь к папке для извлечения изображений. Проверим, является ли переданный параметр путем к документу и является ли расширение данного документа «.docx». Затем проверим, существует ли директория для извлечения изображений. Если нет, создадим ее, так как docx2txt ее самостоятельно не создает.Теперь передаем путь к документу и путь для извлечения изображений в функцию process данной библиотеки. Из нее будет возвращен текст документа. Проверим, содержится ли в переменной text что-нибудь. Для этого обрежем пробелы и знаки переноса каретки, так как, если в документе нет текста, но есть пустая строка, переменна text не будет пуста.Затем открываем текстовый документ на запись и записываем в него полученный текст.

def main() -> None:
    docx_path = input("Enter document path '.docx': ")
    out_img_path = input("Enter a path to save the images: ")

    if Path(docx_path).is_file() and Path(docx_path).suffix == ".docx":
        if not Path(out_img_path).exists():
            Path(out_img_path).mkdir(exist_ok=True)
        text = docx2txt.process(docx_path, out_img_path)
        if text:
            with open(Path.cwd() / f"{Path(docx_path).name.removesuffix(Path(docx_path).suffix)}.txt",
                      "w", encoding="utf-8") as txt:
                txt.write(text)
    else:
        print("The file is not '.docx'.")


if __name__ == "__main__":
    main()

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

Думаю, что вышеприведенных скриптов будет достаточно. Можно использовать также библиотеку aspose-words, которая устанавливается с помощью команды:

pip install aspose-words

Однако, способ извлечения с ее помощью изображений не особо отличается от предыдущих. Создадим файл aspose_extract.py. Вот для примера код:

from pathlib import Path

import aspose.words as aw


def extract_image(docx_path, out_img_path):
    cnt = 0
    doc = aw.Document(docx_path)
    shapes = doc.get_child_nodes(aw.NodeType.SHAPE, True)
    for shape in shapes:
        shape = shape.as_shape()
        if shape.has_image:
            if not Path(out_img_path).exists():
                Path(out_img_path).mkdir(exist_ok=True)
            image = f"image_{cnt}{aw.FileFormatUtil.image_type_to_extension(shape.image_data.image_type)}"
            shape.image_data.save(str(Path(out_img_path) / image))
            cnt += 1


def main():
    docx_path = input("Enter document path '.docx': ")
    out_img_path = input("Enter a path to save the images: ")
    if Path(docx_path).is_file() and Path(docx_path).suffix == ".docx":
        extract_image(docx_path, out_img_path)


if __name__ == "__main__":
    main()

Выбирать, что использовать вам и в каком виде, конечно же, вам. Здесь описаны только несколько способов, с помощью которых можно проделать данную операцию. Скорее всего, если поискать более тщательно, найдется еще множество библиотек, с помощью которых можно извлечь изображения из документов.

А на этом все. Спасибо за внимание.
Надеюсь, данная информация будет вам полезна.
Не забудь подписаться на наши телеграм каналы!