探讨多个不同的Python包,并学习如何从PDF中提取某些图片

有很多时候你会想用python从pdf中提取数据,然后将其导出成其他格式。不幸的是,并没有多少python包可以很好的执行这部分工作。在这篇贴子中,我们将探讨多个不同的python包,并学习如何从pdf中提取某些图片。尽管在python中没有一个完整的解决方案,你还是应该能够运用这里的技能开始上手。提取出想要的数据之后,我们还将研究如何将数据导出成其他格式。
让我们从如何提取文本开始学起!
使用pdfminer提取文本
最被大家所熟知的可能是一个叫做pdfminer的包。pdfminer包大约从python2.4版本就存在了。它的主要目的是从pdf中提取文本。实际上,pdfminer可以告诉你某文本在分页上具体的位置和字体信息。对于python2.4到2.7版本,你可以参考以下网站来了解pdfminer的更多信息:
github – https://github.com/euske/pdfminer
pypi – https://pypi.python.org/pypi/pdfminer/
webpage – https://euske.github.io/pdfminer/
pdfminer是不兼容于python3的。幸运的是,pdfminer家族的一个分支pdfminer.six在python3上完全能胜任同样的功能。
你可以在以下网站上找到:
https://github.com/pdfminer/pdfminer.six
关于pdfminer的安装说明已经比较过时了。其实你可以用pip命令来安装它:
python -m pip install pdfminer
如果你要在python3上安装pdfminer(这也许就是你现在正在做的),你需要这样安装:
python -m pip install pdfminer.six
pdfminer的相关文档很少。你将很大可能地需要使用google和stack overflow两个查询工具来弄清楚如何在这篇贴子的涵盖内容之外有效地使用pdfminer。
提取所有文本
有时你会想要提取pdf文件中的所有文本。pdfminer包提供了一些不同的方法使你能够做到这一点。我们先来探讨一些编程的方法。让我们试着从一个国税局w9表单中读取所有的文本。
你可以从这里得到表单副本:
https://www.irs.gov/pub/irs-pdf/fw9.pdf
保存完这个pdf文件之后,你可以参考以下代码:
import iofrom pdfminer.converter import textconverterfrom pdfminer.pdfinterp import pdfpageinterpreterfrom pdfminer.pdfinterp import pdfresourcemanagerfrom pdfminer.pdfpage import pdfpagedef extract_text_from_pdf(pdf_path): resource_manager = pdfresourcemanager() fake_file_handle = io.stringio() converter = textconverter(resource_manager, fake_file_handle) page_interpreter = pdfpageinterpreter(resource_manager, converter) with open(pdf_path, 'rb') as fh: for page in pdfpage.get_pages(fh, caching=true, check_extractable=true): page_interpreter.process_page(page) text = fake_file_handle.getvalue() # close open handles converter.close() fake_file_handle.close() if text: return textif __name__ == '__main__': print(extract_text_from_pdf('w9.pdf'))
当你直接使用pdfminer包时,往往会有点繁琐。这里,我们从pdfminer的不同模块中引入多个不同的类。由于这些类都没有文档说明,也没有实现其文档字符串属性,我将不会深入讲解它们做了什么。如果你真的好奇的话,尽管可以深入地研究它们的源代码。无论如何,我认为我们可以大致照以上代码行事。
我们做的第一件事就是创建一个资源管理器的实例。然后通过python的输入输出(io)模块创建一个似文件对象。如果你使用的是python2,你应该使用stringio模块。接下来的步骤是创建一个转换器。在这个例子里,我们选择使用textconverter,如果你想要的话,你还可以使用htmlconverter或xmlconverter。最后,我们创建一个pdf解释器对象,携带着我们的资源管理器和转换器对象,来提取文本。
最后一步是打开pdf文件并且循环遍历每一页。结尾部分,我们抓取所有的文本,关闭不同的信息处理器,同时打印文本到标准输出(stdout)。
按页提取文本
通常我们并不需要从一个多页文档中抓取所有的文本。你一般会想要处理文档的某些部分。那么,让我们改写代码以便它提取文本呈分页的格式。这将允许我们在检查文本时,一次一页地进行:
# miner_text_generator.pyimport iofrom pdfminer.converter import textconverterfrom pdfminer.pdfinterp import pdfpageinterpreterfrom pdfminer.pdfinterp import pdfresourcemanagerfrom pdfminer.pdfpage import pdfpagedef extract_text_by_page(pdf_path): with open(pdf_path, 'rb') as fh: for page in pdfpage.get_pages(fh, caching=true, check_extractable=true): resource_manager = pdfresourcemanager() fake_file_handle = io.stringio() converter = textconverter(resource_manager, fake_file_handle) page_interpreter = pdfpageinterpreter(resource_manager, converter) page_interpreter.process_page(page) text = fake_file_handle.getvalue() yield text # close open handles converter.close() fake_file_handle.close()def extract_text(pdf_path): for page in extract_text_by_page(pdf_path): print(page) print()if __name__ == '__main__': print(extract_text('w9.pdf'))
在这个例子中,我们创建了一个生成器函数按页生成(yield)了文本。extract_text函数按页打印出文本。此处我们可以加入一些分析逻辑来得到我们想要的分析结果。或者我们可以仅是将文本(或html或xml)存入不同的文件中以便分析。
你可能注意到这些文本没有按你期望的顺序排列。因此你需要思考一些方法来分析出你感兴趣的文本。
pdfminer的好处就是你可以很方便地按文本、html或xml格式来“导出”pdf文件。
你也可以使用pdfminer的命令行工具,pdf2txt.py和dumppdf.py,来为你执行导出工作。如果你不想试图自己弄明白pdfminer。根据pdf2txt.py的源代码,它可以被用来导出pdf成纯文本、html、xml或“标签”格式。
通过pdf2txt.py导出文本
伴随着pdfminer一起的pdf2txt.py命令行工具会从一个pdf文件中提取文本并且默认将其打印至标准输出(stdout)。它不能识别文字图片,就像pdfminer不支持光学字符识别(ocr)一样。让我们尝试用最简单的方法来使用它,那就是仅仅传递给它一个pdf文件的路径。我们会使用w9.pdf文件。打开一个终端并且定位到你存放pdf文件的位置,或修改一下命令指向待处理文件:
pdf2txt.py w9.pdf
如果你执行这条命令,它将打印出所有的文本到标准输出(stdout)。你也可以使pdf2txt.py 将文本写入文件成文本、html、xml或“带标签pdf”格式。xml格式将给出关于pdf的大部分信息,因为它包含了每一个字母在文件中的位置以及字体信息。不推荐使用html格式,因为pdf2txt生成的标记往往会很丑。以下是教你如何生成不同格式输出的方法:
pdf2txt.py -o w9.html w9.pdf pdf2txt.py -o w9.xml w9.pdf
第一条命令将创建一个html文件,而第二条将创建一个xml文件。
最终的结果看上去有点怪,但是它并不太糟糕。xml格式的输出极其冗长,因此我不能将它完整地在这里重现,以下是一小段示例:
form w-9
使用slate提取文本
tim mcnamara觉得pdfminer使用起来太过愚蠢和费力,因此他写了一个围绕它的包装器叫做slate,以使它更简单地从pdf中提取文本。不幸的是,它和python3不兼容。如果你想试用,你可能需要easy_install以便于安装distribute包,如下:
easy_install distribute
我不能使用pip 正确安装这个包。然而一旦安装了它,你将能够使用pip来安装slate:
python -m pip install slate
注意最新的版本是0.5.2,而pip未必能拿到这个版本。如果拿不到,那么你可以从github上直接获取slate安装:
python -m pip install git+https://github.com/timclicks/slate
现在我们已经准备好写一些代码来从pdf中提取文本了:
# slate_text_extraction.pyimport slatedef extract_text_from_pdf(pdf_path): with open(pdf_path) as fh: document = slate.pdf(fh, password='', just_text=1) for page in document: print(page)if __name__ == '__main__': extract_text_from_pdf('w9.pdf')
正如你能看到的,让slate分析一个pdf文件,你只需要引进slate然后创建一个它的pdf类的实例。pdf类其实是python内置类list的一个子类,所以它仅是返回了一列/可遍历的文本页。如果pdf文件设有密码,你可以传入一个密码参数。不管怎样,一旦文件被分析,我们只要打印出每一页的文本即可。
我非常喜欢slate,它用起来更简单。不幸的是,这个包也几乎没有什么相关文档。在浏览过它的源码之后,它看起来只支持纯文本提取。
导出你的数据
现在我们得到了一些文本,我们会花费一些时间来学习如何导出数据成各种不同的格式。具体来说,我们将学习如何以如下方法导出文本:
xml
json
csv
让我们开始吧!
导出成xml
可扩展标记语言(xml)格式是最为人所熟知的输入输出格式之一。它被广泛运用于互联网中的许多不同的事物。正如我们已经在本贴中看到的,pdfminer也支持xml作为它的输出之一。
话虽这么说,让我们创建我们自己的xml生成工具。如下是一个简单的例子:
# xml_exporter.pyimport osimport xml.etree.elementtree as xmlfrom miner_text_generator import extract_text_by_pagefrom xml.dom import minidomdef export_as_xml(pdf_path, xml_path): filename = os.path.splitext(os.path.basename(pdf_path))[0] root = xml.element('{filename}'.format(filename=filename)) pages = xml.element('pages') root.append(pages) counter = 1 for page in extract_text_by_page(pdf_path): text = xml.subelement(pages, 'page_{}'.format(counter)) text.text = page[0:100] counter += 1 tree = xml.elementtree(root) xml_string = xml.tostring(root, 'utf-8') parsed_string = minidom.parsestring(xml_string) pretty_string = parsed_string.toprettyxml(indent=' ') with open(xml_path, 'w') as fh: fh.write(pretty_string) #tree.write(xml_path)if __name__ == '__main__': pdf_path = 'w9.pdf' xml_path = 'w9.xml' export_as_xml(pdf_path, xml_path)
这段代码将使用python内置的xml库,minidom和elementtree。我们也引入pdfminer生成器代码以用于每次抓取一页文本。在这个例子中,我们用pdf的文件名创建了我们顶层的元素。然后在它的下层增加了一个页(pages)元素。下一步是for循环,在此循环中我们从pdf中提取每一页然后保存想要的信息。此处你可以加入一个特定的分析程序,其中你可以将页分成句子或者单词,从而分析出更有趣的信息。比如,你可能只想得到有某个特定名字或日期/时间戳的句子。你可以运用python的正则表达式来找出这类东西,或者仅是检查子字符串在句子中的存在。
对于这个例子,我们仅仅是提取了每一页的前100个字符并将其存入一个xml的子元素(subelement)中。接下来的一段代码可以简化成仅是写出xml文件。然而,elementtree不会做任何事来使得xml易读。它最后看上去有点像压缩的javascript似的一块巨型文本。所以我们在写入文件之前使用minidom通过空格来“美化”xml,而不是将整块文本写入磁盘。最终看上去像这样:
form w-9(rev. november 2017)department of the treasury internal revenue service request for taxp form w-9 (rev. 11-2017)page 2 by signing the filled-out form, you: 1. certify that the tin you are g form w-9 (rev. 11-2017)page 3 criminal penalty for falsifying information. willfully falsifying cert form w-9 (rev. 11-2017)page 4 the following chart shows types of payments that may be exempt from ba form w-9 (rev. 11-2017)page 5 1. interest, dividend, and barter exchange accounts opened before 1984 form w-9 (rev. 11-2017)page 6 the irs does not initiate contacts with taxpayers via emails. also, th
上面是漂亮干净的xml,同时它也是易读的。锦上添花的是,你可以运用你在pypdf2章节中所学到的知识从pdf中提取元数据(metadata),然后将其也加入到xml中。
导出成json
javascript对象注释, 或者json, 是一种易读易写的轻量级的数据交换格式。python包含一个json模块于它的标准库中,从而允许你用编程方式来读写json。让我们运用从前一章节学到的内容来创建一个导出器脚本来输出json而不是xml:
# json_exporter.pyimport jsonimport osfrom miner_text_generator import extract_text_by_pagedef export_as_json(pdf_path, json_path): filename = os.path.splitext(os.path.basename(pdf_path))[0] data = {'filename': filename} data['pages'] = [] counter = 1 for page in extract_text_by_page(pdf_path): text = page[0:100] page = {'page_{}'.format(counter): text} data['pages'].append(page) counter += 1 with open(json_path, 'w') as fh: json.dump(data, fh)if __name__ == '__main__': pdf_path = 'w9.pdf' json_path = 'w9.json' export_as_json(pdf_path, json_path)
这里,我们引入所需要的不同的库,包括pdfminer模块。然后创建一个函数,以pdf文件的输入路径和json文件的输出路径为参数。在python中json基本上就是一个字典,所以我们创建一对简单的顶层的键:filename和pages。pages键对应一个空的表单。接着,我们循环遍历pdf的每一页并且提取每一页的前100个字符。然后创建一个字典变量以页号作为键100个字符作为值并将其添加到顶层的页表单中。最后,我们利用json模块的dump命令生成文件。
文件的内容最终看上去像这样:
{'filename': 'w9', 'pages': [{'page_1': 'form w-9(rev. november 2017)department of the treasury internal revenue service request for taxp'}, {'page_2': 'form w-9 (rev. 11-2017)page 2 by signing the filled-out form, you: 1. certify that the tin you are g'}, {'page_3': 'form w-9 (rev. 11-2017)page 3 criminal penalty for falsifying information. willfully falsifying cert'}, {'page_4': 'form w-9 (rev. 11-2017)page 4 the following chart shows types of payments that may be exempt from ba'}, {'page_5': 'form w-9 (rev. 11-2017)page 5 1. interest, dividend, and barter exchange accounts opened before 1984'}, {'page_6': 'form w-9 (rev. 11-2017)page 6 the irs does not initiate contacts with taxpayers via emails. also, th'}]}
又一次,我们得到了易读的输出。你也可以通过pdf的元数据(metadata)来加强这个例子,如果你乐意的话。请注意输出将会改变,它依赖于你想从每一页或文档中分析出什么样的结果。
现在让我们来快速看一下怎样导出csv文件。
导出成csv
csv是 **comma separated values** (逗号分隔值)的缩写。它是一种漂亮的标准格式,并且已经存在了很长时间。csv的优点就是microsoft excel和libreoffice都能够自动地以漂亮的电子表格的方式将它们打开。你也可以在一个文本编辑器中打开csv文件,如果你乐意看到它的原始值的话。
python有一个内置的csv模块,你可以用它来读写csv文件。在这里我们将用它从我们由pdf中提取的文本来创建一个csv。让我们看一下代码:
# csv_exporter.pyimport csvimport osfrom miner_text_generator import extract_text_by_pagedef export_as_csv(pdf_path, csv_path): filename = os.path.splitext(os.path.basename(pdf_path))[0] counter = 1 with open(csv_path, 'w') as csv_file: writer = csv.writer(csv_file) for page in extract_text_by_page(pdf_path): text = page[0:100] words = text.split() writer.writerow(words)if __name__ == '__main__': pdf_path = 'w9.pdf' csv_path = 'w9.csv' export_as_csv(pdf_path, csv_path)
这个例子中,我们引入了python的csv库。除此以外,引入的库和前一个例子相同。在函数中,我们利用csv文件路径创建了一个csv文件处理器。然后用文件处理器作为唯一的参数初始化了一个csv写入器对象。接着像之前一样遍历了pdf页。这里唯一的不同就是我们将前100个字符分割成了单个的词。这将允许我们拥有一些真实的数据来加入到csv中。如果不这样做,那么每一行将只会有一个元素在其中,那就不算一个真正的csv文件了。最后,我们将一列单词写入csv文件中。
这就是得到的结果:
form,w-9(rev.,november,2017)department,of,the,treasury,internal,revenue,service,request,for,taxpform,w-9,(rev.,11-2017)page,2,by,signing,the,filled-out,form,,you:,1.,certify,that,the,tin,you,are,gform,w-9,(rev.,11-2017)page,3,criminal,penalty,for,falsifying,information.,willfully,falsifying,certform,w-9,(rev.,11-2017)page,4,the,following,chart,shows,types,of,payments,that,may,be,exempt,from,baform,w-9,(rev.,11-2017)page,5,1.,interest,,dividend,,and,barter,exchange,accounts,opened,before,1984form,w-9,(rev.,11-2017)page,6,the,irs,does,not,initiate,contacts,with,taxpayers,via,emails.,also,,th
我认为这个例子同json或xml的例子相比读起来难了点,但是它不算太难。现在让我们继续来看一下怎样才能将图片从pdf中提取出来。
从pdf中提取图片
不幸的是,并不存在python包可以真正地做到从pdf中提取图片。我找到的最接近的东西是有一个叫minecart的项目宣称可以做到这一点,但是它只在python2.7上有效。我没法使其运行于我的pdf样本。在ned batchelder的博客上有一篇文章谈到了一点儿如何从pdf中提取jpg图片。代码如下:
# extract jpg's from pdf's. quick and dirty.import syspdf = file(sys.argv[1], rb).read()startmark = \xff\xd8startfix = 0endmark = \xff\xd9endfix = 2i = 0njpg = 0while true: istream = pdf.find(stream, i) if istream < 0: break istart = pdf.find(startmark, istream, istream+20) if istart < 0: i = istream+20 continue iend = pdf.find(endstream, istart) if iend < 0: raise exception(didn't find end of stream!) iend = pdf.find(endmark, iend-20) if iend < 0: raise exception(didn't find end of jpg!) istart += startfix iend += endfix print(jpg %d from %d to %d % (njpg, istart, iend)) jpg = pdf[istart:iend] jpgfile = file(jpg%d.jpg % njpg, wb) jpgfile.write(jpg) jpgfile.close() njpg += 1 i = iend
这同样对我使用的pdf文件无效。有一些人在留言中宣称代码对他们的一些pdf文件有效,同时也有一些留言例举了修改后的代码。stack overflow网站上有关于这个的各种代码,其中一些这样或那样地使用了pypdf2。但没有一个对我有效。
我的建议是使用一个类似于poppler的工具来提取图片。poppler有一个工具叫做pdfimages,你可以同python的subprocess模块一起来使用。以下是你如何在没有python的情况下使用它:
pdfimages -all reportlab-sample.pdf images/prefix-jpg
请确保images文件夹(或你想新建的任何输出文件夹)已经被创建,因为pdfimages不会为你创建它。
让我们写一个python脚本来执行同样的命令,请确保输出文件夹已经存在:
# image_exporter.pyimport osimport subprocessdef image_exporter(pdf_path, output_dir): if not os.path.exists(output_dir): os.makedirs(output_dir) cmd = ['pdfimages', '-all', pdf_path, '{}/prefix'.format(output_dir)] subprocess.call(cmd) print('images extracted:') print(os.listdir(output_dir))if __name__ == '__main__': pdf_path = 'reportlab-sample.pdf' image_exporter(pdf_path, output_dir='images')
在这个例子中,我们引入了subprocess和os模块。如果输出路径不存在,我们会尝试创建它。然后我们运用subprocess的call函数来执行pdfimages命令。使用call函数是因为它将等到pdfimages命令完全执行完才返回。你可以代之以popen,但是那将基本上在后台运行命令进程。最后,我们打印出输出路径下的细节,以确定所有的图片都被提取进了其中。
还有一些网络上的其它文章引用了一个叫做wand的库,你也许可以试一试。它是一个imagemagick的包装器。还有一个值得关注的是绑定了poppler的python叫做pypoppler,虽然我没有能够找到任何和这个包相关的提取图片的例子。
总结
这篇文章网罗了很多信息。我们学习了一些可以用来从pdf中提取文本的包,如pdfminer或slate。我们还学习了如何运用python的内置库来导出文本到xml、json和csv。最后,我们研究了一下从pdf中导出图片这个棘手的问题。尽管python目前没有任何出色的库可以完成这个工作,你可以采用其它工具的变通方案,例如poppler的pdfimage工具模块。

生态型摄像机采用了部分技术—单晶硅光伏板及刀片锂电
基于SMP8654平台的MKV播放器设计
Techradar发布2018全球10大最佳游戏手机排名
基于ACP平行视觉理论的车道线检测系统设计
揭秘真相:小米2发布会卖门票的目的
探讨多个不同的Python包,并学习如何从PDF中提取某些图片
移动签名技术在智能电池管理中有什么样的应用
芯驰科技程泰毅:用芯赋能未来汽车电子电气架构
数字课桌的材料
埃派克森推出2.4G无线鼠标模组等系列创新科技
阿里云全球大崩溃是意外?盘点那些自称安全的云厂商
上海协堡电子激光测距传感器
MAX1403和μC/μP及DAC组成的4~20mA变送器(
纯电阻电路和非纯电阻电路的区别
iOS10.3正式版怎么样?iOS10.3值不值得更新?网友评论:iOS10.3我要为你点赞!
腾讯企点腾采通发布,助力电子产业进入数字化转型快车道
SIP电梯五方对讲DJS-I13介绍
压力开关作业原理及分类
TOP412/414三端DC/DC PWM开关电源
使用并联升压转换器的大功率音频放大器方案