如何在 Python 中处理 PDF 数据

如何在 Python 中处理 PDF 数据

Aspose.PDF FOSS for Python 将 PDF 文档的 XMP 元数据表示为一个基于 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:创建 Packet 并设置简单属性

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。每个 getter 在属性不存在时返回 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 构建一个,使用 add 添加 XmpField 对象成员,并使用 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 即使我刚刚设置了该属性

确认传递给 get/get_int/get_date/etc 的 prefix(或 URI)参数与匹配的 set_* 调用中传入的完全一致——get 同时匹配字段的前缀和其命名空间 URI,但如果一次调用使用了不匹配的自定义前缀而另一次使用了 URI,则会失配。

自定义命名空间前缀在序列化时产生不完整的输出

如果前缀不是标准 XMP 命名空间之一(dc、xmp、pdf 等),则需要在每次 set_* 调用时显式提供 uri= 参数,或在调用 serialize()/to_bytes() 之前,通过 XmpPacket(namespace_provider=provider) 注册并将带有该前缀的 NamespaceProvider 附加到数据包上。

get_bool, get_int, 或者 get_real returns None 对于我已知已设置的值

当存储的文本无法解析为预期类型时,这些 typed getter 会返回 None——例如,对一个其值使用 set_value 作为自由文本而非 set_int 设置的属性调用 get_int。请使用相匹配的 typed setter(set_int、set_real、set_bool),使 getter 的解析逻辑与写入路径保持一致。

XmpPacket.parse raises ValueError 在来自不可信来源的包上

parse 拒绝任何 <!DOCTYPE 或 <!ENTITY 声明,以防止 XXE 和 billion-laughs 攻击。合法的 XMP 包从不需要 DTD;应将此异常视为源流已损坏或不安全的信号,而不是绕过该防护。

get_array returns None 而不是列表

get_array 在命名属性不存在或未以 XmpArray 形式存储时返回 None(例如,如果它是使用 set_value 而不是 set_array 设置的)。写入属性时请使用 set_array,以便存储的值符合 get_array 所期望的形状。

常见问题

在 set_value 与类似 set_int 的类型化 setter 之间有什么区别?

set_value 会原样存储您传入的任何 value。类型化 setter(set_date、set_int、set_real、set_bool)会将输入转换为 XMP 内部使用的纯文本形式,并配合相应的类型化 getter 将其解析回来。因此,在需要往返类型安全而不是原始字符串时,请使用它们。

不同的 XmpArray 类型(Bag、Seq、Alt)有什么区别?

Bag 是一个无序的值集合,Seq 是有序列表,Alt 保存备用值(通常是语言替代项,如 set_localized_text 所使用)。将所需的类型传递给 set_array(..., kind="Bag"),或直接构建一个 XmpArray(kind=...)。

我能在事先不知道属性名称的情况下读取数据包上原始属性列表吗?

是的——遍历 packet.fields,它保存了按插入顺序添加到包中的每个 XmpField、XmpArray 和 XmpProperty。

对于标准 XMP 命名空间,我需要一个 NamespaceProvider 吗?

不。dc、xmp、pdf 以及其他标准前缀会自动解析。只有当你自行创建自定义前缀时才需要 NamespaceProvider。

XmpProperty 限定符与普通的 XmpField 有何不同?

XmpProperty 将基础的 XmpField 与一系列限定字段(使用 add_qualifier 添加)包装在一起,用于较少见的情况,即属性本身需要在其值上附加额外的 RDF 元数据,超出 XmpField.language 已经覆盖的 xml:lang 限定符。

另请参阅

 中文