lxml parse方法读取整个文档并在内存中构建一个树。相对于cElementTree,lxml 树的开销要高一些,因为它保持了更多有关节点上下文的信息,包括对其父节点的引用。使用这种方法解析一个2G 的文档时,
会使一个具有2G RAM 的机器进入交换,这会大大影响性能。假设在编写应用程序时这些数据在内存中
可用,那么将要执行较大的重构。
迭代解析
如果构建内存树并不是必须的或并不实际,则可以使用一种迭代解析技术,这种技术不需要读取整个源树。
lxml 提供了两种方法:
∙提供一个目标解析器类
∙使用iterparse方法
python处理xml文件使用目标解析器方法
目标解析器方法对于熟悉SAX 事件驱动代码的开发人员来说应该不陌生。目标解析器是可以实现以下方
法的类:
1. start在元素打开时触发。数据和元素的子元素仍不可用。
2. end在元素关闭时触发。所有元素的子节点,包括文本节点,现在都是可用的。
3. data触发文本子节点并访问该文本。
4. close在解析完成后触发。
清单 2 演示了如何创建实现所需方法的目标解析器类(这里称为TitleTarget)。这个解析器在一个内部列表()中收集Title元素的文本节点,并在到达close()方法后返回列表。
在运行版权数据时,代码运行时间为54 秒。目标解析可以实现合理的速度并且不会生成消耗内存的解析
树,但是在数据中为所有元素触发事件。对于特别大型的文档,如果只对其中一些元素感兴趣,那么这种
方法并不理想,就像在这个例子中一样。能否将处理限制到选择的标记并获得较好的性能呢?
使用iterparse方法
lxml 的iterparse方法是ElementTree API 的扩展。iterparse为所选的元素上下文返回一个Python 迭代器。它接受两个有用的参数:要监视的事件元组和标记名。在本例中,我只
对<Title>的文本内容感兴趣(达到end事件即可获得)。清单 3 的输出与清单2的目标解析器方法的输出相同,但是速度应该会提高很多,因为lxml 可以在内部优化事件处理。同时也会减少代码量。
这里发生了什么?尽管iterparse起初并没有消耗整个文件,但它也没有释放对每一次迭代的节点的引用。当对整个文档进行重复访问时,这点必须注意。不过,在本例中,我选择在每次循环结束后回收内存。这包括对已经处理的子节点和文本节点的引用,以及对当前节点前面的兄弟节点的引用。这些引用中来自根节点的引用也被隐式地保留,如清单 4 所示:
为简单起见,我将清单 4 重构为一个函数,它接受一个可调用的func对当前节点执行操作,如清单5 所示。我将在后面的示例中使用这个方法。
性能特点
清单 4 中的iterparse方法经过优化后生成的输出与清单 2 中目标解析器生成的输出相同,但只用了一半的时间。当处理特定事件或标记名(比如本例)时,处理速度甚至比cElementTree 还快。(但是,大多数情况下,如果解析是主要活动的话,cElementTree 的表现要比lxml 优秀)。
表 1 展示了各种解析器技术在基准测试侧边栏中描述的计算机上测试版权数据使用的时间。
它的伸缩性如何?
对Open Directory 数据使用清单4 中的iterparse方法,每次运行耗时122 秒,约是解析版权数据所用时间的 5 倍。由于Open Directory 数据的数量也约是版权数据的 5 倍(1.9 GB),这种方法应该表现出非常好性能,对特别大的文件尤其如此。
回页首序列化
如果对XML 文件所做的全部操作只是从单个节点获取一些文本,可以使用一个简单的正则表达式,其处理速度可能会比任何XML 解析器都快。但是在实践中,如果数据非常复杂,则几乎不可能完成任务,因此不推荐使用这种方法。在需要真正的数据操作时,XML 库的价值是不可估量的。
将XML 序列化为一个字符串或文件是lxml 的长项,因为它依赖于libxml2 C 代码库。如果要执行要求序列化的任务,lxml 无疑是最佳选择,但是需要使用一些技巧来获得最佳性能。
在序列化子树时使用deepcopy
lxml 保持子节点及其父节点之间的引用。该特性的一个特点就是lxml 中的节点有且仅有一个父节点(cElementTree 没有父节点)。
清单 6 包含版权文件中的所有<Record>,并写入了一条只包含标题和版权信息的简化记录。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论