📚 使用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()
|