Как работать с данными 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.