从pdf读取表格

R
Author

Bowen Qin

Published

April 10, 2024

library(tidyverse)
library(docxtractr)

最近看到烟草局招聘,想报个名,结果发现招聘信息表都放在pdf里,不方便做筛选,于是想用R来提取数据框并处理一下。

1 提取pdf中数据框的常见方案

回顾用R从pdf中读取数据的常见方案及其弊端:

  1. pdftools读取文本,再整理成数据框

    • 实际上就是用正则表达式清洗文本数据,步骤比较繁琐。

    • 表格页数较多或者表格的格式较复杂的时候容易出错。

  2. tabulizer从pdf中提取数据框

    • 需要配置java,非常麻烦。

    • 提取的结果不准确。

  3. 连接pdftables调用api

    • 需要在网站上注册,每次使用还需要输入账号和密码。
    • 提取的结果不准确。

下面这个表格存在单元格内换行的情况,用pdftools根本就无从下手,用tabulizerpdftables也容易出错。

2docxtractr提取

我发现了一个包doxtractr可以很轻松的提取word中的数据,对于一些格式复杂的pdf可以转换成word,然后用doxtractr包读取数据。

为什么pdf转换成word后就能准确提取表格了呢?我猜测这是因为pdf是一种矢量结构,不具备并不会用code注明哪些元素是表格哪些元素是文字,pdf中的元素只有位置的区别而没有属性的区别。而word会标明哪些元素是表格,就像在编程里声明了表格这种class,因此容易提取。

将pdf用word打开并另存为word后,用docxtratr包的read_docx()读入

docs <- read_docx("071052144z0f.docx")
docs
Word document [071052144z0f.docx]

Table 1
  total cells: 88
  row count  : 8
  uniform    : likely!
  has header : likely! => possibly [用人单位, 具体单位, 岗位名称, 岗位简介(主要职责描述), 岗位代码, 招聘人数, 所需专业名称, 学历学位, 政治面貌, 毕业年限, 备注]

Table 2
  total cells: 88
  row count  : 8
  uniform    : likely!
  has header : likely! => possibly [用人单位, 具体单位, 岗位名称, 岗位简介(主要职责描述), 岗位代码, 招聘人数, 所需专业名称, 学历学位, 政治面貌, 毕业年限, 备注]

Table 3
  total cells: 88
  row count  : 8
  uniform    : likely!
  has header : likely! => possibly [用人单位, 具体单位, 岗位名称, 岗位简介(主要职责描述), 岗位代码, 招聘人数, 所需专业名称, 学历学位, 政治面貌, 毕业年限, 备注]

Table 4
  total cells: 88
  row count  : 8
  uniform    : likely!
  has header : likely! => possibly [用人单位, 具体单位, 岗位名称, 岗位简介(主要职责描述), 岗位代码, 招聘人数, 所需专业名称, 学历学位, 政治面貌, 毕业年限, 备注]

Table 5
  total cells: 88
  row count  : 8
  uniform    : likely!
  has header : likely! => possibly [用人单位, 具体单位, 岗位名称, 岗位简介(主要职责描述), 岗位代码, 招聘人数, 所需专业名称, 学历学位, 政治面貌, 毕业年限, 备注]

Table 6
  total cells: 88
  row count  : 8
  uniform    : likely!
  has header : likely! => possibly [用人单位, 具体单位, 岗位名称, 岗位简介(主要职责描述), 岗位代码, 招聘人数, 所需专业名称, 学历学位, 政治面貌, 毕业年限, 备注]

Table 7
  total cells: 77
  row count  : 7
  uniform    : likely!
  has header : likely! => possibly [用人单位, 具体单位, 岗位名称, 岗位简介(主要职责描述), 岗位代码, 招聘人数, 所需专业名称, 学历学位, 政治面貌, 毕业年限, 备注]

Table 8
  total cells: 77
  row count  : 7
  uniform    : likely!
  has header : likely! => possibly [用人单位, 具体单位, 岗位名称, 岗位简介(主要职责描述), 岗位代码, 招聘人数, 所需专业名称, 学历学位, 政治面貌, 毕业年限, 备注]

Table 9
  total cells: 77
  row count  : 7
  uniform    : likely!
  has header : likely! => possibly [用人单位, 具体单位, 岗位名称, 岗位简介(主要职责描述), 岗位代码, 招聘人数, 所需专业名称, 学历学位, 政治面貌, 毕业年限, 备注]

Table 10
  total cells: 77
  row count  : 7
  uniform    : likely!
  has header : likely! => possibly [用人单位, 具体单位, 岗位名称, 岗位简介(主要职责描述), 岗位代码, 招聘人数, 所需专业名称, 学历学位, 政治面貌, 毕业年限, 备注]

Table 11
  total cells: 77
  row count  : 7
  uniform    : likely!
  has header : likely! => possibly [用人单位, 具体单位, 岗位名称, 岗位简介(主要职责描述), 岗位代码, 招聘人数, 所需专业名称, 学历学位, 政治面貌, 毕业年限, 备注]

Table 12
  total cells: 77
  row count  : 7
  uniform    : likely!
  has header : likely! => possibly [用人单位, 具体单位, 岗位名称, 岗位简介(主要职责描述), 岗位代码, 招聘人数, 所需专业名称, 学历学位, 政治面貌, 毕业年限, 备注]

Table 13
  total cells: 77
  row count  : 7
  uniform    : likely!
  has header : likely! => possibly [用人单位, 具体单位, 岗位名称, 岗位简介(主要职责描述), 岗位代码, 招聘人数, 所需专业名称, 学历学位, 政治面貌, 毕业年限, 备注]

Table 14
  total cells: 77
  row count  : 7
  uniform    : likely!
  has header : likely! => possibly [用人单位, 具体单位, 岗位名称, 岗位简介(主要职责描述), 岗位代码, 招聘人数, 所需专业名称, 学历学位, 政治面貌, 毕业年限, 备注]

Table 15
  total cells: 77
  row count  : 7
  uniform    : likely!
  has header : likely! => possibly [用人单位, 具体单位, 岗位名称, 岗位简介(主要职责描述), 岗位代码, 招聘人数, 所需专业名称, 学历学位, 政治面貌, 毕业年限, 备注]

Table 16
  total cells: 77
  row count  : 7
  uniform    : likely!
  has header : likely! => possibly [用人单位, 具体单位, 岗位名称, 岗位简介(主要职责描述), 岗位代码, 招聘人数, 所需专业名称, 学历学位, 政治面貌, 毕业年限, 备注]

Table 17
  total cells: 77
  row count  : 7
  uniform    : likely!
  has header : likely! => possibly [用人单位, 具体单位, 岗位名称, 岗位简介(主要职责描述), 岗位代码, 招聘人数, 所需专业名称, 学历学位, 政治面貌, 毕业年限, 备注]

Table 18
  total cells: 77
  row count  : 7
  uniform    : likely!
  has header : likely! => possibly [用人单位, 具体单位, 岗位名称, 岗位简介(主要职责描述), 岗位代码, 招聘人数, 所需专业名称, 学历学位, 政治面貌, 毕业年限, 备注]

Table 19
  total cells: 77
  row count  : 7
  uniform    : likely!
  has header : likely! => possibly [用人单位, 具体单位, 岗位名称, 岗位简介(主要职责描述), 岗位代码, 招聘人数, 所需专业名称, 学历学位, 政治面貌, 毕业年限, 备注]

Table 20
  total cells: 77
  row count  : 7
  uniform    : likely!
  has header : likely! => possibly [用人单位, 具体单位, 岗位名称, 岗位简介(主要职责描述), 岗位代码, 招聘人数, 所需专业名称, 学历学位, 政治面貌, 毕业年限, 备注]

Table 21
  total cells: 77
  row count  : 7
  uniform    : likely!
  has header : likely! => possibly [用人单位, 具体单位, 岗位名称, 岗位简介(主要职责描述), 岗位代码, 招聘人数, 所需专业名称, 学历学位, 政治面貌, 毕业年限, 备注]

Table 22
  total cells: 77
  row count  : 7
  uniform    : likely!
  has header : likely! => possibly [用人单位, 具体单位, 岗位名称, 岗位简介(主要职责描述), 岗位代码, 招聘人数, 所需专业名称, 学历学位, 政治面貌, 毕业年限, 备注]

Table 23
  total cells: 77
  row count  : 7
  uniform    : likely!
  has header : likely! => possibly [用人单位, 具体单位, 岗位名称, 岗位简介(主要职责描述), 岗位代码, 招聘人数, 所需专业名称, 学历学位, 政治面貌, 毕业年限, 备注]

Table 24
  total cells: 77
  row count  : 7
  uniform    : likely!
  has header : likely! => possibly [用人单位, 具体单位, 岗位名称, 岗位简介(主要职责描述), 岗位代码, 招聘人数, 所需专业名称, 学历学位, 政治面貌, 毕业年限, 备注]

Table 25
  total cells: 77
  row count  : 7
  uniform    : likely!
  has header : likely! => possibly [用人单位, 具体单位, 岗位名称, 岗位简介(主要职责描述), 岗位代码, 招聘人数, 所需专业名称, 学历学位, 政治面貌, 毕业年限, 备注]

Table 26
  total cells: 77
  row count  : 7
  uniform    : likely!
  has header : likely! => possibly [用人单位, 具体单位, 岗位名称, 岗位简介(主要职责描述), 岗位代码, 招聘人数, 所需专业名称, 学历学位, 政治面貌, 毕业年限, 备注]

Table 27
  total cells: 33
  row count  : 3
  uniform    : likely!
  has header : likely! => possibly [用人单位, 具体单位, 岗位名称, 岗位简介(主要职责描述), 岗位代码, 招聘人数, 所需专业名称, 学历学位, 政治面貌, 毕业年限, 备注]
No comments in document

可以看到文档中的表格被自动识别,并且推断出的表格的表头都是正确的。

使用docx_extract_tbl()提取表格

docx_extract_tbl(docs, tbl_number = 1)
# A tibble: 7 × 11
  用人单位            具体单位 岗位名称 岗位简介.主要职责描述. 岗位代码 招聘人数
  <chr>               <chr>    <chr>    <chr>                  <chr>    <chr>   
1 河南省烟草专卖局(… 内设机构 经济运…  负责经济运行管理等工…  10101    1       
2 河南省烟草专卖局(… 内设机构 科技项…  负责科技项目管理等工…  10102    1       
3 河南省烟草专卖局(… 内设机构 企业法…  负责法治建设等工作。   10103    1       
4 河南省烟草专卖局(… 内设机构 资金管…  负责会计核算工作;负…  10104    1       
5 河南省烟草专卖局(… 内设机构 营销管…  负责卷烟营销数据采集…  10105    1       
6 河南省烟草专卖局(… 内设机构 营销管…  负责卷烟营销数据采集…  10106    1       
7 河南省烟草专卖局(… 内设机构 综合管…  负责行政后勤管理等工…  10107    1       
# ℹ 5 more variables: 所需专业名称 <chr>, 学历学位 <chr>, 政治面貌 <chr>,
#   毕业年限 <chr>, 备注 <chr>

这里我们的pdf有27页,每页的表格都是固定的表头,可以用docx_extract_all_tbls()提取全部表格再按行合并。

tbls <- docx_extract_all_tbls(docs)
recruitment <- bind_rows(tbls)
recruitment
# A tibble: 164 × 11
   用人单位          具体单位 岗位名称  岗位简介.主要职责描述. 岗位代码 招聘人数
   <chr>             <chr>    <chr>     <chr>                  <chr>    <chr>   
 1 河南省烟草专卖局… 内设机构 经济运行… 负责经济运行管理等工…  10101    1       
 2 河南省烟草专卖局… 内设机构 科技项目… 负责科技项目管理等工…  10102    1       
 3 河南省烟草专卖局… 内设机构 企业法律… 负责法治建设等工作。   10103    1       
 4 河南省烟草专卖局… 内设机构 资金管理… 负责会计核算工作;负…  10104    1       
 5 河南省烟草专卖局… 内设机构 营销管理… 负责卷烟营销数据采集…  10105    1       
 6 河南省烟草专卖局… 内设机构 营销管理… 负责卷烟营销数据采集…  10106    1       
 7 河南省烟草专卖局… 内设机构 综合管理… 负责行政后勤管理等工…  10107    1       
 8 河南省烟草专卖局… 内设机构 网络安全… 负责网络和信息安全等…  10108    1       
 9 河南省烟草专卖局… 内设机构 质量监督… 负责烟草制品质量监督…  10109    1       
10 中国烟草河南进出… 内设机构 会计(审… 负责会计核算工作;负…  10110    1       
# ℹ 154 more rows
# ℹ 5 more variables: 所需专业名称 <chr>, 学历学位 <chr>, 政治面貌 <chr>,
#   毕业年限 <chr>, 备注 <chr>

数据的提取到这里就完成了,可以进行下一步的分析,或者将数据导出成excel。