赞
踩
工作中有对PDF文件进行数据抽取,现在总结归纳一下相应的方法,本文包括一下内容:
PDF文件分割、拼接;
PDF文件抽取图片,简单的图片识别;
PDF文件抽取表格;
PDF文件抽取文本;
PDF文件转docx文件;
docx文件数据抽取;
目的:尽可能的将pdf中的数据,抽取出来,尤其是文本和表格数据尽可能的精准。
Python版本:Python3.8
使用场景:什么时候会用到这个功能呢?比如你爬取了一堆的PDF文件,但是这些PDF文件中存在一些干扰页,比如广告页。这种情况下,你就需要对PDF文件进行分割、拼接,在本文中,将会为大家演示这个方式的另外一个用途。
- import os
- from PyPDF2 import PdfFileWriter, PdfFileReader
-
-
- def clear_dir(dir_path):
- """清空目录下的文件"""
- names = os.listdir(dir_path)
- for name in names:
- file_path = os.path.join(dir_path, name)
- cmd = 'del %s' % file_path #这个是windows命令
- #cmd = 'rm -rf %s' % file_path #这个是Linux的命令
- cmd = cmd.replace('/', '\\') #为啥要这个?因为windows命令不支持/所以需要替换,Linux命令没有遇见这个问题;
- os.system(cmd) #可以通过返回值来判断是否执行成功:0成功,其他失败
-
-
- def split_pdf(pdf, output_dir):
- """pdf按每页拆分"""
- clear_dir(output_path)
- # 获取 PdfFileReader 对象
- reader = PdfFileReader(pdf_file)
- pages_num = reader.getNumPages()
- # writer = PdfFileWriter() 生成一个文件
- for index in range(pages_num):
- #可以通过对index判断分割想要的
- writer = PdfFileWriter() #按照每页来分割pdf
- pageObj = reader.getPage(index)
- writer.addPage(pageObj)
- # 添加完每页,再一起保存至文件中;如果要输出一个文件,后面这些放置到循环外即可
- file_name = os.path.join(output_path, str(index) + '.pdf')
- with open(file_name, 'wb') as fw:
- writer.write(fw)

我对机器学习了解不多,补充这个也是由于在开发中有这么一个需求。使用场景,需要批量的PDF文件重命名,命名中需要包含发布的机构,这个机构的名称可以在首页获取,但是是一个logo。观察发现,logo中包含了机构名称,所以只需要对logo识别中文即可:
抽取图片:网络上找的代码(太多出处了,懒的写来源了),由于使用的版本不一致,部分代码有微调,这个也是我比较吐槽的地方,测试了半天。
文字识别
- import fitz
- import re
- import os
-
-
- def save_pdf_img(path, save_path):
- '''
- path: pdf的路径
- save_path : 图片存储的路径
- '''
- # 使用正则表达式来查找图片
- checkXO = r"/Type(?= */XObject)"
- checkIM = r"/Subtype(?= */Image)"
- # 打开pdf
- doc = fitz.open(path)
- # 图片计数
- imgcount = 0
- # 获取对象数量长度
- lenXREF = doc.xref_length()
-
- # 遍历每一个图片对象
- for i in range(1, lenXREF):
- # 定义对象字符串
- text = doc.xref_object(i)
- # print(i,text)
- isXObject = re.search(checkXO, text)
- # 使用正则表达式查看是否是图片
- isImage = re.search(checkIM, text)
- # 如果不是对象也不是图片,则continue
- if not isXObject or not isImage:
- continue
- imgcount += 1
- # 根据索引生成图像
- pix = fitz.Pixmap(doc, i)
- # 根据pdf的路径生成图片的名称
- new_name = "img{}.png".format(imgcount)
- new_name = os.path.join(save_path, new_name)
- pix.save(new_name)
- # 释放资源
- pix = None

第三方包:paddleocr,安装教程网上都有,自己搜索即可
- from paddleocr import PaddleOCR
- import logging
-
-
- #将一些日志信息过滤掉
- logging.disable(logging.DEBUG)
- logging.disable(logging.WARNING)
-
-
- ocr = PaddleOCR(use_angle_cls=True, lang='ch')
- result = ocr.ocr(img_path, cls=True)
- if len(result) != 1:
- print('result num=', len(result))
- exit()
- result = result[0]
- lines = []
- #可以直接答应result看看都是些什么结构
- for line in result:
- lines.append(line[1][0])
- #这就是识别出来的文本
- print(lines)

效果:对于正规的的字体识别还是很好的,包括繁体,可能是logo图中的文本比较简单的原因,没有深究,文本识别技术还未深入学习,不发表言论。
PDF文件的数据组成:主要是文本、图片、表格,这三部分组成,但是也会穿插流程图、各种柱状图等。
Python可以抽取的PDF表格、文本数据的第三方包:pdfplumber,tabula,camelot
1、pdfplumber:可以说是目前所有包中做的最好的一个包。
优点:
①每页单独对象,支持文本、表格数据的抽取(亮点);
②文本抽取:保留了文本的格式,比如换行位置有空格,可以通过这个特点将一段的文本整合;
③表格数据抽取:不会被换行数据所干扰;
缺点:
①文本抽取:如果这页有表格数据,抽取到的文本数据中会包含表格数据(也可能是一个优点???)。
②表格数据抽取:对于有合并单元格的表格,无法还原表格结构。
③表格数据抽取:表格数据不能100%保证和原数据一致,可能少那么接个字,可能识别出错等。
④表格数据抽取(缺陷):最主要的还是对无边界的表格,效果很差,会丢失边缘的数据!!!
⑤表格数据抽取:会被流程图、柱状图干扰;
简单使用:
- import pdfplumber
- pdf = r'../data/text.pdf'
- wookroot = pdfplumber.open(pdf)
- pages = wookroot.pages
- for page in pages:
- text = page.extract_text()
- tables = page.extract_tables()
- print(text)
- print(tables)
- break
- wookroot.close()
这个包也有一下高级的用法,主要是对表格数据抽取进行参数调节,但是效果嘛,不是很理想。
tablua:专门用于抽取PDF文件中表格数据的包
优点:
①抽取出来表格数据可以反向推导出表格的结构(亮点);
②不会换行数据干扰;
③可以指定页读取
缺点:
①无法保证表格数据100%准确;
②对于无边界表格支持不好,丢失数据;
- import tablua
-
-
- def get_tabula_tables(pdf_path):
- dfs = tabula.read_pdf(pdf_path, pages='all', encoding='gbk')
- tables = []
- for df in dfs:
- df = df.fillna('')
- headers = df.keys().values.tolist()
- lt = []
- for header in headers:
- if header.count('Unnamed'):
- lt.append('')
- continue
- if header[-1].isdigit() and header.count('.'):
- words = header.split('.')
- line = '.'.join(words[:-1])
- if headers.count(line):
- lt.append(line)
- continue
- lt.append(header)
- headers = lt
- values = df.values.tolist()
- lt = []
- for words in values:
- rows = []
- for word in words:
- if not isinstance(word, str):
- word = str(word)
- if word[-2:] == '.0':
- word = word[:-2]
- rows.append(word)
- continue
- rows.append(word)
- lt.append(rows)
- values = lt
- values.insert(0, headers)
- tables.append(values)
- return tables
-
-
- pdf_file = 'xxxx.pdf'
- tables = get_tabula_tables(pdf_path)

读取表格中的整数,后面会带有“.0“。
安装包以后,默认的模式无法使用,只能够使用stream来读取。
- import camelot
-
-
- tables = camelot.read_pdf(pdf_file, flavor="stream")
- for table in tables:
- print(table.df)
- print('>>'*50)
流式读取会将整页当做一个表格。
文本:最好的包就是pdfplumber,可以获取到完整的文本数据,但是表格文本、流程图文本、图表文本所也会被读取。这个缺点自然是难以拆分,基本没有通用的逻辑可以排除。这个也是优点,如果我们想将表格数据插入到文本中,那么是不是就可以通过这种方式来实现呢?总的来说,还是很复杂,对于同一段落的文本,还需要我们自己去合并,涉及到换页、表格数据干扰、流程图数据干扰以及包本身可能会读取出错,这个工作量是非常的大,想要实现真的是很难(这么过滤表格数据就足以搞崩心态)。
表格:对于有线表格,推荐的pdfplumber;边框线缺失的,基本都会丢失数据。如果你需要重构数据,可以用tablua。值得注意的事,不管那个包,都有可能存在表数据和pdf中表数据不一致的问题(或多、或少、或读错)。
相信大家也看到了,想直接从pdf中解析出文本、表格数据是真的心累。那么我们能够换个思路,从它的前身入手,读取docx文档。docx文件与pdf文件不同之处在于,docx中的数据是标签化的,只要是标签化,我们提取数据基本上就不会出错。
我这里一共有两种方式pdf转docx:pdf2docx+aspose
这个包是相信大家都是比较熟悉的,百度中90%都是这个用法。
- from pdf2docx import Converter
- import logging
-
-
- logging.disable(logging.INFO)
- logging.disable(logging.DEBUG)
- logging.disable(logging.WARNING)
-
-
- pdf_file = 'xxx.pdf'
- cv = Converter(pdf_file)
- docx_file = 'xxx.docx'
- cv.convert(docx_file)
- cv.close()
优点:1、转换速度快,0.2秒一页;2、无线+有线表格都支持;
缺点:1、纸张方向是横向的表格不支持;2、并不是所有表结构都可以转换(表格边框线的多样性会影响转化的效果)。
官网:File Format APIs for .NET Core, Java, Python, C++, Android | products.aspose.com
今天主要用里面的aspose.words。aspose.pdf这个也可读取数据,但是效果还没有pdflumber好。
- import aspose.words as aw
-
- pdf_file = 'xxx.pdf'
- docx_file = 'xxx.docx'
- pdf = aw.Document(pdf_file)
- pdf.save(docx_file)
优点:1.无线+有线表格都支持;2、支持横向表格;3、表格数据更加准确。
缺点:
有两个版本,免费版和付费版:免费版单个文件只能转5页。
有水印+页眉,转换的words中会有该公司的水印和页眉,这个应该能够解决(没试过)。
和pdf2docx一样,对于特殊结构的表格,转换会很糟糕。
某些情况下文本会出现错乱(我也不知为啥,但是用pdf2docx没有问题)。
没有哪个是绝对精准、通用的,但是至少有两种方案来互补。我测试过网上的在线pdf转docx,要么表格数据有缺失,要么就是用ocr来识别、重构的word,这个种word提取出来的表格数据无法使用。
直接上代码
- from docx import Document
-
-
- def get_doc_tables(doc):
- """
- 获取docx文件中表格,转化成list
- :param doc:
- :return:
- """
- result = []
- tables = doc.tables
- for table in tables:
- ret = []
- for row_index in range(len(table.rows)):
- lt = []
- for col_index in range(len(table.row_cells(row_index))):
- text = table.cell(row_index, col_index).text
- lt.append(text)
- ret.append(lt)
- result.append(ret)
- return result
-
-
- def get_doc_lines(paragraphs):
- lines = []
- for paragraph in paragraphs:
- line = paragraph.text.strip()
- if not line:
- continue
- lines.append(line)
- # aspose用的体验板,带有页眉
- lines = lines[1:]
- return lines
-
- docx_file = 'xxx.docx'
- doc = Document(docx_file)
- tables = get_doc_tables(dox)
- paragraphs = doc.paragraphs
- lines = get_doc_lines(paragraphs)

优点:
同一段落的会整合到一块(也会有部分行不会)。
读取的表格数据可以反向推导出表格结构(上下左右合并相同值,我是将list数据转成了<table>包含的字符串)。
缺点:
a.每次读取的数据都是整个文件的数据,不支持单页。
b.无法定位表格数据在原文中的位置。
直接解析PDF文件,获取表格数据还行,但是获取文本数据就很难,建议转成docx文件后再处理。
转docx文件,不是所有的pdf都支持,选取多种方式相互补充更为靠谱,没有那个库适合所有的pdf文件!
从docx中读取数据,数据都好提取,数据的准确度和转成docx文件的准确的有关,转的不好,数据就有问题。
表格数据定位问题,我是使用的pdfplumber来完成的,虽然很low,但是也没有找到其他办法。
流程:
pdf文件---切割--->单页pdf文件---转换--->docx文件---数据提取--->表格数据+文本数据---表格数据定位--->完整的整页数据--->前端展示
找到一个介绍PDF文件数据结构的文件:PDF Explained (译作《PDF 解析》) | PDF-Explained (zxyle.github.io)
以上就是所有的内容,希望对你有帮助~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。