完整实战指南:读取、添加、属性修改、删除与 PDF/A-3B 合规)
pypdf 处理 PDF 附件Embedded Files完整实战指南读取、添加、属性修改、删除与 PDF/A-3B 合规【免费下载链接】pypdfA pure-python PDF library capable of splitting, merging, cropping, and transforming the pages of PDF files项目地址: https://gitcode.com/GitHub_Trending/py/pypdf本文以 pypdf 为对象系统讲解 PDF 附件即内嵌文件 Embedded Files的完整操作流程如何通过PdfReader.attachments与PdfReader.attachment_list提取附件内容如何用PdfWriter.add_attachment()嵌入新文件并精细修改其元数据属性如何删除附件以及如何在不破坏 PDF/A-3B 合规性的前提下附加关联文件。读完本文你将能够在纯 Python 环境下完成 PDF 附件的增删改查全流程并理解 pypdf 底层如何用名称树Name Tree和文件规格字典File Specification Dictionary组织附件。PDF 附件是什么PDF 文档不仅可以包含页面内容还可以携带附件attachment在 PDF 规范中也被称为内嵌文件embedded file。附件与文档主体相对独立通常用于为 PDF 附带原始数据、源文件、补充材料或说明文档。pypdf 将附件抽象为两个层面的接口内容层面通过PdfReader.attachments属性获得文件名 → 内容字节的映射适合快速提取对象层面通过PdfReader.attachment_list获得EmbeddedFile对象序列适合需要访问附件元数据替代名称、描述、校验和、日期等的场景。这两条路径的定义分别位于 pypdf/_doc_common.py而EmbeddedFile类的完整实现位于 pypdf/generic/_files.py。文档 docs/user/handle-attachments.md 是本主题的官方指南。读取附件从 PDF 中提取内嵌文件通过 attachments 映射提取内容读取附件最简单的方式是使用reader.attachments。需要注意的是附件的名称filename可能不唯一同一个文档里可能有多份同名文件例如同一份数据以不同版本出现。因此reader.attachments[attachment_name]的值是一个list而不是单个字节串。from pypdf import PdfReader reader PdfReader(example.pdf) for name, content_list in reader.attachments.items(): for i, content in enumerate(content_list): with open(fout-attachment-{i}-{name}, wb) as fp: fp.write(content)上面的示例会把 PDF 中每一个附件逐一写出到磁盘文件。由于同名附件存在多个版本文件名中加入了序号i以避免互相覆盖。通过 attachment_list 获取对象化信息如果你需要附件的更多细节如替代名称、描述、MIME 子类型、修改日期、校验和等可以改用面向对象的方式遍历reader.attachment_listfrom pypdf import PdfReader reader PdfReader(example.pdf) for attachment in reader.attachment_list: print(attachment.name, attachment.alternative_name, attachment.content)每个元素都是EmbeddedFile实例其content属性返回的是附件内容的原始字节bytes。文件名安全警告必须先做净化处理pypdf 在 pypdf/generic/_files.py 的name属性文档中明确给出了警告附件名称可以包含任意字符。这意味着一个恶意或构造不当的 PDF 可能携带包含路径分隔符如/、\、..甚至绝对路径的文件名。因此在把附件写入磁盘之前务必对文件名做净化处理例如只保留[A-Za-z0-9_.-]之类的安全字符集、替换路径分隔符、拒绝空名或绝对路径否则可能造成目录穿越或文件覆盖风险。alternative_name属性同样存在此风险见 pypdf/generic/_files.py。底层实现LazyDict 与名称树解析reader.attachments在源码中是一个LazyDict只有在实际访问某个键时才真正解析并解压对应附件pypdf/_doc_common.py。内部流程是_list_attachments()遍历attachment_list收集每个附件的name与alternative_name作为候选键pypdf/_doc_common.py_get_attachments()按名称聚合内容遇到同名附件时自动把结果折叠成 listpypdf/_doc_common.pyEmbeddedFile._load()解析文档目录Catalog中的/Names名称树兼容扁平/Names数组与基于/Kids的嵌套名称树两种结构pypdf/generic/_files.py。测试 tests/test_writer.py 验证了同名附件的聚合行为向写入器添加两个foobar2.txt后reader.attachments[foobar2.txt]返回包含两份内容的列表同时确认str(reader.attachments)输出LazyDict(keys[foobar.txt, foobar2.txt])即键集合不会因同名附件而重复。添加附件向 PDF 写入新文件基本用法使用PdfWriter.add_attachment()即可嵌入一个新文件返回新建的EmbeddedFile对象from pypdf import PdfWriter writer PdfWriter(clone_fromexample.pdf) writer.add_attachment(filenametest.txt, databHello World!)其中filename是附件显示名称data是内容。data既可以是bytes也可以是str——传入字符串时pypdf 会按latin-1编码自动转换为字节见 pypdf/generic/_files.py 与content的 setter pypdf/generic/_files.py。测试 tests/test_writer.py 中同样混用了bytes与str两种入参。修改附件的属性add_attachment()返回的EmbeddedFile对象提供了对应属性的 setter可以精细控制附件的元数据import datetime import hashlib from pypdf import PdfWriter from pypdf.generic import create_string_object, ByteStringObject, NameObject, NumberObject writer PdfWriter(clone_fromexample.pdf) embedded_file writer.add_attachment(filenametest.txt, databHello World!) embedded_file.size NumberObject(len(bHello World!)) embedded_file.alternative_name create_string_object(test1.txt) embedded_file.description create_string_object(My test file) embedded_file.subtype NameObject(/text/plain) embedded_file.checksum ByteStringObject(hashlib.md5(bHello World!).digest()) embedded_file.modification_date datetime.datetime.now(tzdatetime.timezone.utc) # embedded_file.content My new content. writer.write(out-add-attachment.pdf)各属性的含义与取值说明如下属性setter 接收类型说明sizeNumberObject未压缩文件的字节大小写入文件参数字典的/Size键alternative_nameTextStringObject替代名称写入文件规格字典的/UF与/F键读取端会优先使用/UFPDF 2.0 规范表 43descriptionTextStringObject附件的描述文本/Desc键subtypeNameObjectMIME 媒体类型必须以斜杠开头如/text/plain/Subtype键checksumByteStringObject未压缩文件的 MD5 校验和/Params/CheckSum键用于接收端校验完整性modification_datedatetime.datetime最后修改时间/Params/ModDate键源码使用format_iso8824_date序列化为 ISO 8824 日期字符串contentstr/bytes附件实际内容传str同样按 latin-1 编码creation_datedatetime.datetime创建时间/Params/CreationDate键读取端通过parse_iso8824_date解析回datetime对象以上 setter 的实现均可对照 pypdf/generic/_files.py。其中size、creation_date、modification_date、checksum落在内嵌文件流的/Params子字典中_ensure_params会在不存在时自动创建该字典而alternative_name、description落在文件规格Filespec字典中subtype落在内嵌文件流本身上。另外写入器本身也支持对象化遍历writer.attachment_list与读取器一样可用add_attachment()添加的附件会出现在其中你可以通过遍历它来修改任意一个已存在附件的属性。底层实现名称树与文件规格字典从源码看add_attachment只是EmbeddedFile._create_new的薄封装pypdf/_writer.py。_create_new在内部做了四件事pypdf/generic/_files.py创建一个类型为/EmbeddedFile的DecodedStreamObject把内容写入流数据构造/EF字典将/F键指向该流对象写入时转为间接引用构造类型为/Filespec的文件规格字典写入/F显示名称与/EF把 名称 文件规格引用 按字典序插入文档名称树/Names/EmbeddedFiles的/Names数组中。名称树的插入使用了bisect二分查找定位索引pypdf/generic/_files.py如果文档原本只有基于/Kids的嵌套名称树pypdf 还会先把它扁平化为/Names数组再插入pypdf/generic/_files.py确保新增附件能被标准读取器识别。删除附件要删除一个已存在的附件可以调用EmbeddedFile.delete()from pypdf import PdfWriter writer PdfWriter(clone_fromexample.pdf) attachment writer.add_attachment(filenametest.txt, databHello World!) attachment.delete() assert list(writer.attachment_list) []delete()的实现会从父级名称数组中同时弹出名称与文件规格引用两项并把内部的pdf_object置为空字典以使其失效pypdf/generic/_files.py。如果对象不在父数组中会抛出PyPdfError。需要特别注意的是delete()不会删除与附件关联的文件关系associated file定义。PDF 规范中一个对象可以通过/AF数组声明它与某个附件的关系详见下文 PDF/A 一节。要彻底移除这种关系需要你自己定位/AF数组并从中移除对应条目——pypdf 官方文档明确说明由于难以自动判断关系定义的位置这一步骤暂时需要手动处理docs/user/handle-attachments.md 的 Delete Attachments 一节。PDF/A 合规为 PDF/A-3B 文档附加关联文件PDF/A-3B 允许在合规文档中嵌入关联文件associated files前提是附件通过/AF数组与文档或其他对象建立显式关系并且正确声明AFRelationship类型。pypdf 在pypdf.constants中提供了AFRelationship常量类pypdf/constants.py支持以下取值常量PDF 中的值含义AFRelationship.SOURCE/Source原始内容来源AFRelationship.DATA/Data可视化呈现所基于的基础数据AFRelationship.ALTERNATIVE/Alternative内容的替代表示AFRelationship.SUPPLEMENT/Supplement对原始来源/数据的补充表示AFRelationship.ENCRYPTED_PAYLOAD/EncryptedPayload加密载荷文档AFRelationship.FORM_DATA/FormData与该 PDF 的 AcroForm 关联的数据AFRelationship.UNSPECIFIED/Unspecified关系未知或无法用上述值描述PDF/A-3B 合规示例下面的完整示例展示了如何向一个 PDF/A-3B 合规文档添加附件而不破坏合规性from pypdf import PdfWriter from pypdf.constants import AFRelationship from pypdf.generic import create_string_object, ArrayObject, NameObject writer PdfWriter(clone_fromexample.pdf) attachment writer.add_attachment(filenametest.txt, dataHello World!) attachment.subtype NameObject(/text/plain) attachment.associated_file_relationship NameObject(AFRelationship.SUPPLEMENT) attachment.alternative_name create_string_object(attachment.name) if /AF in writer.root_object: af writer.root_object[/AF].get_object() else: af ArrayObject() writer.root_object[NameObject(/AF)] af af.append(attachment.pdf_object.indirect_reference) writer.write(out-a3b.pdf)这个示例的关键步骤attachment.subtype设置为/text/plain声明附件的 MIME 类型attachment.associated_file_relationship设置为AFRelationship.SUPPLEMENT声明该附件是对原始内容的补充表示。associated_file_relationship属性读写的是文件规格字典的/AFRelationship键默认值为/Unspecifiedpypdf/generic/_files.pyattachment.alternative_name与主名称保持一致PDF/A-3 要求替代名称可用在文档目录root object的/AF数组中追加该附件文件规格对象的间接引用从而在文档 → 附件之间建立显式关联。文档官方指南docs/user/handle-attachments.md 的 PDF/A compliance 一节还指出这种/AF关系不仅限于整个文档也可以挂到大多数其他 PDF 对象上如某个页面或注释具体语义请参考 PDF 规范第 14.13 节PDF 2.0 规范。总结pypdf 对 PDF 附件的支持已经覆盖了完整的生命周期读取reader.attachments名称 → 内容列表同名附件自动聚合或reader.attachment_listEmbeddedFile对象流底层通过名称树解析并在LazyDict中惰性解压添加writer.add_attachment(filename, data)支持bytes与str自动按 latin-1 编码两种入参返回对象后可继续设置size、alternative_name、description、subtype、checksum、creation_date、modification_date、content等属性删除attachment.delete()从名称数组中移除条目但关联文件关系/AF需自行清理合规配合AFRelationship常量与/AF数组可向 PDF/A-3B 文档安全附加关联文件。如果需要在加密文档上执行上述操作可结合 docs/user/encryption-decryption.md 了解加解密流程附件的增删改测试用例可进一步查阅 tests/test_writer.py以验证同名著附件的聚合、字符串内容自动编码等边界行为。【免费下载链接】pypdfA pure-python PDF library capable of splitting, merging, cropping, and transforming the pages of PDF files项目地址: https://gitcode.com/GitHub_Trending/py/pypdf创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考