如何在python中异步处理xml?

时间:2010-01-18 23:59:58

标签: python xml multithreading sax

我有一个大的XML数据文件(> 160M)要处理,看起来像SAX / expat / pulldom解析是要走的路。我希望有一个线程可以筛选节点并将节点推送到队列中,然后其他工作线程将下一个可用节点拉出队列并进行处理。

我有以下(它应该有锁,我知道 - 它会,稍后)

import sys, time
import xml.parsers.expat
import threading

q = []

def start_handler(name, attrs):
    q.append(name)

def do_expat():
    p = xml.parsers.expat.ParserCreate()
    p.StartElementHandler = start_handler
    p.buffer_text = True
    print("opening {0}".format(sys.argv[1]))
    with open(sys.argv[1]) as f:
        print("file is open")
        p.ParseFile(f)
        print("parsing complete")


t = threading.Thread(group=None, target=do_expat)
t.start()

while True:
    print(q)
    time.sleep(1)

问题是while块的主体只被调用一次,然后我甚至无法ctrl-C中断它。在较小的文件上,输出是预期的,但这似乎表明只有在完全解析文档时才调用处理程序,这似乎违背了SAX解析器的目的。

我确定这是我自己的无知,但我不知道我在哪里弄错了。

PS:我也尝试过改变start_handler

def start_handler(name, attrs):
    def app():
        q.append(name)
    u = threading.Thread(group=None, target=app)
    u.start()
但是,没有爱。

4 个答案:

答案 0 :(得分:7)

我对这个问题不太确定。我猜测对ParseFile的调用是阻塞的,因为GIL只运行解析线程。解决这个问题的方法是使用multiprocessing代替。无论如何,它的目的是与队列一起工作。

您制作Process,然后可以传递Queue

import sys, time
import xml.parsers.expat
import multiprocessing
import Queue

def do_expat(q):
    p = xml.parsers.expat.ParserCreate()

    def start_handler(name, attrs):
        q.put(name)

    p.StartElementHandler = start_handler
    p.buffer_text = True
    print("opening {0}".format(sys.argv[1]))
    with open(sys.argv[1]) as f:
        print("file is open")
        p.ParseFile(f)
        print("parsing complete")

if __name__ == '__main__':
    q = multiprocessing.Queue()
    process = multiprocessing.Process(target=do_expat, args=(q,))
    process.start()

    elements = []
    while True:
        while True:
            try:
                elements.append(q.get_nowait())
            except Queue.Empty:
                break

        print elements
        time.sleep(1)

我已经包含了一个元素列表,只是为了复制原始脚本。您的最终解决方案可能会使用get_nowaitPool或类似的东西。

答案 1 :(得分:7)

ParseFile,正如您已经注意到的那样,只需“吞下”所有内容 - 对于您想要执行的 incremental 解析没有任何好处!因此,只需一次将文件提供给解析器,确保有条件地控制其他线程 - 例如:

while True:
  data = f.read(BUFSIZE)
  if not data:
    p.Parse('', True)
    break
  p.Parse(data, False)
  time.sleep(0.0)

time.sleep(0.0)调用是Python的说法“如果有任何准备就绪并等待其他线程”; Parse方法已记录在案here

第二点是,忘记这个用法的锁! - 使用Queue.Queue代替,它本质上是线程安全的,几乎总是在Python中协调多个线程的最佳和最简单的方法。只需在其上创建一个Queue实例qq.put(name),并在q.get()上进行线程阻塞等待进行更多工作 - 这很简单!

(当没有更多的工作要做时,你可以使用几种辅助策略来协调工作线程的终止,但是最简单的,没有特殊要求的是,只要让它们成为守护进程线程,所以它们都将终止主线程的时候 - 见the docs)。

答案 2 :(得分:1)

我唯一看到的错误是你从不同的线程同时访问q - 你写的确没有锁定。这就是在寻找麻烦 - 而且你可能会以Python解释器锁定你的方式遇到麻烦。 :)

尝试锁定,这真的不是很难:

import sys, time
import xml.parsers.expat
import threading

q = []
q_lock = threading.Lock() <---

def start_handler(name, attrs):
    q_lock.acquire() <---
    q.append(name)
    q_lock.release() <---

def do_expat():
    p = xml.parsers.expat.ParserCreate()
    p.StartElementHandler = start_handler
    p.buffer_text = True
    print("opening {0}".format(sys.argv[1]))
    with open(sys.argv[1]) as f:
        print("file is open")
        p.ParseFile(f)
        print("parsing complete")


t = threading.Thread(group=None, target=do_expat)
t.start()

while True:
    q_lock.acquire() <---
    print(q)
    q_lock.release() <---
    time.sleep(1)

你知道,它非常简单,我们只是创建了一个锁变量来保护我们的对象,并且每次在我们使用对象之前获取该锁,并在每次完成对象上的任务后释放。这样,我们保证q.append(name)永远不会与print(q)重叠。


(对于较新版本的Python,还有一个“with ....”语法可以帮助您不要经常忘记释放锁或关闭文件或其他清理。)

答案 3 :(得分:0)

我对实现并不了解很多,但如果解析是直到完成执行的C代码,那么其他Python线程将无法运行。如果解析器正在回调Python代码,则可能会释放GIL以供其他线程运行,但我不确定。您可能想查看这些详细信息。