使用 R 语言对扫描文档进行 OCR 并提取表格数据

之前也给大家介绍过其他的一些 OCR 方法:

Stata 版本:如何整理 2022 年县域统计年鉴:caj 文件转 pdf、文本识别与数据清洗:https://rstata.duanshu.com/#/brief/course/59fbd94072c844d985f0f4fcdbd4c40e

R 语言版本:如何整理 2022 年县域统计年鉴:caj 文件转 pdf、文本识别与数据清洗:https://rstata.duanshu.com/#/brief/course/c9cb314ab48e496da9761445ab4a28de

里面使用的方法准确性也不错,不过最近在处理《2009全国地市县财政统计资料》时感觉还不是非常好用,因此我又更换了新方法。

使用百度大脑的 OCR 接口解析的效果感觉更好,本次课我们将介绍如何在 Stata 中调用该接口从扫描文档中提取表格。

附件中的 一般预算1-4.pdf 文件是《2009全国地市县财政统计资料》书中一般预算部分的前 4 页。

这个文档是拍的照片,堪称是最难识别的了。

表格文字识别 V2 接口

该接口的介绍文档在这里:https://ai.baidu.com/ai-doc/OCR/Al1zvpylt 。

和之前介绍的情感倾向分析接口一样,该接口的使用也分为两步:

  1. 获取 access_token;
  2. 调用该接口。

创建应用

首先我们领取点免费的额度:https://console.bce.baidu.com/ai/#/ai/ocr/overview/index

每个月是有 1000 次的免费额度。如果觉得免费额度不够用,可以点击开通付费使用。

然后需要在控制台创建一个应用 :

创建后就可以看到这个应用了:

API Key 和 Secret Key 就是我们需要在代码中调用的东西,大家需要替换成自己的。

获取 accsee_token

根据接口的文档介绍,我们需要先根据 API Key 和 Secret Key 请求获取 access_token。

access_token 的获取方法可以参考这个文档:https://ai.baidu.com/ai-doc/REFERENCE/Ck3dwjhhu

这个接口返回的是 json 格式的数据:

library(tidyverse)
library(jsonlite)
library(httr)

fromJSON("https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=RLNEhRcWNZRrtuFP73I4XNJj&client_secret=kskm3ApVkV310o7AHjiRB1wONyxPTMG7") -> lst

lst$access_token
#> [1] "24.ba839bb84ef479e05be193415c554cb5.2592000.1724941738.282335-85431137"

链接里面的 client_id= 后面跟的就是 API key, client_secret= 后面跟的就是 Secret Key。

调用表格文字识别接口

然后就可以使用这个 access_token 调用表格文字识别接口了。不过在此之前,我们需要把 pdf 文件处理下。

按照文档的介绍,如果我们解析的是 PDF 文件:

PDF 文件,base64 编码后进行 urlencode,要求 base64 编码和 urlencode 后大小不超过 8M,最短边至少 15px,最长边最大 4096px

为了避免中文乱码的问题,我把 一般预算1-4.pdf 重命名为 rawpdf.pdf。另外为了避免 pdf 文档过大的问题,我们还是先把 pdf 文件拆分成一页页的:

library(pdftools)
dir.create("pdf")
pdf_split("rawpdf.pdf", output = "pdf/pdf")
#> [1] "pdf/pdf_1.pdf" "pdf/pdf_2.pdf" "pdf/pdf_3.pdf" "pdf/pdf_4.pdf"

base64 编码后进行 urlencode 操作可以通过下面的代码实现:

# 读取为二进制数据
pdf_file_path <- "pdf/pdf_1.pdf"
binary_data <- readBin(pdf_file_path, "raw", file.info(pdf_file_path)$size)
# 对二进制数据进行Base64编码 + URL 编码
base64_string <- URLencode(base64enc::base64encode(binary_data))

然后就可以调用百度接口进行 OCR 了:

POST(paste0("https://aip.baidubce.com/rest/2.0/ocr/v1/table?return_excel=true&access_token=", lst$access_token),
httr::add_headers(.headers = c(
'Content-Type' = 'application/x-www-form-urlencoded'
)),
body = list(pdf_file = base64_string)) -> res
content(res) %>%
listviewer::jsonedit()

得到的 excel 文件也是以加密字符串的形式,使用下面的代码就可以转换了:

content(res) -> excellst

excellst$excel_file %>%
base64_dec() %>%
writeBin("temp.xlsx")

然后就可以循环处理所有的 pdf 文件了:

fs::dir_ls("pdf") -> fls
dir.create("xlsx")
for (f in fls) {
print(f)
y <- str_replace_all(f, "pdf", "xlsx")
pdf_file_path <- f
binary_data <- readBin(pdf_file_path, "raw", file.info(pdf_file_path)$size)
# 对二进制数据进行Base64编码 + URL 编码
base64_string <- URLencode(base64enc::base64encode(binary_data))

# 调用接口
POST(paste0("https://aip.baidubce.com/rest/2.0/ocr/v1/table?return_excel=true&access_token=", lst$access_token),
httr::add_headers(.headers = c(
'Content-Type' = 'application/x-www-form-urlencoded'
)),
body = list(pdf_file = base64_string)) -> res

# 转换得到 xlsx
content(res) -> excellst

excellst$excel_file %>%
base64_dec() %>%
writeBin(y)
}
#> [1] "pdf/pdf_1.pdf"
#> [1] "pdf/pdf_2.pdf"
#> [1] "pdf/pdf_3.pdf"
#> [1] "pdf/pdf_4.pdf"

得到的 xlsx 文件较乱,还需要再手动整理下。

合并提取结果

xlsx2 文件夹中存放的就是我手工整理后的结果。数据分为两类,一类是收入表,一类是支出表。所以我们可以合并得到两个表:

# 合并
fs::dir_ls("xlsx2") -> fls
# 尝试一个
readxl::read_xlsx(fls[1]) %>%
mutate_all(as.character)
#> # A tibble: 19 × 9
#> 地区 收入总计 本年收入 返还性收入 一般性转移支付收入 专项转移支付收入
#> <chr> <chr> <chr> <chr> <chr> <chr>
#> 1 区县合计 16205016 9030452 <NA> 4029832 2304235
#> 2 东城区 971460 729028 <NA> 98333 78198
#> 3 西城区 1795342 1468452 <NA> 84363 158129
#> 4 崇文区 540236 204271 <NA> 186131 66072
#> 5 宣武区 773193 456529 <NA> 157352 67875
#> 6 朝阳区 2148283 1883143 <NA> 131284 117391
#> 7 海淀区 2047327 1622426 <NA> 118242 219096
#> 8 石景山区 364908 179832 <NA> 123644 39254
#> 9 丰台区 841183 391719 <NA> 244972 193272
#> 10 门头沟区 405083 92666 <NA> 221442 83875
#> 11 房山区 787915 233367 <NA> 388009 164965
#> 12 通州区 794974 260607 <NA> 308153 161981
#> 13 昌平区 742206 316138 <NA> 242530 139781
#> 14 顺义区 906243 481208 <NA> 256631 119892
#> 15 大兴区 813856 230857 <NA> 297897 249759
#> 16 怀柔区 621007 164675 <NA> 276551 102307
#> 17 平谷区 563923 122765 <NA> 296794 98947
#> 18 密云县 614599 129269 <NA> 321490 129280
#> 19 延庆县 473278 63500 <NA> 276014 114161
#> # ℹ 3 more variables: 转贷财政部代理发行地方政府债券收入 <chr>, 上年结余 <chr>,
#> # 调入资金 <chr>
# 循环读取所有的
lapply(fls, function(x){
readxl::read_xlsx(x) %>%
mutate_all(as.character) %>%
mutate(file = x,
rowid = row_number()) -> tempxl
tempxl %>%
set_names(colnames(tempxl) %>%
str_remove_all("\\r") %>%
str_remove_all("\\n")) -> tempxl
if ("收入总计" %in% colnames(tempxl)) {
return(tempxl)
}
}) %>%
bind_rows() -> df1

df1
#> # A tibble: 41 × 11
#> 地区 收入总计 本年收入 返还性收入 一般性转移支付收入 专项转移支付收入
#> <chr> <chr> <chr> <chr> <chr> <chr>
#> 1 区县合计 16205016 9030452 <NA> 4029832 2304235
#> 2 东城区 971460 729028 <NA> 98333 78198
#> 3 西城区 1795342 1468452 <NA> 84363 158129
#> 4 崇文区 540236 204271 <NA> 186131 66072
#> 5 宣武区 773193 456529 <NA> 157352 67875
#> 6 朝阳区 2148283 1883143 <NA> 131284 117391
#> 7 海淀区 2047327 1622426 <NA> 118242 219096
#> 8 石景山区 364908 179832 <NA> 123644 39254
#> 9 丰台区 841183 391719 <NA> 244972 193272
#> 10 门头沟区 405083 92666 <NA> 221442 83875
#> # ℹ 31 more rows
#> # ℹ 5 more variables: 转贷财政部代理发行地方政府债券收入 <chr>, 上年结余 <chr>,
#> # 调入资金 <chr>, file <chr>, rowid <int>
# 支出的也类似
lapply(fls, function(x){
readxl::read_xlsx(x) %>%
mutate_all(as.character) %>%
mutate(file = x,
rowid = row_number()) -> tempxl
tempxl %>%
set_names(colnames(tempxl) %>%
str_remove_all("\\r") %>%
str_remove_all("\\n")) -> tempxl
if ("支出总计" %in% colnames(tempxl)) {
return(tempxl)
}
}) %>%
bind_rows() -> df2

df2
#> # A tibble: 43 × 11
#> 地区 支出总计 本年支出 一般性转移支付上解 专项转移支付上解
#> <chr> <chr> <chr> <chr> <chr>
#> 1 区县合计 15032931 13319131 1434518 142496
#> 2 东城区 900682 639265 206548 19671
#> 3 西城区 1712184 1209101 429025 74058
#> 4 崇文区 455292 454642 <NA> 650
#> 5 宜武区 613071 502162 98213 1696
#> 6 朝阳区 2136776 1402892 643077 22717
#> 7 海淀区 1915156 1843983 55541 12632
#> 8 石景山区 337315 334417 2114 784
#> 9 丰台区 824096 822593 <NA> 1503
#> 10 门头沟区 378107 372620 <NA> 487
#> # ℹ 33 more rows
#> # ℹ 6 more variables: 安排预算稳定调节基金 <chr>, 调出资金 <chr>,
#> # 年终结余 <chr>, 其中净结余 <chr>, file <chr>, rowid <int>

这样就得到了提取结果。

点击这里跳转到 RStata 短书平台获取附件:使用 R 语言对扫描文档进行 OCR 并提取表格数据

评论