Как работать с данными PDF в Python

Как работать с данными PDF в Python

Aspose.PDF FOSS для Python представляет XMP-метаданные PDF-документа в виде модели данных в памяти, построенной из объектов XmpPacket, XmpField, XmpArray, XmpStruct и XmpProperty, при этом NamespaceProvider разрешает префиксы пространств имён в URI. Это позволяет читать, создавать и переписывать структурированные метаданные — заголовки, даты, списки ключевых слов, пользовательские поля — без ручного написания RDF/XML. Библиотека полностью написана на Python и устанавливается командой ниже.

Пошаговое руководство

Шаг 1: Установить пакет

Установите пакет Aspose.PDF FOSS:

git clone https://github.com/aspose-pdf-foss/Aspose-PDF-FOSS-for-Python.git
cd Aspose-PDF-FOSS-for-Python
pip install -e .

Проверьте установку, импортировав XmpPacket и выведя сообщение подтверждения:

from aspose_pdf import XmpPacket
print("aspose-pdf-foss-for-python is ready.")

Шаг 2: Импортировать необходимые классы

Импортировать классы модели данных XMP и разрешитель пространств имён:

from aspose_pdf import (
    NamespaceProvider,
    XmpArray,
    XmpField,
    XmpPacket,
    XmpProperty,
    XmpStruct,
)

Шаг 3: Создать пакет и задать простые свойства

XmpPacket() начинается с пустого списка полей. set_value(prefix, name, value, uri=...) задаёт или заменяет простое свойство, а get(prefix_or_uri, name) считывает его обратно как XmpField; get принимает либо зарегистрированный префикс, либо полный URI пространства имён:

from aspose_pdf import XmpPacket

packet = XmpPacket()
packet.set_value("dc", "title", "Quarterly Report")
packet.set_value(
    "dc", "creator", "Automation Pipeline",
    uri="http://purl.org/dc/elements/1.1/",
)

title_field = packet.get("dc", "title")
print(title_field.value)  # "Quarterly Report"

Шаг 4: Сохранить типизированные значения

XmpPacket предоставляет типизированные удобные аксессоры, которые преобразуют в и из текстового формата, который XMP хранит внутренне: set_date/get_date, set_int/get_int, set_real/get_real и set_bool/get_bool. Каждый геттер возвращает None, когда свойство отсутствует, вместо того чтобы вызывать ошибку:

from datetime import datetime

packet.set_date("xmp", "CreateDate", datetime(2026, 7, 29, 9, 30))
packet.set_int("pdf", "PageCount", 42)
packet.set_real("custom", "ConfidenceScore", 0.97, uri="https://example.com/ns/custom/1.0/")
packet.set_bool("custom", "IsFinal", True, uri="https://example.com/ns/custom/1.0/")

print(packet.get_date("xmp", "CreateDate"))
print(packet.get_int("pdf", "PageCount"))
print(packet.get_real("custom", "ConfidenceScore"))
print(packet.get_bool("custom", "IsFinal"))

Шаг 5: Сохранить локализованный текст и упорядоченные массивы

set_localized_text записывает свойство с альтернативой языка (rdf:Alt), например dc:title, на конкретном языке, по умолчанию "x-default". set_array записывает упорядоченный (Seq), неупорядоченный (Bag) или альтернативный (Alt) массив из простого списка значений, а get_array считывает его обратно как список строк:

packet.set_localized_text("dc", "description", "Quarterly summary", lang="en")
packet.set_localized_text(
    "dc", "description", "Resumen trimestral",
    uri="http://purl.org/dc/elements/1.1/", lang="es",
)
print(packet.get_localized_text("dc", "description", lang="es"))

packet.set_array("dc", "subject", ["finance", "quarterly", "internal"], kind="Bag")
print(packet.get_array("dc", "subject"))

Шаг 6: Создать структурированные значения с помощью XmpStruct

Некоторые свойства XMP (записи размеров, записи истории) являются структурированными значениями, а не простым текстом. Создайте одно с помощью XmpStruct, добавьте объект XmpField-членов с помощью add, прочитайте член по имени с помощью get, затем присоедините структуру к пакету с помощью add:

dimensions = XmpStruct()
dimensions.add(XmpField(prefix="stDim", name="w", value="612"))
dimensions.add(XmpField(prefix="stDim", name="h", value="792"))
dimensions.add(XmpField(prefix="stDim", name="unit", value="pt"))

print(dimensions.get("w").value)  # "612"

packet.add(XmpField(prefix="xmpTPg", name="MaxPageSize", value=dimensions))

Шаг 7: Зарегистрировать пользовательские пространства имён XMP

NamespaceProvider предзагружен стандартными пространствами имён XMP (Dublin Core, Adobe XMP, PDF и другие). Вызовите register(prefix, uri), чтобы добавить пользовательскую привязку — он возвращает самого поставщика, поэтому вызовы можно цепочкой — затем присоедините поставщика к пакету, чтобы сериализация могла разрешить пользовательский префикс в его URI:

from aspose_pdf import NamespaceProvider, XmpPacket

provider = NamespaceProvider()
provider.register("custom", "https://example.com/ns/custom/1.0/")

packet = XmpPacket(namespace_provider=provider)
packet.set_value("custom", "batch_id", "run-2026-07-29")

Шаг 8: Разобрать и сериализовать пакет

XmpPacket.parse(data, provider=...) считывает необработанные байты или текст XMP-пакета в XmpPacket. serialize() и to_bytes() оба выводят пакет обратно в байты XMP-пакета:

xmp_bytes = packet.to_bytes()

restored = XmpPacket.parse(xmp_bytes)
print(restored.get("dc", "title").value)

Распространённые проблемы и их решения

get() или типизированный getter возвращает None хотя я только что установил свойство

Подтвердите, что аргумент prefix (или URI), передаваемый в get/get_int/get_date/etc., точно соответствует тому, что был передан в соответствующий вызов set_* — get сопоставляется как по префиксу поля, так и по URI пространства имён, но несоответствующий пользовательский префикс в одном вызове и URI в другом приведут к несоответствию.

Пользовательский префикс пространства имён приводит к неполному выводу при сериализации

Префикс, который не относится к стандартным пространствам имён XMP (dc, xmp, pdf и аналогичным), требует либо явного аргумента uri= в каждом вызове set_*, либо NamespaceProvider с зарегистрированным этим префиксом и присоединённым к пакету через XmpPacket(namespace_provider=provider) перед вызовом serialize()/to_bytes().

get_bool, get_int, или get_real returns None для значения, которое, как я знаю, установлено

Эти типизированные геттеры возвращают None, когда сохранённый текст не разбирается как ожидаемый тип — например, get_int у свойства, значение которого было установлено с помощью set_value как свободный текст, а не set_int. Используйте соответствующий типизированный сеттер (set_int, set_real, set_bool), чтобы логика разбора геттера соответствовала пути записи.

XmpPacket.parse raises ValueError на пакете из недоверенного источника

parse отклоняет любые объявления <!DOCTYPE или <!ENTITY как защиту от враждебного ввода против атак XXE и billion-laughs. Законный XMP-пакет никогда не требует DTD; рассматривайте исключение как сигнал о том, что исходный поток повреждён или небезопасен, а не пытайтесь обходить эту защиту.

get_array returns None вместо списка

get_array возвращает None, когда именованное свойство либо не существует, либо не было сохранено как XmpArray (например, если оно было установлено с помощью set_value вместо set_array). Используйте set_array при записи свойства, чтобы сохранённое значение соответствовало форме, которую ожидает get_array.

Часто задаваемые вопросы

В чём разница между set_value и типизированными сеттерами, такими как set_int?

set_value сохраняет всё, что value вы передаёте без изменений. Типизированные сеттеры (set_date, set_int, set_real, set_bool) преобразуют свой ввод в текстовую форму, используемую XMP внутри, и сопоставляются с соответствующим типизированным геттером, который разбирает её обратно, поэтому используйте их, когда нужна сквозная типобезопасность, а не простые строки.

Чем отличаются виды XmpArray (Bag, Seq, Alt)?

Bag — это неупорядоченный набор значений, Seq — упорядоченный список, а Alt хранит альтернативные значения (чаще всего языковые альтернативы, как используется в set_localized_text). Передайте нужный тип в set_array(..., kind="Bag") или создайте XmpArray(kind=...) напрямую.

Могу ли я прочитать необработанный список свойств пакета, не зная их имён заранее?

Да — итерировать packet.fields, который содержит каждый XmpField, XmpArray и XmpProperty, добавленные в пакет в порядке вставки.

Нужно ли мне NamespaceProvider для стандартных пространств имён XMP?

Нет. dc, xmp, pdf и другие стандартные префиксы разрешаются автоматически. NamespaceProvider нужен только для пользовательских префиксов, которые вы придумываете сами.

Чем квалификаторы XmpProperty отличаются от простого XmpField?

XmpProperty объединяет базовый XmpField со списком полей-квалификаторов (добавляемых с помощью add_qualifier), используемый в редких случаях, когда свойству самому требуется дополнительный RDF-метаданные, прикреплённые к его значению, помимо квалификатора xml:lang, который уже покрывается XmpField.language.

См. также:

 Русский