侯体宗的博客
  • 首页
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

Python使用PDFMiner解析PDF代码实例

Python  /  管理员 发布于 7年前   177

近期在做爬虫时有时会遇到网站只提供pdf的情况,这样就不能使用scrapy直接抓取页面内容了,只能通过解析PDF的方式处理,目前的解决方案大致只有pyPDF和PDFMiner。因为据说PDFMiner更适合文本的解析,而我需要解析的正是文本,因此最后选择使用PDFMiner(这也就意味着我对pyPDF一无所知了)。

首先说明的是解析PDF是非常蛋疼的事,即使是PDFMiner对于格式不工整的PDF解析效果也不怎么样,所以连PDFMiner的开发者都吐槽PDF is evil. 不过这些并不重要。官方文档在此:http://www.unixuser.org/~euske/python/pdfminer/index.html

一.安装:

1.首先下载源文件包 http://pypi.python.org/pypi/pdfminer/,解压,然后命令行安装即可:python setup.py install

2.安装完成后使用该命令行测试:pdf2txt.py samples/simple1.pdf,如果显示以下内容则表示安装成功:

Hello World Hello World H e l l o W o r l d H e l l o W o r l d

3.如果要使用中日韩文字则需要先编译再安装: 

# make cmappython tools/conv_cmap.py pdfminer/cmap Adobe-CNS1 cmaprsrc/cid2code_Adobe_CNS1.txtreading 'cmaprsrc/cid2code_Adobe_CNS1.txt'...writing 'CNS1_H.py'......(this may take several minutes) # python setup.py install 

二.使用

由于解析PDF是一件非常耗时和内存的工作,因此PDFMiner使用了一种称作lazy parsing的策略,只在需要的时候才去解析,以减少时间和内存的使用。要解析PDF至少需要两个类:PDFParser 和 PDFDocument,PDFParser 从文件中提取数据,PDFDocument保存数据。另外还需要PDFPageInterpreter去处理页面内容,PDFDevice将其转换为我们所需要的。PDFResourceManager用于保存共享内容例如字体或图片。

 

Figure 1. Relationships between PDFMiner classes

比较重要的是Layout,主要包括以下这些组件:

LTPage

Represents an entire page. May contain child objects like LTTextBox, LTFigure, LTImage, LTRect, LTCurve and LTLine.

LTTextBox

Represents a group of text chunks that can be contained in a rectangular area. Note that this box is created by geometric analysis and does not necessarily represents a logical boundary of the text. It contains a list of LTTextLine objects. get_text() method returns the text content.

LTTextLine

Contains a list of LTChar objects that represent a single text line. The characters are aligned either horizontaly or vertically, depending on the text's writing mode. get_text() method returns the text content.

LTChar

LTAnno

Represent an actual letter in the text as a Unicode string. Note that, while a LTChar object has actual boundaries, LTAnno objects does not, as these are "virtual" characters, inserted by a layout analyzer according to the relationship between two characters (e.g. a space).

LTFigure

Represents an area used by PDF Form objects. PDF Forms can be used to present figures or pictures by embedding yet another PDF document within a page. Note that LTFigure objects can appear recursively.

LTImage

Represents an image object. Embedded images can be in JPEG or other formats, but currently PDFMiner does not pay much attention to graphical objects.

LTLine

Represents a single straight line. Could be used for separating text or figures.

LTRect

Represents a rectangle. Could be used for framing another pictures or figures.

LTCurve

Represents a generic Bezier curve.

 

官方文档给了几个Demo但是都过于简略,虽然给了一个详细一些的Demo,但链接地址是旧的现在已经失效,不过最终还是找到了新的地址:http://denis.papathanasiou.org/posts/2010.08.04.post.html

这个Demo就比较详细了,源码如下:

#!/usr/bin/pythonimport sysimport osfrom binascii import b2a_hex###### pdf-miner requirements###from pdfminer.pdfparser import PDFParserfrom pdfminer.pdfdocument import PDFDocument, PDFNoOutlinesfrom pdfminer.pdfpage import PDFPagefrom pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreterfrom pdfminer.converter import PDFPageAggregatorfrom pdfminer.layout import LAParams, LTTextBox, LTTextLine, LTFigure, LTImage, LTChardef with_pdf (pdf_doc, fn, pdf_pwd, *args): """Open the pdf document, and apply the function, returning the results""" result = None try:  # open the pdf file  fp = open(pdf_doc, 'rb')  # create a parser object associated with the file object  parser = PDFParser(fp)  # create a PDFDocument object that stores the document structure  doc = PDFDocument(parser, pdf_pwd)  # connect the parser and document objects  parser.set_document(doc)  # supply the password for initialization  if doc.is_extractable:   # apply the function and return the result   result = fn(doc, *args)  # close the pdf file  fp.close() except IOError:  # the file doesn't exist or similar problem  pass return result### ### Table of Contents### def _parse_toc (doc): """With an open PDFDocument object, get the table of contents (toc) data [this is a higher-order function to be passed to with_pdf()]""" toc = [] try:  outlines = doc.get_outlines()  for (level,title,dest,a,se) in outlines:   toc.append( (level, title) ) except PDFNoOutlines:  pass return tocdef get_toc (pdf_doc, pdf_pwd=''): """Return the table of contents (toc), if any, for this pdf file""" return with_pdf(pdf_doc, _parse_toc, pdf_pwd)###### Extracting Images###def write_file (folder, filename, filedata, flags='w'): """Write the file data to the folder and filename combination (flags: 'w' for write text, 'wb' for write binary, use 'a' instead of 'w' for append)""" result = False if os.path.isdir(folder):  try:   file_obj = open(os.path.join(folder, filename), flags)   file_obj.write(filedata)   file_obj.close()   result = True  except IOError:   pass return resultdef determine_image_type (stream_first_4_bytes): """Find out the image file type based on the magic number comparison of the first 4 (or 2) bytes""" file_type = None bytes_as_hex = b2a_hex(stream_first_4_bytes) if bytes_as_hex.startswith('ffd8'):  file_type = '.jpeg' elif bytes_as_hex == '89504e47':  file_type = '.png' elif bytes_as_hex == '47494638':  file_type = '.gif' elif bytes_as_hex.startswith('424d'):  file_type = '.bmp' return file_typedef save_image (lt_image, page_number, images_folder): """Try to save the image data from this LTImage object, and return the file name, if successful""" result = None if lt_image.stream:  file_stream = lt_image.stream.get_rawdata()  if file_stream:   file_ext = determine_image_type(file_stream[0:4])   if file_ext:    file_name = ''.join([str(page_number), '_', lt_image.name, file_ext])    if write_file(images_folder, file_name, file_stream, flags='wb'):     result = file_name return result###### Extracting Text###def to_bytestring (s, enc='utf-8'): """Convert the given unicode string to a bytestring, using the standard encoding, unless it's already a bytestring""" if s:  if isinstance(s, str):   return s  else:   return s.encode(enc)def update_page_text_hash (h, lt_obj, pct=0.2): """Use the bbox x0,x1 values within pct% to produce lists of associated text within the hash""" x0 = lt_obj.bbox[0] x1 = lt_obj.bbox[2] key_found = False for k, v in h.items():  hash_x0 = k[0]  if x0 >= (hash_x0 * (1.0-pct)) and (hash_x0 * (1.0+pct)) >= x0:   hash_x1 = k[1]   if x1 >= (hash_x1 * (1.0-pct)) and (hash_x1 * (1.0+pct)) >= x1:    # the text inside this LT* object was positioned at the same    # width as a prior series of text, so it belongs together    key_found = True    v.append(to_bytestring(lt_obj.get_text()))    h[k] = v if not key_found:  # the text, based on width, is a new series,  # so it gets its own series (entry in the hash)  h[(x0,x1)] = [to_bytestring(lt_obj.get_text())] return hdef parse_lt_objs (lt_objs, page_number, images_folder, text=[]): """Iterate through the list of LT* objects and capture the text or image data contained in each""" text_content = []  page_text = {} # k=(x0, x1) of the bbox, v=list of text strings within that bbox width (physical column) for lt_obj in lt_objs:  if isinstance(lt_obj, LTTextBox) or isinstance(lt_obj, LTTextLine):   # text, so arrange is logically based on its column width   page_text = update_page_text_hash(page_text, lt_obj)  elif isinstance(lt_obj, LTImage):   # an image, so save it to the designated folder, and note its place in the text    saved_file = save_image(lt_obj, page_number, images_folder)   if saved_file:    # use html style <img /> tag to mark the position of the image within the text    text_content.append('<img src="https:/article/'+os.path.join(images_folder, saved_file)+'" />')   else:    print >> sys.stderr, "error saving image on page", page_number, lt_obj.__repr__  elif isinstance(lt_obj, LTFigure):   # LTFigure objects are containers for other LT* objects, so recurse through the children   text_content.append(parse_lt_objs(lt_obj, page_number, images_folder, text_content)) for k, v in sorted([(key,value) for (key,value) in page_text.items()]):  # sort the page_text hash by the keys (x0,x1 values of the bbox),  # which produces a top-down, left-to-right sequence of related columns  text_content.append(''.join(v)) return '\n'.join(text_content)###### Processing Pages###def _parse_pages (doc, images_folder): """With an open PDFDocument object, get the pages and parse each one [this is a higher-order function to be passed to with_pdf()]""" rsrcmgr = PDFResourceManager() laparams = LAParams() device = PDFPageAggregator(rsrcmgr, laparams=laparams) interpreter = PDFPageInterpreter(rsrcmgr, device) text_content = [] for i, page in enumerate(PDFPage.create_pages(doc)):  interpreter.process_page(page)  # receive the LTPage object for this page  layout = device.get_result()  # layout is an LTPage object which may contain child objects like LTTextBox, LTFigure, LTImage, etc.  text_content.append(parse_lt_objs(layout, (i+1), images_folder)) return text_contentdef get_pages (pdf_doc, pdf_pwd='', images_folder='/tmp'): """Process each of the pages in this pdf file and return a list of strings representing the text found in each page""" return with_pdf(pdf_doc, _parse_pages, pdf_pwd, *tuple([images_folder]))a = open('a.txt','a')for i in get_pages('/home/jamespei/nova.pdf'): a.write(i)a.close()

这段代码重点在于第128行,可以看到PDFMiner是一种基于坐标来解析的框架,PDF中能解析的组件全都包括上下左右边缘的坐标,如x0 = lt_obj.bbox[0]就是lt_obj元素的左边缘的坐标,同理x1则为右边缘。以上代码的意思就是把所有x0且x1的坐标相差在20%以内的元素分成一组,这样就实现了从PDF文件中定向抽取内容。

----------------补充--------------------

有一个需要注意的地方,在解析有些PDF的时候会报这样的异常:pdfminer.pdfdocument.PDFEncryptionError: Unknown algorithm: param={'CF': {'StdCF': {'Length': 16, 'CFM': /AESV2, 'AuthEvent': /DocOpen}}, 'O': '\xe4\xe74\xb86/\xa8)\xa6x\xe6\xa3/U\xdf\x0fWR\x9cPh\xac\xae\x88B\x06_\xb0\x93@\x9f\x8d', 'Filter': /Standard, 'P': -1340, 'Length': 128, 'R': 4, 'U': '|UTX#f\xc9V\x18\x87z\x10\xcb\xf5{\xa7\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', 'V': 4, 'StmF': /StdCF, 'StrF': /StdCF}

从字面意思来看是因为这个PDF是一个加密的PDF,所以无法解析 ,但是如果直接打开PDF却是可以的并没有要求输密码什么的,原因是这个PDF虽然是加过密的,但密码是空,所以就出现了这样的问题。

解决这个的问题的办法是通过qpdf命令来解密文件(要确保已经安装了qpdf),要想在python中调用该命令只需使用call即可:

 from subprocess import callcall('qpdf --password=%s --decrypt %s %s' %('', file_path, new_file_path), shell=True)

其中参数file_path是要解密的PDF的路径,new_file_path是解密后的PDF文件路径,然后使用解密后的文件去做解析就OK了

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


  • 上一条:
    Python中的命令行参数解析工具之docopt详解
    下一条:
    详解python并发获取snmp信息及性能测试
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 在python语言中Flask框架的学习及简单功能示例(0个评论)
    • 在Python语言中实现GUI全屏倒计时代码示例(0个评论)
    • Python + zipfile库实现zip文件解压自动化脚本示例(0个评论)
    • python爬虫BeautifulSoup快速抓取网站图片(1个评论)
    • vscode 配置 python3开发环境的方法(0个评论)
    • 近期文章
    • 在go语言中实现字符串可逆性压缩及解压缩功能(0个评论)
    • 使用go + gin + jwt + qrcode实现网站生成登录二维码在app中扫码登录功能(0个评论)
    • 在windows10中升级go版本至1.24后LiteIDE的Ctrl+左击无法跳转问题解决方案(0个评论)
    • 智能合约Solidity学习CryptoZombie第四课:僵尸作战系统(0个评论)
    • 智能合约Solidity学习CryptoZombie第三课:组建僵尸军队(高级Solidity理论)(0个评论)
    • 智能合约Solidity学习CryptoZombie第二课:让你的僵尸猎食(0个评论)
    • 智能合约Solidity学习CryptoZombie第一课:生成一只你的僵尸(0个评论)
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2016-11
    • 2018-04
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2022-01
    • 2023-07
    • 2023-10
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客