Skip to main content

基于Call-back方法来解析XML文件

11 基于Call-back方法来解析XML文件

​ 在上一个章节中,我们详细讲解了一个商业级别的ETL中间件框架,虽然很轻量,但是毫无疑问,可以胜任一个全国级别的ETL数据中心。例如 X银行的国家级别数据中心的数据集中化管理的工作。如果你的爬虫功夫到位,构建一些实时的资料检索平台自然也是不在话下,比如一个机票查询的平台。

​ 然而,是不是掌握了一套高度扩展的ETL中间件就万事大吉了呢,非也。 这不过是开始进入了商业级大数据应用的殿堂而已。 后续的学习和精进主要三个层面。

1、使用面向对象的软件工程方法(object oriented software engineering) 的方法,快速构建需求模型。

2、掌握一系列的软件和中间件,敏捷的将需求模型转换为实现模型。

3,软件系统上线以后,及时的进行优化资源的配置,提高硬件的利用率

这三个方面的工作,需要的技能有各不相同。

1,需求模型方面的工作,主要需要掌握OOSE方面的基本知识,并持续不断的进行实践。

2,实现模型方面的工作,则需要有良好的外语基础,学习和实践大量CI/CD方面的工具和解决方案。

3,而对软件运行效率方面的优化,则主要需要设计模式(Design pattern) 方面的学习和实践。

所以,我们来解读一下,如果要成为一个出色的大数据专业人才,你需要熟悉OOSE的工作方法,熟练掌握英文检索外文资料,并且具备熟练的DP的设计能力。 所以,这个大数据方面的专才,是很难速成。 需要在很多细微的方面下功夫。

11.1 职场大数据新人与xml文件

​ 在本培训教程的前传《Python Big Data starter 》(python 大数据开发入门篇)中,我们曾经说到过从一个职场新人,成长为职场专家的困难。而xml解析就是这方面很大的一个难点问题。

​ 很多时候,前辈也告知了成长的路径,但是很多软件工具在落地的时候,往往就非常的艰困。 在2012年,尝试使用kettle中间件进行开发的时候,我们进行的第二个项目,就是要实现XML的解析处理。 由于处理的XML的字段比较多,必须要使用一种流式的解决方案。研究了很久都无法解决,后来找了我们城市的一所大学中研究生帮忙,才顺利的解决了这个问题。所以,在这里把当初的问题在分享一下,这样聪明的你一定会了解,设计模式对于大数据工程师来说的重要性。

11.1.1 XML文件的kettle解析方案

​ 用kettle把XML文档转换成数据表结构,是ETL分析中非常常见的方案。在kettle中Get data from xml 步骤和 XML Input Stream (StAX)步骤读取并解析xml文件。

Get data from xml 步骤使用dom方式解析,比较消耗内存,当文件很大时,就不可取。XML Input Stream (StAX)步骤使用完全不同的方式解析大而复杂的文件,且能快速载入数据,所以建议使用该步骤。

以上是kettle 官方文档的说明:

11 1 STAX in kettle

前期案例回顾

​ 虽然,我们完全是基于python+celery+rabbitmq的大数据开发框架,基本上不太可能在去应用kettle框架了。这里,还是希望简单的回顾一下kettle的解决方案。这样做的目的,一方面是对当初帮助我们探索kettle的研究生老师(当时他在一所985大学读研究生,后来毕业后去了南方)致敬,而来也想和大家分享,找到一条适合的道路成为一个职场达人,是多么的不容易,如果得到了就一定要珍惜,坚持走下去。

用XML处理单一表格可以通过简单的数据操作来完成。如果需要从一个xml中提出去多个表格的数据,则需要比较复杂的流程。面对多个表格主要的解决思路就是,为每个数据加入标识符,表示这个数据属于某个表格,然后在将数据提出出来,存入不同表格。大致框图如下所示:

11 2 kettle sax solution

第一部分:XML数据输入和基本的过滤。(红色部分)

XML输入的数据都是按照结点和属性进行划分,其中很多的无用数据,可以通过一些简单的步骤进行过滤。

第二部分:XML数据标记。(蓝色部分)

XML数据本身没有带有属性,因此必须将XML的数据加入属性字段进行标记。可以通过如下的java代码进行处理。

// Repeat id and name in each row

private int pos_xml_data_name;

private int pos_xml_data_value;

private int pos_new_field_1;

private int pos_new_field_2;

private int pos_new_field_3;

private String xml_data_name_1;

private String xml_data_name_2;

private String xml_data_name_3;

private String last_value_1;

private String last_value_2;

private String last_value_3;

此处有省略

第三步:进行字段筛选,获得需要的信息。(紫色部分)

一般可以通过多步筛选,实现数据净化。

第四步:数据拆分。(黑色部分)

第三步输出就是单表xml操作的输出,因此直接进行字段拆分获取相应的格式,并存入数据库。

11.1.2Python的方案

11 3 3 XML parser

Python 提供三种场景的XML方案, 其中ElememtTree 的方案 可以提供一种即易于理解,又高效利用内存的解决方案。所以当时,我们介绍的是基于ET的XML的解析方案。

SAX本质上是一种PushAPI的编程模式。而假如我们使用一种push API的模式来进行增量解析,需要使用回调的编程模型,这个对刚开始大数据编程的学习这来说,可能很难理解。所以当时并没有介绍这种方案。

而在本书的第八章,基于komboo的ETL方案中,我们详细讲解了回调编程的内容。 所以我们准备再次讲解SAX解析方案。

11.1.3Callback 编程

使用SAX 进行xml解析的主要思路,见下图所显示:

11 4 event driven parsing

这时候,读者可能感觉很奇怪,既然是一种比较tricky的方案,而且我们也掌握了一种比较简单的ET的方案,那为什么要花费功夫在研究这种tricky的解决方案呢?

原因很简单,因为call back这种设计模式,在大数据的编程中有广泛的应用。 所以我们是从理解设计模式的角度,来解读SAX解析方法。 而call back 的应用价值,可以见下图:

11 5 SAX call back

11.2XML解析的用例模型

​ XML 文件是一种具有自描述特性的文件,非常适合表达一些复杂的信息结构,所以也是一种非常重要的文件格式。 如何将XML文件转换为方便阅读的CSV格式,并从中提取需要的信息结构, 是非常重要的一种数据结构。

11.2.1 输入数据分析 (I)

​ 在这里,我们假设银行的交易记录是XML格式的数据,相关的组成结构见下图所显示:

11 6 xml based ticket

​ 图 4-1 XML 格式的交易清单的组成结构

从上图可见, 相关的XML的数据包含以下的组成部分。

1, XML 的header 部分: 包含信息模型参考索引, 产生xml文件的营业网点名称, XML中包含的交易清单的开始时间和结束时间。

2,交易清单的表头部分(信息结构的名称): 描述交易ticket的全部信息结构的名称,以list列表的方式来呈现(信息结构的名称在”value”部分进行表达)。

3,交易清单的数据部分(休息结构的取值):描述交易ticket的全部信息结构的取值,以list列表的方式来呈现(信息结构的取值在“value”部分进行表达)。

根据上面的格式,我们可以提供三个xml 文件。

文件的命名为:

QLR2019020710-001.xml

QLR2019020710-002.xml

QLR2019020710-003.xml

关于输入数据的描述,参考以下文件。

11.2.2 XML 解析处理过程(P)

1,用户界面向处理组件传递参数,入参包括 需要 进行xml解析的文件清单,以及期望的解析后的CSV文件清单。

2,处理组件接收入参以后, 首先根据文件清单中的文件绝对路径,通过文件系统,识别需要处理的文件列表。

3、处理组件读取一个配置文件, ticket_infofield.py 配置文件,确定需要提取的信息结构。

3,处理组件提取第一个文件,根据文件的后缀名称确定为xml格式,就调用xml处理组件,首先识别出xml文件的ticket_name部分的信息结构取值,将相关的信息结构传入到一个list当中。

4,处理组件将ticket_infofield.py 中的定义的需要提取的信息结构名字的列表(list),和当前xml文件中识别出来的ticket_name部分的信息结构(list)进行比较,如果后者包含前者,这说明文件可以解析,转入后续的环节;否则,则提示当前文件错误。

5,处理组件根据入参中期望解析的csv文件名称和绝对路径,打开一个CSV文件的句柄, 并根据ticket_infofield.py 中的定义的需要提取的信息结构名字的列表(list)顺序,写入CSV的表头数据。

6,处理组件接下来。从当前的xml文件中识别出ticket_data部分的信息结构的取值, 根据并根据ticket_infofield.py 中的定义的需要提取的信息结构名字的列表名字,匹配出需要提取的ticket_data数据,处理完一条ticket交易记录,就在上一步操作中,打开的文件中,写入一行数据;直到把所有的行记录处理完毕。

7,当前的文件处理完毕以后, 处理组件会打开一个结果list,记录本次的处理结果。包含2个信息结构,[process result, xml-file-size],

其中,process result=0,处理成;=1,处理失败。

​ Xml-file-size 记录生成的xml文件的大小。

8, 处理组件,继续完成后续文件的处理,并生成返回值。

9, 全部文件处理完毕以后,处理组件返回,处理结果的返回数值。

11.2.3 ticket_infofield.py

ticket_infofield.py 是一个python的配置文件,用于配置需要提取的信息结构的字段。

11 7 ticket infor

11.2.4 返回数值

返回值也是一个list, 包含了每个文件的处理结果。

[ [ 0,size],[0,size],[0,xize] ]

11 8 return value of xml

11.3 SAX 方案简介

参考一下方案:

https://www.cnblogs.com/hongfei/p/python-xml-sax.html

11.3.1 SAX方案的特点

1、是基于事件的 API

2、在一个比 DOM 低的级别上操作

3、为您提供比 DOM 更多的控制

4、几乎总是比 DOM 更有效率

5、但不幸的是,需要比 DOM 更多的工作

相关的方案,是基于事件驱动的方案。

基于事件的语法分析器将事件发送给应用程序。这些事件类似于用户界面事件,例如,浏览器中的 ONCLICK 事件或者 Java 中的 AWT/Swing 事件。事件通知应用程序发生了某件事并需要应用程序作出反应。在浏览器中,通常为响应用户操作而生成事件:当用户单击按钮时,按钮产生一个 ONCLICK 事件。

在 XML 语法分析器中,事件与用户操作无关,而与正在读取的 XML 文档中的元素有关。有对于以下方面的事件:

1、元素开始和结束标记

2、元素内容

3、实体

4、语法分析错误

11.3.2 一个简单的案例

下面,我们来讨论一个简单的案例

11 9 语法分析器的结构

XML 语法分析器读取并解释该文档。每当它识别出文档中的某些内容,就会生成一个事件。

1、读取 清单 1 时,语法分析器首先读取 XML 声明并生成文档开始事件。当它遇到第一个开始标记 <xbe:price-list> 时,语法分析器生成它的第二个事件来通知应用程序已经遇到了 price-list 元素。

2、接下来,语法分析器看到 product 元素的开始标记(为简单起见,在本文其余部分,我将忽略名称空格和缩进空格)并生成它的第三个事件。在开始标记后,语法分析器看到 product 元素的内容: XML Training ,它产生另一个事件。下一个事件指出 product 元素的结束标记。语法分析器已经完成了对 product 元素的语法分析。到目前为止,它已经激发了 5 个事件: product 元素的 3 个事件,一个文档开始事件和一个 price-list 开始标记事件。

3、语法分析器现在移动到第一个 price-quote 元素。它为每个 price-quote 元素生成两个事件:一个开始标记事件和一个结束标记事件。是的,即使将结束标记简化为开始标记中的 / 字符,语法分析器仍然生成一个结束事件。有 4 个 price-quote 元素,所以语法分析器在分析它们时生成 8 个事件。

4、最后,语法分析器遇到 price-list 的结束标记并生成它的最后两个事件:结束 price-list 和文档结束。

如图 5 所示,这些事件共同向应用程序描述了文档树。开始标记事件意味着“转到树的下一层”,而结束标记元素意味着“转到树的上一层”

11 10 xml tree scanning

​ 图 5 XML tree scanning

请注意,语法分析器传递了足够信息以构建 XML 文档的文档树,但是与 DOM 语法分析器不同,它并不显式地构建该树。

11.3.3 Python的解决方案

使用Python解析XML的时候,需要 import xml.sax 和 xml.sax.handler

1.3.3.1 Xml SAX

xml.sax提供了3个函数以及 SAX 异常类

1、make_parser方法

创建并返回一个SAX XMLReader对象

xml.sax.make_parser([parser_list])

· parser_list - 可选参数,解析器列表

2、parse方法

创建一个 SAX 解析器并解析xml文档

xml.sax.parse(filename_or_stream, handler[, error_handler])  

· file_or_stream:xml文件名

· handler:必须是一个ContentHandler的对象

· error_handler:如果指定该参数,errorhandler必须是一个SAX ErrorHandler对象

3、parseString方法

创建一个XML解析器并解析xml字符串

xml.sax.parseString(string, handler[, error_handler])

· string: xml字符串

· handler:必须是一个ContentHandler的对象

· error_handler:如果指定该参数,errorhandler必须是一个SAX ErrorHandler对象

1.3.3.2 Contenthandler 类方法

· characters(content)方法

o 调用时机

o 从行开始,遇到标签之前,存在字符,content的值为这些字符串。

o 从一个标签,遇到下一个标签之前, 存在字符,content的值为这些字符串。

o 从一个标签,遇到行结束符之前,存在字符,content的值为这些字符串。

o 标签可以是开始标签,也可以是结束标签。

· startDocument()方法:文档启动的时候调用。

· endDocument()方法:解析器到达文档结尾时调用。

· startElement(name, attrs)方法:遇到XML开始标签时调用,name是标签的名字,attrs是标签的属性值字典。

· endElement(name)方法:遇到XML结束标签时调用。

11.3.4 待解析XML文件的树形结构

借助上面的树形结构的概念,我们来看看银行的ticket交易清单的XML文件的树形结构。

11 11 xml tree structure

图 4-3 银行 ticket 的XML 树形结构

从上图可见, 我们将一个具体的交易清单记录封装在子节点measurement下。 在这个节点下,一共有三个字节点:

子节点1: objecttype 用于描述这个数据结构在现实世界的对象名称。

子节点2:ticket name 用于描述银行交易清单的表头结构(column field) 。

子节点3: ticket data 用于记录多条具体的交易记录的数据。子节点三下面还有多个子节点, 子节点的数量和ticket data中包含的记录数相当。

从上面的描述中,聪明的你是否感觉到很奇怪,为什么要把ticket name,和ticket data 分离在不同的子节点中。 因为从现实的事件来看,ticket name 代表的是key, 而ticket data 代表的是 value。 大家直观的想法,是参考fileheader 这个节点的表达方式,将key ,和value 放置在同一个节点中进行表达?

这是因为,在现实的情况中,可能会存在着很多条交易记录, 假如将(key,value )的结构在同一个节点中表达,那么会耗费很多的空间来保存key的信息,而这些信息是完全一致的,存在大量的冗余信息。

所以在这里,采用的是将ticket name(key), ticket data(value) 进行分离保存的方案。 在这个方案中,压缩了key的保存空间,所以比较优势一些。当然,这种处理方案也存在一个缺点,就是在解码的时候,会增加一些障碍。而这正是python的优势,可以使用一些善巧的结构,例如dict 和set来简洁的处理这种情况。

11.4 Xml.SAX. handler

The SAX API defines four kinds of handlers: content handlers, DTD handlers, error handlers, and entity resolvers. Applications normally only need to implement those interfaces whose events they are interested in; they can implement the interfaces in a single object or in multiple objects. Handler implementations should inherit from the base classes provided in the module xml.sax.handler, so that all methods get default implementations.

Python 的SAX API 定义了四种类型的handler:分别是content handlers, DTD handlers, error handlers, and entity resolvers。应用程序(application)通常仅仅只需要根据他们需要处理的事件来实现这些接口而已;这些接口可以在一个对象中实现,也可以在多个接口中实现。处理程序(application)实现应从xml.sax.handler模块中提供的基类继承,以便所有方法均获得默认实现。

其中ContentHandler是SAX中的主要回调接口,并且对于应用程序来说是最重要的接口。 该界面中事件的顺序反映了XML文档中信息的顺序。

1class xml.sax.handler.ContentHandler

This is the main callback interface in SAX, and the one most important to applications. The order of events in this interface mirrors the order of the information in the document.

4class xml.sax.handler.ErrorHandler

Interface used by the parser to present error and warning messages to the application. The methods of this object control whether errors are immediately converted to exceptions or are handled in some other way.

11.4.1 Content handler object

Users are expected to subclass ContentHandler to support their application. The following methods are called by the parser on the appropriate events in the input document:

主要的方法有:

ContentHandler.``**startDocument**()

Receive notification of the beginning of a document.

The SAX parser will invoke this method only once, before any other methods in this interface or in DTDHandler (except for setDocumentLocator()).

接收到文档开始的事件通知。这是整个xml解码操作过程中首先调用的方法,且只会调用一次。

ContentHandler.``**endDocument**()

Receive notification of the end of a document.

The SAX parser will invoke this method only once, and it will be the last method invoked during the parse. The parser shall not invoke this method until it has either abandoned parsing (because of an unrecoverable error) or reached the end of input.

接收到文档处理结束的通知。 这是整个xml解码操作过程中,最后一次调用的方法,且直会调用一次。

ContentHandler.startElement(name, attrs)

Signals the start of an element in non-namespace mode.

The name parameter contains the raw XML 1.0 name of the element type as a string and the attrs parameter holds an object of the Attributes interface (see The Attributes Interface) containing the attributes of the element. The object passed as attrs may be re-used by the parser; holding on to a reference to it is not a reliable way to keep a copy of the attributes. To keep a copy of the attributes, use the copy() method of the attrs object.

在非命名空间模式下发送的xml文档的element开始的信号。

name参数包含作为字符串的元素类型的原始XML 1.0名称,而attrs参数保存包含元素属性的Attributes接口的对象(请参见Attributes接口)。

解析器可以重复使用作为attrs传递的对象; 维持attribute引用不是保存属性副本的可靠方法。 要保留属性的副本,请使用attrs对象的copy()方法。

ContentHandler.endElement(name)

Signals the end of an element in non-namespace mode.

The name parameter contains the name of the element type, just as with the startElement() event.

在非命名空间模式下发送的xml文档的element结束的信号。

ContentHandler.characters(content)

Receive notification of character data.

接收到字符数据的事件通知。

The Parser will call this method to report each chunk of character data. SAX parsers may return all contiguous character data in a single chunk, or they may split it into several chunks; however, all of the characters in any single event must come from the same external entity so that the Locator provides useful information.

11.4.2 The Attributes interface

Attributes objects implement a portion of the mapping protocol, including the methods copy(), get(), contains(), items(), keys(), and values(). The following methods are also provided:

Attributes.getLength()

Return the number of attributes.

Attributes.getNames()

Return the names of the attributes.

Attributes.getType(name)

Returns the type of the attribute name, which is normally ‘CDATA’.

Attributes.getValue(name)

Return the value of attribute name.

11.5设计模型

11 12 SAX based XML parser

以上,是相关的解析器的设计方案。 主要的组件包含

1,main()方法: 实现主程序的调用管理,主要的是调用xml.Sax.parse()方法,这个对象Xml SAX提供的解析方法。

2, handler()实例, 实现了Content handler object 提供·的回调接口。 其中根据本次xml的特征,编写了相关的业务逻辑。是本实验中最为关键的部分。

3,error handler() 实例, 实现ErrorHandler**提供的回调接口。**

1.6 总结

在本章节中,我们回顾了xml的sax解析方案,并再次对回调编程进行了总结。

1, 在大数据的应用中,资源调优非常重要,所以需要加强设计模式方面的练习。

2,SAX解析方案,因为可以极大的节省内存,是一种非常好的解析方案。

3,SAX解析方案中,语法分析器传递了足够信息以构建 XML 文档的文档树,但是与 DOM 语法分析器不同,它并不显式地构建该树,这需要结合处理逻辑,来控制信息结构的提取过程。

Starter
MicroServ
Tutorials
Blog