目录
一、需求如下:
数以万计pdf文件,其内容包含了公司的名称、统一社会信用代码等信息,如何对pdf进行识别,把pdf文件名更改为统一社会信用代码或企业名称?
二、思路
1. 首先肯定是想办法把pdf识别出来,变成文字信息,想必大家一定想到了使用OCR(optical character recognition)的方法,这里我使用的是百度paddlepaddle框架的paddleocr;paddleocr对pdf就可以进行识别,省去了转成图片文件的麻烦。
2. 第二步,对于识别出的字段,把我们想要的字段提取出来,经过测试,使用正则表达式是比较稳妥的方法。
3.最后将提取到的字段重命名成文件名即可。
三、代码
3.1 导入包,并定义存放文件的路径与文件夹。
import os.path
import re
import shutil
import time
from paddleocr import PaddleOCR
# 需要的目录
dirs = ['finished', 'pdf']
for d in dirs:
if not os.path.exists(f'./{d}'):
os.makedirs(f'./{d}')
# 需要的路径
PDF_PATH = './pdf'
SAVE_PATH = './finished'
3.2 获取PDF文件
st1 = time.perf_counter()
for file_name in os.listdir(PDF_PATH): # 返回pdf目录下的文件夹名称
pdf_names = os.listdir(rf'{PDF_PATH}/{file_name}') # 包含pdf所有文件名组成的列表
pdf_dir = os.path.join(PDF_PATH, file_name) # pdf路径名 + 下面的文件名
finished_path = os.path.join(SAVE_PATH, file_name) # 完成时存储的路径,finished + pdf文件夹名
这部分注释写的很详细,注意st1是用于计算开始时间的。
3.3 OCR识别提取字段
# 调用PaddleOCR进行识别
OCR = PaddleOCR(use_angle_cls=True, lang='en')
result_ocr = OCR.ocr(pdf_crop_save, cls=True)
credit_code = re.findall('[0-9A-Z]{18}', str(result_ocr))
这就是paddleocr强大的地方,只需要三行代码,就可以完成识别。字段是通过正则表达式进行提取的,提取规则为:连续18位的数字与字母组合
3.4 重命名并移动文件
if credit_code:
credit_code = credit_code[0]
credit_code = replace_str(credit_code)
if not os.path.exists(f'./finished/{file_name}'):
os.makedirs(f'./finished/{file_name}')
if os.path.exists(rf'{finished_path}/{credit_code}.pdf'):
print(f'文件名称存在重复,{credit_code}')
continue
shutil.move(rf'{pdf_dir}/{pdf_name}', rf'{finished_path}/{credit_code}.pdf')
print('完成文件移动:', credit_code)
st2 = time.perf_counter()
print('累计耗时:', st2 - st1)
else:
print('识别失败:', credit_code, '累计耗时:', time.perf_counter() - st1)
这部分也没什么需要说的,最后把消耗的时间输出一下就好(其实不输出也没影响),主要就是记录下时间。
四、改进
4.1 减少耗时
有的读者可能会发现,一个pdf文件的文字可能上千字,而我们需要的仅仅是文件的几行内容。是否可以对pdf文件进行裁剪,直接识别我们所需的区域?
答案是可以的,PyPDF2为我们提供了方法:
# 定义用于裁剪的函数
def split(page, tup):
page.mediaBox.lowerLeft = (tup[0], tup[1])
page.mediaBox.lowerRight = (tup[2], tup[1])
page.mediaBox.upperLeft = (tup[0], tup[3])
page.mediaBox.upperRight = (tup[2], tup[3])
# 打开与关闭操作
output_file = PyPDF2.PdfFileWriter()
inputStream = open(pdf_name_path, 'rb')
input_file = PyPDF2.PdfFileReader(inputStream)
# pdf处理操作
this_page = input_file.getPage(0) # 获取第1页
split(this_page, (60, 700, 500, 800))
output_file.addPage(this_page)
pdf_crop_save = rf'{CROP_PATH}/{pdf_name}' # pdf_crop + pdf文件名
outputStream = open(pdf_crop_save, "wb")
output_file.write(outputStream) # 将切割的文件存储在pdf_crop文件下
注释我写的非常详细了,就不在过多赘述了。
4.2 提升准确率
在识别统一社会信用代码时,会发现ocr会将l识别成1,0识别成O的情况,但实际l和O都是不会在统一社会信用代码中存在的,我们可以用replace进行替换:
def replace_str(credit_code: str):
credit_code = credit_code.replace('O', '0')
credit_code = credit_code.replace('o', '0')
credit_code = credit_code.replace('l', '1')
credit_code = credit_code.replace('?', 'T')
credit_code = credit_code.replace('I', '1')
credit_code = credit_code.replace('x', 'X')
credit_code = credit_code.upper()
return credit_code
五、完整代码
改进后的完整版代码如下:
import os.path
import re
import shutil
import time
import PyPDF2
from paddleocr import PaddleOCR
# 需要的目录
dirs = ['finished', 'pdf', 'pdf_crop']
for d in dirs:
if not os.path.exists(f'./{d}'):
os.makedirs(f'./{d}')
# 需要的路径
PDF_PATH = './pdf'
SAVE_PATH = './finished'
CROP_PATH = './pdf_crop'
# 定义用于裁剪的函数
def split(page, tup):
page.mediaBox.lowerLeft = (tup[0], tup[1])
page.mediaBox.lowerRight = (tup[2], tup[1])
page.mediaBox.upperLeft = (tup[0], tup[3])
page.mediaBox.upperRight = (tup[2], tup[3])
# 定义替换规则
def replace_str(credit_code: str):
credit_code = credit_code.replace('O', '0')
credit_code = credit_code.replace('o', '0')
credit_code = credit_code.replace('l', '1')
credit_code = credit_code.replace('?', 'T')
credit_code = credit_code.replace('I', '1')
credit_code = credit_code.replace('x', 'X')
credit_code = credit_code.upper()
return credit_code
st1 = time.perf_counter()
for file_name in os.listdir(PDF_PATH): # 返回pdf目录下的文件夹名称
pdf_names = os.listdir(rf'{PDF_PATH}/{file_name}') # 包含pdf所有文件名组成的列表
pdf_dir = os.path.join(PDF_PATH, file_name) # pdf路径名 + 下面的文件名
finished_path = os.path.join(SAVE_PATH, file_name) # 完成时存储的路径,finished + pdf文件夹名
for pdf_name in pdf_names:
pdf_name_path = rf'{pdf_dir}/{pdf_name}' # pdf文件的具体路径,包含pdf文件本身
# 打开与关闭操作
output_file = PyPDF2.PdfFileWriter()
inputStream = open(pdf_name_path, 'rb')
input_file = PyPDF2.PdfFileReader(inputStream)
# pdf处理操作
this_page = input_file.getPage(0) # 获取第1页
split(this_page, (60, 700, 500, 800))
output_file.addPage(this_page)
pdf_crop_save = rf'{CROP_PATH}/{pdf_name}' # pdf_crop + pdf文件名
outputStream = open(pdf_crop_save, "wb")
output_file.write(outputStream) # 将切割的文件存储在pdf_crop文件下
# 关闭pdf文件,否则会报文件被占用的错
inputStream.close()
outputStream.close()
# 调用PaddleOCR进行识别
OCR = PaddleOCR(use_angle_cls=True, lang='en')
result_ocr = OCR.ocr(pdf_crop_save, cls=True)
credit_code = re.findall('[0-9A-Z]{18}', str(result_ocr))
# 判断结果
if credit_code:
credit_code = credit_code[0]
credit_code = replace_str(credit_code)
if not os.path.exists(f'./finished/{file_name}'):
os.makedirs(f'./finished/{file_name}')
if os.path.exists(rf'{finished_path}/{credit_code}.pdf'):
print(f'文件名称存在重复,{credit_code}')
continue
shutil.move(rf'{pdf_dir}/{pdf_name}', rf'{finished_path}/{credit_code}.pdf')
print('完成文件移动:', credit_code)
st2 = time.perf_counter()
print('累计耗时:', st2 - st1)
else:
print('识别失败:', credit_code, '累计耗时:', time.perf_counter() - st1)
大家有更好的方法,欢迎留言讨论,也欢迎大家指正!