快速支持XPath的Python XML验证器

5 投票
2 回答
2095 浏览
提问于 2025-04-15 19:31

我需要读取一个很大的XML文件(65MB),并且要根据一个xsd文件来验证它,还要在这个XML上运行XPath查询。下面,我给出了一个使用lxml的版本。运行这个查询花了很多时间(超过5分钟),不过验证的速度似乎还挺快的。

我有几个问题。一个注重性能的Python程序员会怎么用lxml来写这个程序?其次,如果lxml不适合这个任务,还有什么其他选择?能不能给我一个代码示例?

import sys
from datetime import datetime
from lxml import etree

start = datetime.now()
schema_file = open("library.xsd")
schema = etree.XMLSchema(file=schema_file)
parser = etree.XMLParser(schema = schema)
data_file = open(sys.argv[1], 'r')
tree = etree.parse(data_file, parser)
root = tree.getroot()
data_file.close()
schema_file.close()
end = datetime.now()
delta = end-start
print "Parsing time = ", delta

start = datetime.now()
name_list = root.xpath("book/author/name/text()")
print ("Size of list = " + str(len(name_list)))
end = datetime.now()
delta = end-start
print "Query time = ", delta

2 个回答

0

我在想,能不能把这个xpath表达式改写得更快一点?有一个方法可能有效,就是避免构建name_list这个节点集合(如果后面不需要用到的话),直接在lxml里面计算节点数量。可以试试这样:

start = datetime.now()
name_list_len = root.xpath("count(/book/author/name/text())")
print ("Size of list = " + str(name_list_len))
end = datetime.now()

另外,你可能会发现使用expat解析器提取文本会更快,但它不进行验证,而且使用起来更复杂(你需要写一个状态机和几个回调函数)。如果你只是想要文本,使用元素树API的C实现可能会更快。lxml的基准测试也很有意思,似乎暗示用这个方法提取文本会更快。

一个常见的xpath性能问题是表达式开头不必要地使用'//'。在这种情况下,如果文档结构允许,改成绝对路径,比如:

 name_list = root.xpath("/rootelement/book/author/name/text()")

可能会快很多。不过在这里应该不是问题。

0

这个lxml性能测试非常有用。根据我的观察,使用XPath提取元素节点的速度很快,但提取文本的速度可能会比较慢。下面我提供了三种解决方案,它们的速度都很快。

def bench_lxml_xpath_direct(root): # Very slow but very fast if text() is removed.
  name_list = root.xpath("book/author/name/text()")
  print ("Size of list = " + str(len(name_list)))

def bench_lxml_xpath_loop(root): # Fast
  name_list = root.xpath("book/author/name")
  result = []
  for n in name_list:
    result.append(n.text)

  print ("Size of list = " + str(len(name_list)))

def bench_lxml_getiterator(tree): # Very fast
  result = []
  for name in tree.getiterator("name"):
    result.append(name.text)
  print ("Size of list = " + str(len(result)))


def bench_lxml_findall(tree):  # Superfast
  result = []
  for name in tree.findall("//name"):
    result.append(name.text)
  print ("Size of list = " + str(len(result)))

撰写回答