教你如何用python轻轻松松解析XML和PDF,一文就够了,赶紧码住!!!

3,433 阅读11分钟

Python 解析 XML

XML的诞生

XML 全称 Extensible Markup Language,中文译为可扩展标记语言

XML 之前有两个先行者:SGMLHTML,率先登场的是 SGML, 尽管它功能强大,但文档结构复杂,既不容易学也不易于使用,因此几个主要的浏览器厂商均拒绝支持 SGML,这些因素限制了 SGML 在网上的传播性;

1989 年 HTML 登场,它继承了 SGML 诸多优点,去除了 SGML 复杂庞大的缺点,HTML 在数据显示上表现十分出色,但它的语法是不可扩展的,因此其无法描述数据、可读性差,没办法人们再次将目光转向 SGML,经过对 SGML 一系列改造,终于在 1998 年,XML 第一个版本问世

简单来说就是:XML 和 HTML 均由 SGML 改造而来HTML 是一种页面技术,聚焦的是数据的显示,而 XML 易于扩展,主要用来传送和存储数据,聚焦的是数据的内容。

解析

解析方式

Python 有三种 XML 解析方式:SAX(simple API for XML)DOM(Document Object Model)ElementTree

  • DOM 方式:DOM 中文译为文档对象模型,是 W3C 组织推荐的标准编程接口,它将 XML 数据在内存中解析成一个树,通过对树的操作来操作 XML。
  • SAX 方式:SAX 是一个用于处理 XML 事件驱动的模型,它逐行扫描文档,一边扫描一边解析,对于大型文档的解析拥有巨大优势,尽管不是 W3C 标准,但它却得到了广泛认可。
  • ElementTree 方式:ElementTree 相对于 DOM 来说拥有更好的性能,与 SAX 性能差不多,API 使用也很方便。

具体实现

在具体解析之前我们先准备一个 XML,命名为test.xml,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<list>
<student id="stu1" name="stu">
   <id>1001</id>
   <name>张三丰</name>
   <age>22</age>
   <gender></gender>
</student>
<student id="stu2" name="stu">
   <id>1002</id>
   <name>李牛花</name>
   <age>25</age>
   <gender></gender>
</student>
</list>

DOM 方式解析

使用 DOM 方式,首先要对其 API 有一定了解,如果不了解,网上的教程也比较多,比如:DOM 教程,下面看一下使用示例。

from xml.dom.minidom import parse

# 读取文件
dom = parse('test.xml')
# 获取文档元素对象
data = dom.documentElement
# 获取 student
stus = data.getElementsByTagName('student')
for stu in stus:
	# 获取标签属性值
    st_id = stu.getAttribute('id')
    st_name = stu.getAttribute('name')
	# 获取标签中内容
    id = stu.getElementsByTagName('id')[0].childNodes[0].nodeValue
    name = stu.getElementsByTagName('name')[0].childNodes[0].nodeValue
    age = stu.getElementsByTagName('age')[0].childNodes[0].nodeValue
    gender = stu.getElementsByTagName('gender')[0].childNodes[0].nodeValue
    print('st_id:', st_id,  ', st_name:',st_name)
    print('id:', id, ', name:', name, ', age:', age, ', gender:',gender)

输出结果:
在这里插入图片描述
通过输出结果,我们可以发现已经获取了标签属性值和标签内容了。

SAX 方式解析

使用 SAX 解析 XML 文档主要涉及到解析器和事件处理器

  • 解析器负责读取 XML 文档,并向事件处理器发送事件
  • 事件处理器负责对事件作出响应,对传递的 XML 数据进行处理。

Python 使用 SAX 处理 XML 需要用到 xml.sax 中的 parse 函数和 xml.sax.handler 中的 ContentHandler 类,下面看一下 ContentHandler 类中的一些方法。

  • characters(content):调用时机:从行开始,遇到标签之前,存在字符,content 的值为这些字符串;从一个标签,遇到下一个标签之前, 存在字符,content 的值为这些字符串;从一个标签,遇到行结束符之前,存在字符,content 的值为这些字符串。
  • startDocument():文档启动的时候调用。
  • endDocument():解析器到达文档结尾时调用。
  • startElement(name, attrs):遇到 XML 开始标签时调用,name 是标签的名字,attrs 是标签的属性值字典。
  • endElement(name):遇到 XML 结束标签时调用。

下面通过示例看一下如何通过 SAX 方式解析 XML。

import xml.sax

class StudentHandler(xml.sax.ContentHandler):
    def __init__(self):
        self.id = ""
        self.name = ""
        self.age = ""
        self.gender = ""

    # 元素开始调用
    def startElement(self, tag, attributes):
        self.CurrentData = tag
        if tag == "student":
            stu_name = attributes["name"]
            print("stu_name:", stu_name)

    # 元素结束调用
    def endElement(self, tag):
        if self.CurrentData == "id":
            print("id:", self.id)
        elif self.CurrentData == "name":
            print("name:", self.name)
        elif self.CurrentData == "age":
            print("age:", self.age)
        elif self.CurrentData == "gender":
            print("gender:", self.gender)
        self.CurrentData = ""

    # 读取字符时调用
    def characters(self, content):
        if self.CurrentData == "id":
            self.id = content
        elif self.CurrentData == "name":
            self.name = content
        elif self.CurrentData == "age":
            self.age = content
        elif self.CurrentData == "gender":
            self.gender = content

if (__name__ == "__main__"):
    # 创建 XMLReader
    parser = xml.sax.make_parser()
    # 关闭命名空间
    parser.setFeature(xml.sax.handler.feature_namespaces, 0)
    # 重写 ContextHandler
    Handler = StudentHandler()
    parser.setContentHandler(Handler)
    parser.parse("test.xml")

输出结果:
在这里插入图片描述

ElementTree 方式解析

Python 提供了两种 ElementTree 的实现方式。

  1. 纯 Python 实现的 xml.etree.ElementTree
  2. C 语言实现 xml.etree.cElementTree,使用 C 语言实现的方式速度更快且内存消耗更少。

Python3.3 之后,ElemenTree 模块会自动优先使用 C 加速器,如果不存在 C 实现,则会使用 Python 实现。因此,使用 Python3.3+ 时,只需要 import xml.etree.ElementTree 即可。下面看一下示例。

import xml.etree.ElementTree as ET

tree = ET.parse("test.xml")
# 根节点
root = tree.getroot()
# 标签名
print('root_tag:',root.tag)
for stu in root:
    # 属性值
    print ("stu_name:", stu.attrib["name"])
    # 标签中内容
    print ("id:", stu[0].text)
    print ("name:", stu[1].text)
    print("age:", stu[2].text)
    print("gender:", stu[3].text)

输出结果:
在这里插入图片描述

Python 解析 PDF

用 Python 如何解析 PDF ,从它的表现来看,它更像是一张图片,在一张白纸上把内容摆放在固定的位置上,没有逻辑结构

正是因为 PDF 没有统一的规范,也没有逻辑结构,比如句子或段落,并且不能自适应页面大小的调整。今天要介绍的 PDFMiner 尝试通过猜测它们的布局来重建它们的结构,但是并不能保证一定能识别成功,尤其是对图片和表格的识别处理会差一些。

安装 PDFMiner

解析 PDF 需要用到 pdfminer 库,目前最新版本只支持 Python3.6 及以上 ,执行如下安装命令:

C:\Users\Y>pip install pdfminer
Looking in indexes: https://pypi.doubanio.com/simple
Collecting pdfminer
  Downloading https://pypi.doubanio.com/packages/71/a3/155c5cde5f9c0b1069043b2946a93f54a41fd72cc19c6c100f6f2f5bdc15/pdfminer-20191125.tar.gz (4.2MB)
     |████████████████████████████████| 4.2MB 930kB/s
Collecting pycryptodome (from pdfminer)
  Downloading https://pypi.doubanio.com/packages/44/72/3db473557ae21238229f3805de723d1fe34fa337d08cf94877073daf62ef/pycryptodome-3.9.8-cp37-cp37m-win_amd64.whl (14.1MB)
     |████████████████████████████████| 14.1MB 3.3MB/s
Building wheels for collected packages: pdfminer
  Building wheel for pdfminer (setup.py) ... done
  Created wheel for pdfminer: filename=pdfminer-20191125-cp37-none-any.whl size=6140080 sha256=9a1c9db96fae1c4231b2272f6edce3e8ebf92645bdb0a318e0952d751b3d70bf
  Stored in directory: C:\Users\Y\AppData\Local\pip\Cache\wheels\95\e4\2f\493db6cf7b26fd7b962c543b0560cbc13d42193890a39a1c65
Successfully built pdfminer
Installing collected packages: pycryptodome, pdfminer
Successfully installed pdfminer-20191125 pycryptodome-3.9.8

OK,如果提示以上信息则安装成功。

解析概述

由于PDF文件有如此大和复杂的结构,完整解析 PDF 文件很费时费力的。因此 PDFMiner 采用了一个懒惰分析的策略,就是只分析你所需要的部分。换句话就是说,根据你自己的需要只解释出你要的那部分就可以了。这里有两个核心类是必须的 PDFParserPDFDocument,除了这两个模块还有以下几个模块来配合使用。

模块名说明
PDFParser从文件中获取数据
PDFDocument存储文档数据结构到内存中
PDFPageInterpreter解析page内容
PDFDevice把解析到的内容转化为你需要的东西
PDFResourceManager存储共享资源,例如字体或图片等

下面这个图表示了 PDFMiner 各模块之间的关系:
在这里插入图片描述

基本用法

首先我准备了一个 pdf 格式的文档,内容基本如下图这样:
在这里插入图片描述
下面这段代码给出了 PDFMiner 解析 PDF 文档的基本方法:

  • 打开 pdf 文件,创建解析对象,存储文档结构,创建资源管理对象以及共享资源
  • 再创建 device 对象
  • 再创建文档解析对象,并处理文档中的每一页

是不是看起来很复杂,不过确实也挺麻烦,还是让我们直接看代码吧。

# 导入库
from pdfminer.pdfparser import PDFParser
from pdfminer.pdfdocument import PDFDocument
from pdfminer.pdfpage import PDFPage
from pdfminer.pdfpage import PDFTextExtractionNotAllowed
from pdfminer.pdfinterp import PDFResourceManager
from pdfminer.pdfinterp import PDFPageInterpreter
from pdfminer.pdfdevice import PDFDevice
from pdfminer.layout import LAParams
from pdfminer.converter import PDFPageAggregator

# 设置文档密码
password = ''

#打开pdf文件
fp = open('pdf_test.pdf','rb')

#从文件句柄创建一个pdf解析对象
parser = PDFParser(fp)

#创建pdf文档对象,存储文档结构
document = PDFDocument(parser,password)

#创建一个pdf资源管理对象,存储共享资源
rsrcmgr = PDFResourceManager()

#创建一个device对象
device = PDFDevice(rsrcmgr)

#创建一个解释对象
interpreter = PDFPageInterpreter(rsrcmgr, device)

#处理包含在文档中的每一页
for page in PDFPage.create_pages(document):
    interpreter.process_page(page)

这样就完成了将页面对象加载的操作, 运行程序,没有报错就说明页面信息已经成功加载至内存,如下图:
在这里插入图片描述
然后我们就得想办法分别解析各类型信息。

解析对象

布局分析器把 pdf 文档中每一页返回为一个 LTPage 对象。 该对象包含该页面中的所有子对象,它们之间的关系大概如下图所示:
在这里插入图片描述
列出一个表格具体说明下各对象:

对象名对象说明备注
LTPage代表一个完整的页面,可以包含子对象LTTextBox,LTFigure,LTImage,LTRect,LTCurve和LTLine
LTTextBox它包含 LTTextLine 对象的列表,代表一组被包含在矩形区域中的文本注意:该box是根据几何学分析得到的,并不一定准确地表现为该文本的逻辑范围,get_text()方法可以返回文本内容
LTTextLine包含一个LTChar对象的列表,表现为单行文本字符表现为一行或一列,取决于文本书写方式,get_text()方法返回文本内容
LTChar表示一个在文本中的真实的字母,作为一个unicode字符串LTChar 对象有真实的分隔符
LTAnno表示一个在文本中的真实的字母,作为一个unicode字符串LTAnno 对象没有,是虚拟分隔符,按照两个字符之间的关系,布局分析器插入虚拟分隔符
LTFigure表示一个被 PDF Form 对象使用的区域pdf form适用于目前的图表(present figures)或者页面中植入的另一个pdf文档图片,LTFigure对象可以递归
LTImage表示一个图形对象,可以是JPEG或者其他格式但 PDFMiner 目前没有花太多精力在图形对象上
LTLine表示一根直线用来分割文本或图表(figures)
LTRect表示一个矩形用来框住别的图片或者图表
LTCurve代表一个贝塞尔曲线

了解以上对象都表示什么以后,现在我们写一段代码解析一个 pdf 并打印出来所解析的内容。

# 导入库
from pdfminer.pdfparser import PDFParser
from pdfminer.pdfdocument import PDFDocument
from pdfminer.pdfpage import PDFPage
from pdfminer.pdfpage import PDFTextExtractionNotAllowed
from pdfminer.pdfinterp import PDFResourceManager
from pdfminer.pdfinterp import PDFPageInterpreter
from pdfminer.pdfdevice import PDFDevice
from pdfminer.layout import *
from pdfminer.converter import PDFPageAggregator

# 提供初始密码
password = ''
# 没有密码可以初始密码
# document.initialize()

#打开pdf文件
fp = open('pdf_test.pdf','rb')

#从文件句柄创建一个pdf解析对象
parser = PDFParser(fp)

#创建pdf文档对象,存储文档结构
document = PDFDocument(parser, password)

#创建一个pdf资源管理对象,存储共享资源
rsrcmgr = PDFResourceManager()

laparams = LAParams()

#创建一个device对象
device = PDFPageAggregator(rsrcmgr, laparams=laparams)

#创建一个解释对象
interpreter = PDFPageInterpreter(rsrcmgr, device)

#处理包含在文档中的每一页
for page in PDFPage.create_pages(document):
    interpreter.process_page(page)
    layout = device.get_result()
    for x in layout:
        # 获取文本对象
        if isinstance(x, LTTextBox):
            print(x.get_text().strip())
        # 获取图片对象
        if isinstance(x,LTImage):
            print('这里获取到一张图片')
        # 获取 figure 对象
        if isinstance(x,LTFigure):
            print('这里获取到一个 figure 对象')

OK,这次我们把 pdf 文件加载到内存后,循环加载到每个页面对象,并遍历各个对象,使用 isinstance 方法判断对象的类型,将文本对象时直接打印出来,当为其他对象时打印一个字符串,返回结果如下图:
在这里插入图片描述
可以看出 PDFMiner 对文本的解析还是不错的,不过对图片的解析,正如官方文档所说,识别并不是很准确,这里将图片识别为了 figure 对象。另外对于表格的支持也不够好,虽然能读取出来表格内容,但完全看不出表格的样式来,后期还需要进一步处理。

参考

Python3 XML解析:www.runoob.com/python3/pyt…

python-pdfminer 官网:euske.github.io/pdfminer/

Python PDF Parser:github.com/euske/pdfmi…

资源传送门

1\. 关注【做一个柔情的程序猿】公众号
2\. 在【做一个柔情的程序猿】公众号后台回复 【python资料】【2020秋招】 即可获取相应的惊喜哦!

「❤️ 感谢大家」

  1. 点赞支持下吧,让更多的人也能看到这篇内容(收藏不点赞,都是耍流氓 -_-)
  2. 欢迎在留言区与我分享你的想法,也欢迎你在留言区记录你的思考过程。
  3. 觉得不错的话,也可以阅读近期梳理的文章(感谢各位的鼓励与支持):