当前位置: 首页 > news >正文

获取PDF中的布局信息——如何获取段落

  PDF解析是极其复杂的问题。不可能靠一个工具解决全部问题,尤其是五花八门,格式不统一的PDF文件。除非有钞能力。如果没有那就看看可以分为哪些问题。

  提取文本内容,提取表格内容,提取图片。我认为这些应该是分开做的事情。python有一些组件,是有专长的。

  问题分解以后,最重要的一个事情是,版面分析。怎么确定边界,就是哪一块是什么内容?是正文,还是表格,还是图片?

  文本、图片及形状涵盖了常见的PDF元素,本文介绍利用PyMuPDF提取这些页面元素,及其基本数据结构。本文会提供可运行的代码!

一、技术选型 PyMuPDF

PyMuPDFTextpage对象提供的extractDICT()extractRAWDICT()用以获取页面中的所有文本和图片(内容、位置、属性),基本数据结构如下:

看到这里,有分类,有位置信息。

二、代码演示

2.1 安装

pip install PyMuPDF

2.2 demo代码 

import fitz  # PyMuPDFdef extract_text_blocks(pdf_path):# 打开 PDF 文件pdf_document = fitz.open(pdf_path)# 存储文本块和行块信息text_blocks = []line_blocks = []# 遍历 PDF 中的每一页for page_number in range(len(pdf_document)):page = pdf_document.load_page(page_number)# 获取文本块和行块信息blocks = page.get_text("dict")["blocks"]for b in blocks:for l in b["lines"]:line_blocks.append({"line": l["spans"],"bbox": l["bbox"],"height": l["bbox"][3] - l["bbox"][1]  # 计算行块的高度})text_blocks.append({"block": b["lines"],"bbox": b["bbox"]})# 关闭 PDF 文件pdf_document.close()return text_blocks, line_blocks# 示例用法
pdf_path = "D:\\angus\\py\\困难pdf节选西藏奇正2022.pdf"
text_blocks, line_blocks = extract_text_blocks(pdf_path)# 打印提取的文本块信息
for index, block in enumerate(text_blocks):print(f"Text Block {index + 1}:")for line_index, line in enumerate(block["block"]):print(f"  Line {line_index + 1}: '{line['spans']}' at position {block['bbox']}")# 打印提取的行块信息
for index, line in enumerate(line_blocks):print(f"Line {index + 1}: '{line['line']}' at position {line['bbox']}, height={line['height']}")

三、效果展示

3.1 原文PDF内容

 3.2 解析后得到的结果

 3.3 分析原文和结果

对比输出的结果和原文。我们可以发现,我们拿到了行的数据,也拿到了段落的数据。上述的代码中已经给我们分好了块!这样解可以区分段落了。

3.4 获取更多信息,包括位置

来看一个文本块:

  1. size: 文本的大小。
  2. flags: 文本的标志。
  3. font: 字体名称。
  4. color: 字体颜色。
  5. ascender: 文本的上升高度。
  6. descender: 文本的下降高度。
  7. text: 文本内容。
  8. origin: 文本的起始位置坐标。
  9. bbox: 文本的边界框坐标,即左下角和右上角的坐标。

通过这些信息,我们可以获取到每个文本块的具体内容、大小、位置和格式等信息。这些信息对于分析和处理 PDF 文件中的文本内容非常有用。例如,你可以根据文本的大小、位置和格式来识别标题、正文和其他内容,并进行相应的处理和分析。当然,就以这个文档为例,我们可以看到的是,因为文档本身字体大小都一样,所以很难根据字体和大小获取到标题。

四、错误问题

 但是也发现了问题

4.1 段落有被分开了

原文

错误的问题如下

4.2 将表格错当成了文本内容

原文表格内容如下

 

解析得到的内容如下

表格的一行为一个块内容,

这里调试了一版,可以去掉表格。

逻辑是:判断相邻的block,表格的特征是,当个block内的 lines的 bbox的第四位是相同的。且相邻的block的lines一定是相同的,且lines不为空。逻辑本身没有问题,就怕PDF有问题,识别出来的表格的同一行的bbox中的第四位不一样,这样会错误判断!

import fitz  # PyMuPDFdef is_table_block(b1, b2):# 检查连续相邻的文本块是否具有相同的行数,并且其 bbox 的高度也相同if len(b1["lines"]) == len(b2["lines"]) and b1["bbox"][3] - b1["bbox"][1] == b2["bbox"][3] - b2["bbox"][1]:return Truereturn Falsedef extract_text_blocks(pdf_path):# 打开 PDF 文件pdf_document = fitz.open(pdf_path)# 存储文本块信息text_blocks = []line_blocks = []# 遍历 PDF 中的每一页for page_number in range(len(pdf_document)):page = pdf_document.load_page(page_number)# 获取文本块和行块信息blocks = page.get_text("dict")["blocks"]for i in range(len(blocks)):if i < len(blocks) - 1 and is_table_block(blocks[i], blocks[i+1]):  # 如果是表格,则跳过continuefor l in blocks[i]["lines"]:line_blocks.append({"line": l["spans"],"bbox": l["bbox"],"height": l["bbox"][3] - l["bbox"][1]  # 计算行块的高度})text_blocks.append({"block": blocks[i]["lines"],"bbox": blocks[i]["bbox"]})# 关闭 PDF 文件pdf_document.close()return text_blocks, line_blocks# 示例用法
pdf_path = "D:\\angus\\py\\困难pdf节选西藏奇正2022.pdf"
text_blocks, line_blocks = extract_text_blocks(pdf_path)# 打印提取的文本块信息
# 用于检查两个文本块中的行是否相同
def check_lines_same(block1, block2):num_lines_block1 = len(block1["block"])num_lines_block2 = len(block2["block"])return num_lines_block1 == num_lines_block2for index, block in enumerate(text_blocks):# 获取当前文本块中行的个数num_lines = len(block["block"])# 如果当前文本块是表格,则继续检查下一个文本块是否是表格if num_lines > 1 and index < len(text_blocks) - 1:  # 需要多于一行,并且不是最后一个文本块next_block = text_blocks[index + 1]if check_lines_same(block, next_block):# 如果下一个文本块也是表格,则跳过,不进行打印输出continue# 如果当前文本块不是表格,则打印输出print(f"Text Block {index + 1}:")for line_index, line in enumerate(block["block"]):print(f"  Line {line_index + 1}: '{line['spans']}' at position {block['bbox']}")# 打印提取的行块信息
# for index, line in enumerate(line_blocks):
#     print(f"Line {index + 1}: '{line['line']}' at position {line['bbox']}, height={line['height']}")

4.3 解析丢失整行数据

测试了另外一个法律法规文件。

发现文件丢失了。原文件内容如下:

解析后的:

还没找到bug的原因。 

五、升级版

解决了丢行的问题,因为代码bug,在判断表格的时候有问题。

解决了段落被分开的问题。解决思路是,判断两个段落之间,应该有空白分隔。如果两个块之间没有空白分隔,则为同一个段。

并将内容输出为json格式

import fitz  # PyMuPDF
import jsondef is_table_block(b1, b2):# 检查连续相邻的文本块是否具有相同的行数,并且其 bbox 的高度也相同if len(b1["lines"]) == len(b2["lines"]) and b1["bbox"][3] - b1["bbox"][1] == b2["bbox"][3] - b2["bbox"][1]:return Truereturn Falsedef extract_text_blocks(pdf_path):# 打开 PDF 文件pdf_document = fitz.open(pdf_path)# 存储文本块信息text_blocks = []line_blocks = []# 遍历 PDF 中的每一页for page_number in range(len(pdf_document)):page = pdf_document.load_page(page_number)# 获取文本块和行块信息blocks = page.get_text("dict")["blocks"]# 对当前页面内的文本块按照坐标进行排序blocks.sort(key=lambda x: (x['bbox'][3], x['bbox'][0]))for i in range(len(blocks)):for l in blocks[i]["lines"]:line_blocks.append({"line": l["spans"],"bbox": l["bbox"],"height": l["bbox"][3] - l["bbox"][1],  # 计算行块的高度"page_number": page_number + 1  # 记录页码信息})text_blocks.append({"block": blocks[i]["lines"],"bbox": blocks[i]["bbox"],"page_number": page_number + 1  # 记录页码信息})# 关闭 PDF 文件pdf_document.close()return text_blocks, line_blocksdef is_same_paragraph(line1, line2):# 判断相邻行是否属于同一个段落的逻辑# 这里提供一个简单的示例,你可以根据实际情况调整和扩展# 判断两行之间的垂直间距是否小于某个阈值vertical_threshold = 5  # 垂直间距阈值,根据实际情况调整if abs(line1['bbox'][3] - line2['bbox'][1]) < vertical_threshold:return Truereturn False# 示例用法pdf_path = "D:\\angus\\py\\困难pdf节选西藏奇正2022.pdf"
text_blocks, line_blocks = extract_text_blocks(pdf_path)# 用于检查两个文本块中的行是否相同
def check_lines_same(block1, block2):num_lines_block1 = len(block1["block"])num_lines_block2 = len(block2["block"])return num_lines_block1 == num_lines_block2# 收集打印的 JSON
printed_json_list = []for index, block in enumerate(text_blocks):# 获取当前文本块中行的个数num_lines = len(block["block"])# 如果当前文本块是表格,则继续检查下一个文本块是否是表格if num_lines > 1 and index < len(text_blocks) - 1:  # 需要多于一行,并且不是最后一个文本块next_block = text_blocks[index + 1]if check_lines_same(block, next_block):# 如果下一个文本块也是表格,则跳过,不进行打印输出continue# 如果当前文本块不是表格,则添加到打印的 JSON 列表中block_info = {"block_index": index + 1,"page_number": block['page_number'],"lines": [line['spans'] for line in block['block']],"bbox": block['bbox']}print(block_info)printed_json_list.append(block_info)previous_json = None  # 用于记录上一个非空 JSONfor printed_json in printed_json_list:# 获取 lines 的最后一个对象last_line_array = printed_json["lines"][-1]# 获取最后一个对象中的最后一个对象last_object_in_last_line = last_line_array[-1]# 获取最后一个对象中的 text 字段的值text_value = last_object_in_last_line["text"]# 输出截取到的最后一个text值#print("text字段的取值为>>>>>>>>>>>>..:", text_value)if text_value.strip() == "":# 如果 text_value 为空,则打印当前 JSONif previous_json is not None:# 合并当前 JSON 到上一个非空 JSON 上previous_json["lines"].extend(printed_json["lines"])previous_json["bbox"] = [min(previous_json["bbox"][0], printed_json["bbox"][0]),min(previous_json["bbox"][1], printed_json["bbox"][1]),max(previous_json["bbox"][2], printed_json["bbox"][2]),max(previous_json["bbox"][3], printed_json["bbox"][3])]# 更新页码信息previous_json["page_number"] = printed_json["page_number"]print(json.dumps(previous_json, ensure_ascii=False))# 重置jsonprevious_json = Noneelse:print(json.dumps(printed_json, ensure_ascii=False))     else:# 如果 text_value 不为空,则合并当前 JSON 到上一个非空 JSON 上if previous_json is not None:# 合并当前 JSON 到上一个非空 JSON 上previous_json["lines"].extend(printed_json["lines"])previous_json["bbox"] = [min(previous_json["bbox"][0], printed_json["bbox"][0]),min(previous_json["bbox"][1], printed_json["bbox"][1]),max(previous_json["bbox"][2], printed_json["bbox"][2]),max(previous_json["bbox"][3], printed_json["bbox"][3])]# 更新页码信息previous_json["page_number"] = printed_json["page_number"]else:# 如果没有上一个非空 JSON,则将当前 JSON 赋值给上一个非空 JSONprevious_json = printed_json

相关文章:

获取PDF中的布局信息——如何获取段落

PDF解析是极其复杂的问题。不可能靠一个工具解决全部问题&#xff0c;尤其是五花八门&#xff0c;格式不统一的PDF文件。除非有钞能力。如果没有那就看看可以分为哪些问题。 提取文本内容&#xff0c;提取表格内容&#xff0c;提取图片。我认为这些应该是分开做的事情。python有…...

Laya2.13.3在Web条件下使用键盘控制相机移动

需求&#xff1a;在Laya开发时&#xff0c;常常没法移动相机来观察场内的环境&#xff0c;故制作一个移动相机的脚本来是实现此功能&#xff0c;目前先使用键盘后续会添加鼠标控制移动旋转等功能。 onEnable(){this.camera new Laya.Camera(0, 0.1, 100);this._tempVector3 n…...

centos系统服务器在Jenkins执行playwright UI自动化测试框架

centos系统服务器在Jenkins执行playwright UI自动化测试框架 1. centos7.9系统中安装playwright环境报错 playwright/driver/node: /lib64/libc.so.6: version `GLIBC_2.25 not found经过查找资料,playwright 仅支持Ubuntu系统,其他的Linux服务器系统不支持,为此采用docke…...

boost.redis崩溃的解决方法

使用boost.redis的协程一定要co_spawn在strand对象中。 正确的用法&#xff1a; boost::asio::co_spawn(boost::dasio::make_strand(ioc),XXXCoroutine(),boost::asio::detached ); 错误的用法&#xff1a; boost::asio::co_spawn(ioc,XXXCoroutine(),boost::asio::detache…...

蓝桥杯——123

123 二分等差数列求和前缀和数组 题目分析 连续一段的和我们想到了前缀和&#xff0c;但是这里的l和r的范围为1e12&#xff0c;明显不能用O(n)的时间复杂度去求前缀和。那么我们开始观察序列的特点&#xff0c;可以按照等差数列对序列进行分块。如上图&#xff0c;在求前10个…...

嵌入式基础知识-信号量,PV原语与前趋图

本篇来介绍信号量与PV原语的一些知识&#xff0c;并介绍其在前趋图上的应用分析。本篇的知识属于操作系统部分的通用知识&#xff0c;在嵌入式软件开发中&#xff0c;同样会用到这些知识。 1 信号量 信号量是最早出现的用来解决进程同步与互斥问题的机制&#xff08;可以把信…...

代码遗产:探索祖传代码的历史、挑战与现代融合艺术

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua&#xff0c;在这里我会分享我的知识和经验。&#x…...

Vue3:用vite创建Vue3项目

一、简介 vite是新一代前端构建工具&#xff0c;官网地址&#xff1a;https://vitejs.cn vite的优势如下&#xff1a; 轻量快速的热重载&#xff08;HMR&#xff09;&#xff0c;能实现极速的服务启动。对 TypeScript、JSX、CSS 等支持开箱即用。真正的按需编译&#xff0c;不…...

STM32 (2)

1.stm32编程模型 将C语言程序烧录到芯片中会存储在单片机的flsah存储器中&#xff0c;给芯片上电后&#xff0c;Flash中的程序会逐条进入到CPU中去执行&#xff0c;进而CPU去控制各种模块&#xff08;即外设&#xff09;去实现各种功能。 2.寄存器和寄存器编程 CPU通过控制其…...

docker部署nginx+反向代理配置/代理宿主机网段服务器

1、安装docker&#xff0c;并运行 2、拉取nginx镜像 docker pull nginx3、运行nginx容器&#xff0c;将文件拷贝至本地&#xff0c;并将nginx容器删除 #运行nginx容器 docker run -id --name mynginx -p 8080:80 nginx#将配置文件从容器内拷贝至本地 docker cp 容器ID:/et…...

初识Hive

官网地址为&#xff1a; Design - Apache Hive - Apache Software Foundation 一、架构 先来看下官网给的图&#xff1a; 图上显示了Hive的主要组件及其与Hadoop的交互。Hive的主要组件有&#xff1a; UI&#xff1a; 用户向系统提交查询和其他操作的用户界面。截至2011年&…...

Google发布Genie硬杠Sora:通过大量无监督视频训练最终生成可交互虚拟世界

前言 Sora 问世才不到两个星期&#xff0c;谷歌的世界模型也来了&#xff0c;能力看似更强大&#xff1a;它生成的虚拟世界自主可控 第一部分 首个基础世界模型Genie 1.1 Genie是什么 Genie是第一个以无监督方式从未标记的互联网视频中训练的生成式交互环境(the first gener…...

全球首台!未磁科技256通道无液氦脑磁图仪及芯片化原子磁力计正式发布

2024年2月3日&#xff0c;由北京未磁科技有限公司牵头的国家重点研发计划诊疗装备与生物医用材料重点专项“新型无液氦脑磁图系统研发”项目2023年度总结会暨2024年推进会顺利召开。会上发布了项目取得的重大成果——全球首台256通道无液氦脑磁图仪Marvel MEG Pro。此项重磅成果…...

openssl3.2 - exp - 内存操作(建立,写入,读取)配置

文章目录 openssl3.2 - exp - 内存操作(建立,写入,读取)配置概述笔记调试细节运行效果测试工程实现main.cppCMyOsslConfig.hCMyOsslConfig.cppEND openssl3.2 - exp - 内存操作(建立,写入,读取)配置 概述 我的应用的配置文件是落地加密的, 无法直接用openssl配置接口载入读取…...

前端食堂技术周刊第 114 期:Interop 2024、TS 5.4 RC、2 月登陆浏览器的新功能、JSR、AI SDK 3.0

美味值&#xff1a;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f; 口味&#xff1a;凉拌鸡架 食堂技术周刊仓库地址&#xff1a;https://github.com/Geekhyt/weekly 大家好&#xff0c;我是童欧巴。欢迎来到前端食堂技术周刊&#xff0c;我们先来看下…...

#QT(信号与槽)

1.IDE&#xff1a;QTCreator 2.实验:自动添加槽函数&#xff0c;手动添加槽函数 3.记录 &#xff08;1&#xff09;自动添加 a.拖拽widget.ui&#xff0c;放置push-button组件&#xff0c;并且自动生成槽函数 b.发现widget.cpp和widget.h中出现添加的槽函数&#xff0c;注意w…...

go 设置滚动日志

方案 通过 log/slog 实现结构化日志生成&#xff0c;这是go1.21中推出的新特性&#xff1b;通过 lumberjack 实现日志文件分割。 示例 package mainimport ("gopkg.in/natefinch/lumberjack.v2""log/slog""os""path/filepath" )fun…...

Rollup入门学习:前端开发的构建利器

在前端开发领域&#xff0c;构建工具对于优化项目结构和提升代码效率扮演着至关重要的角色。Rollup作为一款轻量级且功能强大的JavaScript模块打包器&#xff0c;近年来备受开发者青睐。本文将带你走进Rollup的世界&#xff0c;帮助你快速入门并掌握其核心用法。 一、Rollup简介…...

游戏寻路之A*算法(GUI演示)

一、A*算法介绍 A*算法是一种路径搜索算法,用于在图形网络中找到最短路径。它结合了Dijkstra算法和启发式搜索的思想,通过综合利用已知的最短路径和估计的最短路径来优化搜索过程。在游戏自动寻路得到广泛应用。 二、A*算法的基本思想 在图形网络中选择一个起点和终点。维护…...

软件工程顶会——ICSE '24 论文清单、摘要

1、A Comprehensive Study of Learning-based Android Malware Detectors under Challenging Environments 近年来&#xff0c;学习型Android恶意软件检测器不断增多。这些检测器可以分为三种类型&#xff1a;基于字符串、基于图像和基于图形。它们大多在理想情况下取得了良好的…...

AI 术语通俗词典:矩阵乘法

矩阵乘法是线性代数、数据分析、机器学习和人工智能中非常核心的一个术语。它用来描述两组二维数值结构之间的一种特定运算规则。这个运算结果仍然是一个矩阵&#xff0c;但它并不是简单地把对应位置的元素相乘&#xff0c;而是通过“行与列”的组合来生成新的数值。如果说矩阵…...

如何选择适合你的Python Web服务器:uvicorn与gunicorn深度对比

1. 为什么需要关注Web服务器选择&#xff1f; 当你用Python开发完一个Web应用后&#xff0c;最后一步就是把它部署上线。这时候你会发现&#xff0c;直接运行python app.py这种方式根本撑不住几个用户访问。我刚开始做项目时就犯过这个错误&#xff0c;结果上线当天服务器就直接…...

OpenClaw+Qwen3.5-9B:个人知识库自动更新系统

OpenClawQwen3.5-9B&#xff1a;个人知识库自动更新系统 1. 为什么需要自动化知识管理 作为一个长期使用Obsidian管理个人知识库的用户&#xff0c;我经常面临一个痛点&#xff1a;收集的资料越来越多&#xff0c;但整理的时间却越来越少。每天订阅的几十个RSS源、技术博客和…...

从“中式英语”到地道表达:我用Notion搭建了一个动态写作原则库

从“中式英语”到地道表达&#xff1a;我用Notion搭建了一个动态写作原则库 第一次参加国际学术会议时&#xff0c;我站在海报前手足无措——不是研究内容不够扎实&#xff0c;而是当外国学者用"Your findings are intriguing but the methodology section lacks clarity&…...

从 0 到 1 构建你的第一个 AI Agent 项目——完整实战指南

【AI 开发】从 0 到 1 构建你的第一个 AI Agent 项目&#xff08;2026 最新实战指南&#xff09; 摘要 想做一个能写进简历的 AI Agent 项目&#xff0c;但不知道从哪开始&#xff1f;本文从项目选择、架构设计、技术选型到落地表达&#xff0c;给你一套完整的方法论。避开&q…...

vscode下载+插件

作为一款轻量又强大的代码编辑器&#xff0c;VSCode 是程序员入门的必备工具&#xff0c;但很多新手第一步就卡在了下载慢、不知怎么下的问题上。这篇指南从官方下载、国内加速下载&#xff0c;到简单的安装注意事项进行讲述。 一、下载 VSCode 的官方下载渠道唯一且安全&…...

linux——PV操作

int semop(int semid ,struct sembuf *sops ,size_t nsops); //用户改变信号量的值。也就是使用资源还是释放资源使用权 包含头文件&#xff1a; include<sys/sem.h> 参数&#xff1a; semid : 信号量的标识码。也就是semget&#xff08;&#xff09;的返回值 sops是一…...

MogFace-large部署案例:嵌入式AI盒子(Jetson Orin)上轻量运行方案

MogFace-large部署案例&#xff1a;嵌入式AI盒子&#xff08;Jetson Orin&#xff09;上轻量运行方案 1. 项目背景与价值 人脸检测技术在实际应用中面临着一个关键挑战&#xff1a;如何在资源受限的嵌入式设备上实现高精度实时检测。传统方案往往需要在精度和速度之间做出妥协…...

实战应用:基于快马平台开发战网服务状态监控与修复管理系统

实战应用&#xff1a;基于快马平台开发战网服务状态监控与修复管理系统 最近在游戏公司做运维的朋友经常抱怨战网更新服务莫名其妙进入睡眠模式的问题&#xff0c;每次都需要手动唤醒&#xff0c;特别影响工作效率。于是我用InsCode(快马)平台开发了一个完整的服务监控与修复管…...

用快马AI将开源下载想法秒变可运行Web应用原型

最近在尝试快速搭建一个待办事项Web应用原型时&#xff0c;发现了一个特别高效的开发方式——用InsCode(快马)平台的AI能力直接把想法变成可运行的项目。整个过程就像搭积木一样简单&#xff0c;特别适合需要快速验证想法的场景。下面分享下我的实践过程&#xff1a; 明确需求 …...