从 pdf 中提取表格信息、合并、解析、输出

  • pdf 格式浅述
    • word 文档 与 pdf
    • pdf 文档撰写的优点与难点
  • 从 pdf 中抽取表格所在的页
  • 从 pdf 中抽取表格
  • 合并表格、解析表格、生成输出数据
    • 进一步的反思
    • 用 perl 来完成文字处理

pdf 格式浅述

pdf 作为一种使用极为广泛的【可移植文件格式】,常用于各种用户手册及专业文档的撰写。可能很多人都认为 pdf 只是一些图片的打包合集,这其实是对 pdf 格式的一种误解。

作为专业的文档格式,pdf 中支持插入各种【图表、公式】等。完全使用图片来构建一个 pdf 仅仅使用了 pdf 中很小的一部分功能,且生成的 pdf 的内容不能直接复制。

word 文档 与 pdf

也许很多人在日常办公中经常接触到 pdf ,经常扫描文件生成扫描版 pdf。word 文档可能还经常撰写,但 pdf 却从来没有自己编写过。实际上对于专业性要求不高的内容, word 文档的撰写确实要比 pdf 文档更容易,图形化的排版过程隐藏了背后复杂的命令,更方便用户使用。

word 文档的优点一目了然,可在专业的文档撰写中 word 文档却常常被诟病。实际上,使用 word 排版专业文档并不容易。每次排版设定的格式可复用性不高,保存的文件格式也极不便于进行版本控制。

pdf 文档撰写的优点与难点

pdf 文档弥补了 word 的上述缺陷,但 pdf 撰写的门槛却比 word 高出许多,这也就意味着你几乎不太可能只【点点鼠标】就能排版出一篇文章,你需要对排版系统进行学习,了解各种命令。虽然这种学习会耗费许多的时间与精力,可学到的知识能够一直使用下去,同时你对排版本身的各种原理也能有更进一步的认识。

使用过 Latex 排版系统的人可能对上面的描述有切身的体会。使用 Latex 来撰写一篇 pdf 文档时,你需要用到许多的宏与命令来对文档格式进行精细的控制,这比 word 中点点鼠标就能排版更加困难,也更加接近排版本身。

我不对排版系统进行更进一步的叙述,感兴趣的读者可以搜索相关的网页进一步了解。

从 pdf 中抽取表格所在的页

常见的图文混排 pdf 中,表格零散的分布在 pdf 文档的各个部分。我们需要从 pdf 文档中将需要抽取的表格所在的页【抽取】出来。这一过程可以通过一些高级的 【pdf 编辑器】来完成。我没有使用 【pdf 编辑器】,我使用了一种命令行工具——【pdftk】来完成了这一任务。下面是相关的操作命令:

sudo apt-get install pdftk
pdftk in.pdf cat 50-60 output out.pdf

命令说明文档请 man pdftk

从 pdf 中抽取表格

从一个大的 pdf 中抽取出来表格所在的页后,我们就可以从生成的 pdf 中抽取表格内容。注意抽出来的内容不能是扫描生成的图片,如果是图片将抽取失败!!!

pdf 格式有自己的文件格式规范,这一规范能够在网上找到。从 pdf 中抽取信息的参考便是这一文件格式规范。在这里我并没有尝试自己去撰写一个抽取工具,我直接使用了网页搜索得到的方法。

我使用 【tabula】 来从 pdf 中抽取表格信息,关于此模块的安装使用详见——【使用 tabula 脚本从 pdf 中提取表格信息为 python 数据帧】。

在安装使用过程中有如下【注意事项】:

  1. python-numpy 版本问题

    如果你的系统中安装了多个版本的 【python-numpy】,请参考 【如何升级 numpy】 中的回答来解决。

  2. java 环境依赖问题

    由于我已经安装并配置了 java 环境,我没有遇到这一问题。遇到这个问题的读者请自行 google、百度解决。

【tabula】 可以将表格信息输出为四种格式:

1. python DataFrame
2. json
3. tsv
4. csv

在这里我使用 csv 作为输出格式。注意当待抽取的 pdf 文档有多页时,需要设定 pages 属性为 “all”’。

我使用 【tabula】 从下图中的 pdf 表格中提取信息。
提取的信息中的部分内容如下:

176,Pin,DEFAUL,ALT0,ALT1,ALT2,ALT3,ALT4,ALT5,ALT6,ALT7
VFBGA,Name,T,
C1,PTB3,LPADC0_,LPADC0_,PTB3/,LPSPI0_,LPUART1,I2S0_TX_,FB_AD10,TPM0_C,—
“”,SE0,SE0,RF0_EXT,PCS3,TX,FS,H1, “”,OSC_E, “”,N,
C2,PTB4/,LPADC0,LPADC0,PTB4/,LPSPI0_,LPUART1,I2S0_TX_,FB_AD9,TPM0_C,—
“”,LLWU_P6,SE1,SE1,LLWU_P6,SCK,CTS,BCLK,H2, “”,/,
“”,RF0_RF
, “”,OFF/, “”,RF0_DFT,
“”,RESET,
D2,PTB5,DISABLE,—,PTB5/,LPSPI0
,LPUART1,I2S0_MC,FB_AD8,TPM0_C,—
“”,D,RF0_ACT,SOUT,_RTS,LK,H3, “”,IVE,

上面的输出有点奇怪,它与表格的内容看上去并不一致。如果你仔细观察,你会发现当表格中单行的数据由多个行组成时,每一个行都被单独的提取出来了,这是 【tabula】 的一个缺陷,不过这并不致命,只要信息是正确的,那么编写代码将数据合并起来就行了。

合并表格、解析表格、生成输出数据

从上面抽取出来的表格信息中每一行的不同项之间通过逗号分割,使用 awk 来处理这种具有多个字段的信息相对容易,我就使用 awk 脚本来合并被分开的行,并完成【解析与输出】的任务。

代码如下:

#!/usr/bin/gawk -fBEGIN {parse_and_generate()exit 0
}function make_output_result(hash_table, buffer, gpio_name)
{gpio_name = hash_table["GPIONAME"]if (hash_table["PIN_MUX_ALT0"] != "") {buffer = sprintf("#define %8s_%-26s%s(%s)\n\n", gpio_name,hash_table["PIN_MUX_ALT0"],"RV32M1_VEGA_PIN_MUX","PIN_MUX_ALT0")}if (hash_table["PIN_MUX_ASGPIO"] != "") {buffer =  buffer sprintf("#define %8s_%-26s%s(%s)\n\n", gpio_name,"GPIO","RV32M1_VEGA_PIN_MUX","PIN_MUX_AS_GPIO")}if (hash_table["PIN_MUX_ALT2"] != "") {buffer = buffer sprintf("#define %8s_%-26s%s(%s)\n\n", gpio_name,hash_table["PIN_MUX_ALT2"],"RV32M1_VEGA_PIN_MUX","PIN_MUX_ALT2")}if (hash_table["PIN_MUX_ALT3"] != "") {buffer = buffer sprintf("#define %8s_%-26s%s(%s)\n\n", gpio_name,hash_table["PIN_MUX_ALT3"],"RV32M1_VEGA_PIN_MUX","PIN_MUX_ALT3")}if (hash_table["PIN_MUX_ALT4"] != "") {buffer = buffer sprintf("#define %8s_%-26s%s(%s)\n\n", gpio_name,hash_table["PIN_MUX_ALT4"],"RV32M1_VEGA_PIN_MUX","PIN_MUX_ALT4")}if (hash_table["PIN_MUX_ALT5"] != "") {buffer = buffer sprintf("#define %8s_%-26s%s(%s)\n\n", gpio_name,hash_table["PIN_MUX_ALT5"],"RV32M1_VEGA_PIN_MUX","PIN_MUX_ALT5")}if (hash_table["PIN_MUX_ALT6"] != "") {buffer = buffer sprintf("#define %8s_%-26s%s(%s)\n\n", gpio_name,hash_table["PIN_MUX_ALT6"],"RV32M1_VEGA_PIN_MUX","PIN_MUX_ALT6")}if (hash_table["PIN_MUX_ALT7"] != "") {buffer = buffer sprintf("#define %8s_%-26s%s(%s)\n\n", gpio_name,hash_table["PIN_MUX_ALT7"],"RV32M1_VEGA_PIN_MUX","PIN_MUX_ALT7")}buffer = buffer "\n\n"return buffer
}function eval_string(buffer, num,result_buffer,split_array, hash_table, i, j, len)
{for (i = 1; i <= num; i++) {if (buffer[i] == "") {continue}len = split(buffer[i], split_array, ",")gsub(/\/.*/, "", split_array[1])gsub(/PT/, "GPIO", split_array[1])gsub(/[0-9][0-9]*/, "_&", split_array[1])for (j = 2; j <= len; j++) {sub(/\/.*/, "", split_array[j])}hash_table["GPIONAME"] = split_array[1]hash_table["PIN_MUX_ALT0"] = split_array[2]hash_table["PIN_MUX_ASGPIO"] = split_array[3]hash_table["PIN_MUX_ALT2"] = split_array[4]hash_table["PIN_MUX_ALT3"] = split_array[5]hash_table["PIN_MUX_ALT4"] = split_array[6]hash_table["PIN_MUX_ALT5"] = split_array[7]hash_table["PIN_MUX_ALT6"] = split_array[8]hash_table["PIN_MUX_ALT7"] = split_array[9]result_buffer = result_buffer make_output_result(hash_table)}return result_buffer
}function parse_table(array_buffer, array_index, output_buffer,split_array, split_array_temp,output_index, i, j, len, start)
{if (array_index == 0) {return 0}start = 0output_index = 1for (i = 1; i < array_index; i++) {if (array_buffer[i] ~ /^[A-Z][0-9][0-9]*,PT.*/) {start = 1len = split(array_buffer[i], split_array, ",")} else if ((array_buffer[i] ~ /^""/) && (start == 1)) {len = split(array_buffer[i], split_array_temp, ",")for (j = 2; j <= len; j++) {if (j == 3) {continue}if (split_array_temp[j] != "\"\"" &&split_array_temp[j] != "") {split_array[j] = split_array[j] split_array_temp[j]}}}if (array_buffer[i + 1] !~ /^""/ && (start == 1)) {start = 0for (j = 2; j <= len; j++) {if (j == 2) {output_buffer[output_index] = split_array[j]} else if (j >= 4) {output_buffer[output_index] = output_buffer[output_index] ","\split_array[j]}}output_index++}}return output_index - 1
}function exchange_data(array, first, second,temp)
{temp = array[first]array[first] = array[second]array[second] = temp
}function transform_key(string)
{sub(/[,/].*/, "", string)if (!match(string, /[0-9][0-9]/)) {sub(/[0-9]/, "0&", string)}return string
}function sort_table(array, len,i, j, first_string, second_string)
{for (i = 1; i <= len; i++) {for (j = i + 1; j <= len; j++) {first_string = array[j]second_string = array[i]first_string = transform_key(first_string)second_string = transform_key(second_string)if (first_string < second_string) {exchange_data(array, j, i)}}}
}function uniq_table(table, num, i)
{for (i = 1; i <= num; i++) {if (table[i] == table[i + 1]) {table[i] == ""}}
}function read_component_then_parse(filename, output_buffer,buffer, i, num)
{i = 1if (filename == "") {return 0}while (getline < filename > 0) {buffer[i++] = $0 gsub(/—/, "", buffer[i - 1])}num = parse_table(buffer, i, output_buffer)sort_table(output_buffer, num)uniq_table(output_buffer, num)return num
}function parse_and_generate(result_buffer, output_filename, list_buffer, num, i)
{result_buffer = ""for (i = 1; i < ARGC; i++) {output_filename = ARGV[i] ".out"num = read_component_then_parse(ARGV[i], list_buffer)result_buffer = eval_string(list_buffer, num)printf("%s\n", result_buffer) > output_filenameclose(output_filename)}
}

【parse_and_generate】 函数中依次对命令行中以参数传递的待解析的文件名进行处理。首先读取文件内容到 【buffer】 中,然后解析数据,生成新的表格,并按照特定的 【key】 对表格的行进行排序,并对排序的结果进行 【uniq】 操作。

【read_component_then_parse】 将新的表格填充到 【list_buffer】 中,表项数量通过返回值返回。

【eval_string】 根据传入的表格信息进行 “求值” ,返回 “求值” 得到的输出数据。这个输出数据被输出到预先生成的输出文件名(输入文件名加后缀 .out)中。

执行上述脚本,得到的输出的部分信息如下:

#define  GPIOB_3_LPADC0_SE0                RV32M1_VEGA_PIN_MUX(PIN_MUX_ALT0)#define  GPIOB_3_GPIO                      RV32M1_VEGA_PIN_MUX(PIN_MUX_AS_GPIO)#define  GPIOB_3_LPSPI0_PCS3               RV32M1_VEGA_PIN_MUX(PIN_MUX_ALT2)#define  GPIOB_3_LPUART1_TX                RV32M1_VEGA_PIN_MUX(PIN_MUX_ALT3)#define  GPIOB_3_I2S0_TX_FS                RV32M1_VEGA_PIN_MUX(PIN_MUX_ALT4)#define  GPIOB_3_FB_AD10                   RV32M1_VEGA_PIN_MUX(PIN_MUX_ALT5)#define  GPIOB_3_TPM0_CH1                  RV32M1_VEGA_PIN_MUX(PIN_MUX_ALT6)#define  GPIOB_4_LPADC0_SE1                RV32M1_VEGA_PIN_MUX(PIN_MUX_ALT0)#define  GPIOB_4_GPIO                      RV32M1_VEGA_PIN_MUX(PIN_MUX_AS_GPIO)#define  GPIOB_4_LPSPI0_SCK                RV32M1_VEGA_PIN_MUX(PIN_MUX_ALT2)#define  GPIOB_4_LPUART1_CTS               RV32M1_VEGA_PIN_MUX(PIN_MUX_ALT3)#define  GPIOB_4_I2S0_TX_BCLK              RV32M1_VEGA_PIN_MUX(PIN_MUX_ALT4)#define  GPIOB_4_FB_AD9                    RV32M1_VEGA_PIN_MUX(PIN_MUX_ALT5)#define  GPIOB_4_TPM0_CH2                  RV32M1_VEGA_PIN_MUX(PIN_MUX_ALT6)#define  GPIOB_5_GPIO                      RV32M1_VEGA_PIN_MUX(PIN_MUX_AS_GPIO)#define  GPIOB_5_LPSPI0_SOUT               RV32M1_VEGA_PIN_MUX(PIN_MUX_ALT2)#define  GPIOB_5_LPUART1_RTS               RV32M1_VEGA_PIN_MUX(PIN_MUX_ALT3)#define  GPIOB_5_I2S0_MCLK                 RV32M1_VEGA_PIN_MUX(PIN_MUX_ALT4)#define  GPIOB_5_FB_AD8                    RV32M1_VEGA_PIN_MUX(PIN_MUX_ALT5)#define  GPIOB_5_TPM0_CH3                  RV32M1_VEGA_PIN_MUX(PIN_MUX_ALT6)

进一步的反思

虽然上述 awk 脚本输出了正确的结果,但是在编写过程中我发现一些问题很难找到。例如当你使用某个变量时,不小心打错了名字,那么程序便不能正常工作,而寻找这个问题的过程比较困难。

当然,对于变量名打错的问题,你可以通过 【–dump-variables】 参数来生成全局变量表来寻找问题。你也可以通过调试来解决这个问题。不过如果 awk 能够报个错或者警告,那你可能会更快的发现问题。上述脚本有 200 多行代码,算是一个比较复杂的脚本了。编写过程中遇到的问题最后也都在短时间内得到了解决,但我觉得很多问题都应该可以避免。

用 perl 来完成文字处理

awk 中还有很多我可能没有接触到的细节,但我觉得浸淫于这种技术一角来解决问题的方法依赖性过强。我们应该使用更为通用的方式来解决问题,而不是玩弄各种奇技淫巧。为此,以后关于文字处理的部分,对于相对复杂的逻辑,我会用 【perl】 来实现,【perl】 的文字处理能力毋庸置疑,只是标识符用的有点多啊!!!

从 pdf 中提取表格信息、合并、解析、输出数据相关推荐

  1. Camelot:从pdf中提取表格数据

    Camelot:从pdf中提取表格数据 文章目录: 一.Camelot的介绍和安装 1. Camelot介绍 2. Camelot的安装 3. 其他 二.Camelot的使用 1. 快速入门使用 2. ...

  2. python分析pdf年报 货币现金_如何用Python从大量pdf 中提取表格中的数据进行分析?...

    根据一楼答案@森林的建议 说说我的处理经验 我也是借助开源项目tabula,不得不说tabula的功能确实很强大. 我是用Python来处理数据,但是没有用tabula-py,因为表格跨列跨行等情况比 ...

  3. python pdfminer读取pdf表格_如何使用PDFMiner从pdf中提取表格?

    我正试图从pdf文档中的一些表中提取信息. 考虑输入:Title 1 some text some text some text some text some text some text some ...

  4. 怎么提取pdf中的表格数据_如何从pdf第1部分中提取表格数据

    怎么提取pdf中的表格数据 In this article, we talk about the challenges and principles of extracting tabular dat ...

  5. [小技巧] 使用tabula批量提取pdf中的表格

    想不到我也能更新这种东西了 原文链接~~~~~ [小技巧] 使用tabula批量提取pdf中的表格https://mp.weixin.qq.com/s/HWLneqJj42ywLghPR-ushA 起 ...

  6. Python提取pdf中的表格数据(附实战案例)

    14天阅读挑战赛 今天给大家介绍一个Python使用工具,那就是从pdf文件中读取表格数据,主要用到第三方库 pdfplumber. pdfplumber简介 pdfplumber是一款基于pdfmi ...

  7. python中提取几列_Python一键提取PDF中的表格到Excel(实例50)

    从PDF文件获取表格中的数据,也是日常办公容易涉及到的一项工作.一个一个复制吧,效率确实太低了.用Python从PDF文档中提取表格数据,并写入Excel文件,灰常灰常高效. 上市公司的年报往往包含几 ...

  8. 免费离线PDF工具箱,PDF工具大全,PDF合并PDF加密PDF解密PDF格式转换PDF分割PDF旋转以及从PDF中提取图片,满足对PDF操作的一切需求~完全免费无使用次数限制,文末附下载链接~

    一款 完全免费 的PDF工具箱,软件一共 内置45个和PDF文件操作相关的功能,无需注册登录 即可 免费使用,所有的功能都 不限制使用次数,你对PDF操作的大多数需求它都能满足!而且 所有操作均在本地 ...

  9. 用python 将PDF中的表格转化为Excel

    这几天想统计一下<中国人文社会科学期刊 AMI 综合评价报告(2018 年):A 刊评价报告>中的期刊,但是只找到了该报告的PDF版,对于表格的编辑不太方便,于是想到用Python将表格转 ...

最新文章

  1. A-FRAME初体验
  2. nginx+uwsgi+flask配置记录
  3. 工控设备 如何将数据发送到串口_嵌入式无风扇工控机在水质监测系统中的应用...
  4. 类成员_月隐学python第17课
  5. Error: Could not find or load main class org.elasticsearch.tools.JavaVersionChecker
  6. php tipask yii 单点登录_php实现单点登录实例
  7. python读取多行json_如何在Python中读取包含多个JSON对象的JSON文件?
  8. do while(false)实用技巧
  9. python四边形转矩形_python opencv minAreaRect 生成最小外接矩形的方法
  10. ●BZOJ 2393 Cirno的完美算数教室
  11. 即时通讯IM的安全性比较
  12. Window下完全卸载MySQL教程
  13. 3DS MAX 批量导出文件脚本 MAXScript 带界面
  14. BI、大数据、数字化转型傻傻分不清?5分钟带你了解区别和联系
  15. vue-router路由防卫
  16. 小写金额转为中文大写
  17. 测试学习笔记之--pytest使用和断言处理以及setup,theardown使用
  18. 新年财报“开门红”,谷歌的疫情复苏红利还能吃多久?
  19. Lesson 19 A very dear cat 内容鉴赏
  20. SAP-MM知识精解-计划协议-01

热门文章

  1. 我叫小M,立志建立MySQL帝国。
  2. Python读取Excel日期列读出来是数字的处理
  3. 聚划算影响淘宝搜索权重?客服:未接到通知
  4. 【实战】python-docx---每页表格固定显示行数
  5. 用卷积神经网络和自注意力机制实现QANet(问答网络)
  6. python语言不用编译_python程序不需要编译吗
  7. ESP32 LVGL8.1 ——Style multiple styles 多种风格样式 (Style 12)
  8. 云堡垒机的作用_阿里云堡垒机详解
  9. Oracle EBS Interface/API(35) -创建供应商地点API
  10. STM32开发 --- 1.8寸显示屏ST7735_输出英文、汉字、图片