使用spaCy处理PDF与Word文档的技术指南

本文介绍了spacy-layout库如何将PDF、Word等文档转换为结构化数据,集成spaCy管道进行NLP分析,包括文本提取、表格数据处理、布局识别及序列化操作,适用于RAG管道和信息提取任务。

📚 使用spaCy处理PDF、Word文档等技术内容

spaCy Layout插件集成了Docling技术,可将PDF、Word等文档转换为结构化数据并集成至spaCy管道。输出包含清洁的文本化结构化数据及spaCy Doc对象,支持访问带标签的文本区块(如章节、标题)和转换为pandas.DataFrame的表格数据。

该工作流便于对文档应用强大的NLP技术,包括语言分析、命名实体识别、文本分类等,同时也适用于RAG管道的分块处理。

📝 使用方法

⚠️ 要求Python 3.10及以上版本。

1
pip install spacy-layout

初始化spaCyLayout预处理器后,可调用其处理文档路径并转换为结构化数据。生成的Doc对象包含布局区块,映射至原始文本并暴露多种属性(如内容类型和布局特征)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import spacy
from spacy_layout import spaCyLayout

nlp = spacy.blank("en")
layout = spaCyLayout(nlp)

# 处理文档并创建spaCy Doc对象
doc = layout("./starcraft.pdf")

# 文档的文本内容
print(doc.text)
# 文档布局(包含页面及页面尺寸)
print(doc._.layout)
# 文档中的表格及提取的数据
print(doc._.tables)
# 文档的Markdown表示
print(doc._.markdown)

# 不同章节的布局区块
for span in doc.spans["layout"]:
    # 文档区块及文本字符偏移量
    print(span.text, span.start, span.end, span.start_char, span.end_char)
    # 区块类型(如"text"、"title"、"section_header"等)
    print(span.label_)
    # 区块的布局特征(包含边界框)
    print(span._.layout)
    # 区块最近的标题(精度取决于文档结构)
    print(span._.heading)

批量处理文档可使用spaCyLayout.pipe方法,接收路径或字节的可迭代对象并生成Doc对象:

1
2
3
paths = ["one.pdf", "two.pdf", "three.pdf", ...]
for doc in layout.pipe(paths):
    print(doc._.layout)

可对已创建的Doc对象调用nlp管道,应用语言分析、命名实体识别、规则匹配等操作:

1
2
3
4
5
6
7
8
# 加载基于transformer的英文管道
# 安装: python -m spacy download en_core_web_trf
nlp = spacy.load("en_core_web_trf")
layout = spaCyLayout(nlp)

doc = layout("./starcraft.pdf")
# 应用管道以获取词性标注、依赖解析、实体识别等
doc = nlp(doc)

表格与表格数据处理

表格以"table"标签包含在布局区块中,可通过Doc._.tables快捷访问。它们暴露布局扩展属性及data属性(包含转换为pandas.DataFrame的表格数据)。

1
2
3
4
5
for table in doc._.tables:
    # 词符位置及边界框
    print(table.start, table.end, table._.layout)
    # 内容的pandas.DataFrame
    print(table._.data)

默认区块文本为占位符"TABLE",可通过向spaCyLayout提供display_table回调函数自定义表格渲染方式(接收pandas.DataFrame数据),便于在文档文本中包含表格数据并在后续信息提取中使用。

1
2
3
4
def display_table(df: pd.DataFrame) -> str:
    return f"Table with columns: {', '.join(df.columns.tolist())}"

layout = spaCyLayout(nlp, display_table=display_table)

序列化

处理文档后,可将结构化Doc对象序列化为spaCy高效二进制格式,避免重复运行资源密集型转换。

1
2
3
4
5
from spacy.tokens import DocBin

docs = layout.pipe(["one.pdf", "two.pdf", "three.pdf"])
doc_bin = DocBin(docs=docs, store_user_data=True)
doc_bin.to_disk("./file.spacy")

⚠️ 反序列化扩展属性注意:当前自定义扩展属性(如Doc._.layout)在初始化spaCyLayout时注册。若从二进制文件加载带布局信息的Doc对象,需重新初始化以重新填充自定义属性。未来版本将优化此流程。

1
2
3
layout = spaCyLayout(nlp)
doc_bin = DocBin(store_user_data=True).from_disk("./file.spacy")
docs = list(doc_bin.get_docs(nlp.vocab))

🎛️ API

数据与扩展属性

1
2
3
4
5
layout = spaCyLayout(nlp)
doc = layout("./starcraft.pdf")
print(doc._.layout)
for span in doc.spans["layout"]:
    print(span.label_, span._.layout)
属性 类型 描述
Doc._.layout DocLayout 文档的布局特征
Doc._.pages list[tuple[PageLayout, list[Span]]] 文档中的页面及包含的区块
Doc._.tables list[Span] 文档中的所有表格
Doc._.markdown str 文档的Markdown表示
Doc.spans["layout"] spacy.tokens.SpanGroup 文档中的布局区块
Span.label_ str 提取的布局区块类型
Span.label int 区块标签的整数ID
Span.id int 布局区块的运行索引
Span._.layout SpanLayout | None 布局区块的布局特征
Span._.heading Span | None 区块最近的标题(若存在)
Span._.data pandas.DataFrame | None 表格区块的提取数据

dataclass PageLayout

属性 类型 描述
page_no int 页码(从1开始)
width float 页面宽度(像素)
height float 页面高度(像素)

dataclass DocLayout

属性 类型 描述
pages list[PageLayout] 文档中的页面

dataclass SpanLayout

属性 类型 描述
x float 边界框水平偏移(像素)
y float 边界框垂直偏移(像素)
width float 边界框宽度(像素)
height float 边界框高度(像素)
page_no int 区块所在页码

class spaCyLayout

method spaCyLayout.init

初始化文档处理器。

1
2
nlp = spacy.blank("en")
layout = spaCyLayout(nlp)
参数 类型 描述
nlp spacy.language.Language 用于词符化的初始化nlp对象
separator str 在创建的Doc对象中分隔区块的词符。分隔符不会成为布局区块的一部分。若为None则不添加分隔符。默认为"\n\n"。
attrs dict[str, str] 覆盖自定义spaCy属性。可包含"doc_layout"、“doc_pages”、“doc_tables”、“doc_markdown”、“span_layout”、“span_data”、“span_heading"和"span_group”。
headings list[str] 用于Span._.heading检测的标题标签。默认为[“section_header”, “page_header”, “title”]。
display_table Callable[[pandas.DataFrame], str] | str 生成Doc.text中表格文本表示的函数或占位文本。默认为"TABLE"。
docling_options dict[InputFormat, FormatOption] 传递给Docling的DocumentConverter的格式选项。

RETURNS: spaCyLayout初始化的对象。

method spaCyLayout.call

处理文档并创建包含文本内容及布局区块的spaCy Doc对象(默认通过Doc.spans["layout"]访问)。

1
2
layout = spaCyLayout(nlp)
doc = layout("./starcraft.pdf")
参数 类型 描述
source str | Path | bytes | DoclingDocument 要处理的文档路径、字节或已创建的DoclingDocument。

RETURNS: Doc处理后的spaCy Doc对象。

method spaCyLayout.pipe

处理多个文档并创建spaCy Doc对象。处理大量文档时推荐使用此方法。as_tuples行为与spaCy的Language.pipe相同。

1
2
3
layout = spaCyLayout(nlp)
paths = ["one.pdf", "two.pdf", "three.pdf", ...]
docs = layout.pipe(paths)
1
2
3
sources = [("one.pdf", {"id": 1}), ("two.pdf", {"id": 2})]
for doc, context in layout.pipe(sources, as_tuples=True):
    ...
参数 类型 描述
sources Iterable[str | Path | bytes] | Iterable[tuple[str | Path | bytes, Any]] 要处理的文档路径或字节,或若as_tuples设为True时的(source, context)元组。
as_tuples bool 若设为True,输入应为(source, context)元组的可迭代对象。输出将为(doc, context)元组序列。默认为False。

YIELDS: Doc | tuple[Doc, Any] 处理后的spaCy Doc对象或若as_tuples设为True时的(doc, context)元组。

💡 示例与代码片段

本节包含更多spacy-layout应用示例。若有适合的示例,欢迎提交拉取请求!

使用matplotlib可视化页面及边界框

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import pypdfium2 as pdfium
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
import spacy
from spacy_layout import spaCyLayout

DOCUMENT_PATH = "./document.pdf"

# 加载PDF页面并转换为图像
pdf = pdfium.PdfDocument(DOCUMENT_PATH)
page_image = pdf[2].render(scale=1)  # 获取第3页(索引2)
numpy_array = page_image.to_numpy()
# 使用spaCy处理文档
nlp = spacy.blank("en")
layout = spaCyLayout(nlp)
doc = layout(DOCUMENT_PATH)

# 获取第3页布局及区块
page = doc._.pages[2]
page_layout = doc._.layout.pages[2]
# 创建具有页面尺寸的图形和轴
fig, ax = plt.subplots(figsize=(12, 16))
# 显示PDF图像
ax.imshow(numpy_array)
# 为每个区块的边界框添加矩形
for section in page[1]:
    # 创建矩形补丁
    rect = Rectangle(
        (section._.layout.x, section._.layout.y),
        section._.layout.width,
        section._.layout.height,
        fill=False,
        color="blue",
        linewidth=1,
        alpha=0.5
    )
    ax.add_patch(rect)
    # 在框顶部添加文本标签
    ax.text(
        section._.layout.x,
        section._.layout.y,
        section.label_,
        fontsize=8,
        color="red",
        verticalalignment="bottom"
    )

ax.axis("off")  # 隐藏轴
plt.show()
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计