spaCy布局:使用spaCy处理PDF、Word文档等
该插件与Docling集成,为您的spaCy流程带来对PDF、Word文档和其他输入格式的结构化处理。它输出基于文本格式的清晰、结构化数据,并创建spaCy熟悉的Doc对象,使您能够访问如章节或标题等带标签的文本片段,以及将其数据转换为pandas.DataFrame的表格。
此工作流程使将强大的NLP技术应用于您的文档变得容易,包括语言分析、命名实体识别、文本分类等。它也非常适合为RAG(检索增强生成)管道实现数据分块。
📖 博客文章:"从PDF到AI就绪的结构化数据:深入探索" – 一个将PDF及类似文档转换为结构化数据的新模块化工作流程,以spacy-layout和Docling为特色。
📝 使用方法
⚠️ 注意:该包需要Python 3.10或更高版本。
pip install spacy-layout
使用nlp对象进行分词初始化spaCyLayout预处理器后,可以在文档路径上调用它,将其转换为结构化数据。生成的Doc对象包含映射到原始文本中的布局跨度,并暴露各种属性,包括内容类型和布局特征。
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"]:
# 文档部分及其在文本中的token和字符偏移量
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对象:
paths = ["one.pdf", "two.pdf", "three.pdf", ...]
for doc in layout.pipe(paths):
print(doc._.layout)
spaCy还允许您在已创建的Doc上调用nlp对象,因此您可以轻松应用组件流程进行语言分析或命名实体识别,使用基于规则的匹配,或执行任何其他spaCy能做的操作。
# 加载基于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的表格数据。
for table in doc._.tables:
# Token位置和边界框
print(table.start, table.end, table._.layout)
# 内容的pandas.DataFrame
print(table._.data)
默认情况下,跨度文本是一个占位符"TABLE",但您可以通过向spaCyLayout提供display_table回调来自定义表格的呈现方式,该回调接收数据的pandas.DataFrame。这允许您在文档文本中包含表格数据,并在后续使用它们,例如在训练好的命名实体识别器或文本分类器进行信息提取时。
def display_table(df: pd.DataFrame) -> str:
return f"包含列的表格: {', '.join(df.columns.tolist())}"
layout = spaCyLayout(nlp, display_table=display_table)
序列化
处理文档后,您可以将结构化的Doc对象以spaCy高效的二进制格式进行序列化,这样就无需重新运行资源密集型转换。
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对象,需要初始化它以便重新填充自定义属性。我们计划在即将发布的版本中使这个过程更加优雅。
+ layout = spacyLayout(nlp)
doc_bin = DocBin(store_user_data=True).from_disk("./file.spacy")
docs = list(doc_bin.get_docs(nlp.vocab))
🎛️ API
数据和扩展属性
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 | 提取的布局跨度的类型,例如 "text" 或 "section_header"。 | |
Span.label | int | 跨度标签的整数ID。 | |
Span.id | int | 布局跨度的运行索引。 | |
Span._.layout | `SpanLayout | None` | 布局跨度的布局特征。 |
Span._.heading | `Span | None` | 距离跨度最近的标题(如果有)。 |
Span._.data | `pandas.DataFrame | None` | 为表格跨度提取的数据。 |
数据类 PageLayout
| 属性 | 类型 | 描述 |
|---|---|---|
page_no | int | 页码(从1开始)。 |
width | float | 页面宽度(像素)。 |
height | float | 页面高度(像素)。 |
数据类 DocLayout
| 属性 | 类型 | 描述 |
|---|---|---|
pages | list[PageLayout] | 文档中的页面。 |
数据类 SpanLayout
| 属性 | 类型 | 描述 |
|---|---|---|
x | float | 边界框的水平偏移量(像素)。 |
y | float | 边界框的垂直偏移量(像素)。 |
width | float | 边界框的宽度(像素)。 |
height | float | 边界框的高度(像素)。 |
page_no | int | 跨度所在的页码。 |
类 spaCyLayout
方法 spaCyLayout.__init__
初始化文档处理器。
nlp = spacy.blank("en")
layout = spaCyLayout(nlp)
| 参数 | 类型 | 描述 | |
|---|---|---|---|
nlp | spacy.language.Language | 用于分词的已初始化nlp对象。 | |
separator | str | 用于分隔所创建Doc对象中各部分的token。分隔符不会是布局跨度的一部分。如果为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的格式选项。 | |
| 返回 | spaCyLayout | 初始化后的对象。 |
方法 spaCyLayout.__call__
处理文档并创建一个包含文本内容和布局跨度的spaCy Doc对象,默认通过Doc.spans["layout"]可访问。
layout = spaCyLayout(nlp)
doc = layout("./starcraft.pdf")
| 参数 | 类型 | 描述 | |||
|---|---|---|---|---|---|
source | `str | Path | bytes | DoclingDocument` | 要处理的文档路径、字节或已创建的DoclingDocument。 |
| 返回 | Doc | 处理后的spaCy Doc对象。 |
方法 spaCyLayout.pipe
批量处理多个文档并创建spaCy Doc对象。如果处理大规模文档,应使用此方法。as_tuples的行为与spaCy的Language.pipe相同。
layout = spaCyLayout(nlp)
paths = ["one.pdf", "two.pdf", "three.pdf", ...]
docs = layout.pipe(paths)
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。 | |||||
| 生成 | `Doc | tuple[Doc, Any]` | 处理后的spaCy Doc对象,或者如果as_tuples设置为True,则为(doc, context)元组。 |
💡 示例和代码片段
本节包含关于如何使用spacy-layout的更多示例。如果您有适合的示例,欢迎提交拉取请求!
使用matplotlib可视化页面和边界框
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()
```FINISHED