完全流式XML PAR

2024-09-27 00:12:00 发布

您现在位置:Python中文网/ 问答频道 /正文

我试图使用requestslxml和{a4}使用ExchangeGetAttachmentweb服务。此服务在soapxmlhttp响应中返回base64编码的文件。文件内容包含在单个XML元素的单行中。GetAttachment只是一个例子,但问题更普遍。在

我想将解码后的文件内容直接流式传输到磁盘,而不必在任何时候将附件的全部内容存储在内存中,因为一个附件可能有几个100 MB。在

我试过这样的方法:

r = requests.post('https://example.com/EWS/Exchange.asmx', data=..., stream=True)
with open('foo.txt', 'wb') as f:
    for action, elem in lxml.etree.iterparse(GzipFile(fileobj=r.raw)):
    if elem.tag == 't:Content':
        b64_encoder = Base64IO(BytesIO(elem.text))
        f.write(b64_encoder.read())

但是lxml仍然将附件的副本存储为elem.text。我有什么方法可以创建一个完全流式的XML解析器,它也可以直接从输入流中流式处理元素的内容?在


Tags: 文件方法text元素内容encoder附件流式
1条回答
网友
1楼 · 发布于 2024-09-27 00:12:00

在这种情况下不要使用iterparseiterparse()方法只能发出元素开始和结束事件,因此在找到结束的XML标记后,元素中的任何文本都将被提供给您。在

相反,请使用SAX parser interface。这是XML解析库的通用标准,用于将解析后的数据传递给内容处理程序。^{} callback以块的形式传递字符数据(假设实现XML库实际上利用了这种可能性)。这是一个来自elementtreeapi的低级API,Python标准库已经绑定了Expat解析器来驱动它。在

所以流程就变成了:

  • 将传入的请求流包装在GzipFile中,以便于解压缩。或者,更好的方法是设置response.raw.decode_content = True,并根据服务器设置的内容编码将解压缩留给请求库。在
  • GzipFile实例或原始流传递给使用^{}创建的解析器的^{} method。然后解析器继续从流中分块读取。通过使用make_parser(),您首先可以启用诸如命名空间处理之类的功能(这可以确保在Exchange决定更改每个名称空间使用的短前缀时代码不会中断)。在
  • 内容处理程序characters()方法是用XML数据块调用的;请检查元素start事件是否正确,这样就知道何时需要base64数据。您可以一次解码chunks of (a multiple of) 4 characters中的base64数据,并将其写入文件。我不会在这里使用base64io,只需要自己进行分块。在

简单的内容处理程序可以是:

from xml.sax import handler
from base64 import b64decode

class AttachmentContentHandler(handler.ContentHandler):
    types_ns = 'http://schemas.microsoft.com/exchange/services/2006/types'

    def __init__(self, filename):
        self.filename = filename

    def startDocument(self):
        self._buffer = None
        self._file = None

    def startElementNS(self, name, *args):
        if name == (self.types_ns, 'Content'):
            # we can expect base64 data next
            self._file = open(self.filename, 'wb')
            self._buffer = []

    def endElementNS(self, name, *args):
        if name == (self.types_ns, 'Content'):
            # all attachment data received, close the file
            try:
                if self._buffer:
                    raise ValueError("Incomplete Base64 data")
            finally:
                self._file.close()
                self._file = self._buffer = None

    def characters(self, data):
        if self._buffer is None:
            return
        self._buffer.append(data)
        self._decode_buffer()

    def _decode_buffer(self):
        remainder = ''
        for data in self._buffer:
            available = len(remainder) + len(data)
            overflow = available % 4
            if remainder:
                data = (remainder + data)
                remainder = ''
            if overflow:
                remainder, data = data[-overflow:], data[:-overflow]
            if data:
                self._file.write(b64decode(data))
        self._buffer = [remainder] if remainder else []

你可以这样使用它:

^{pr2}$

这将以64KB的块(默认值为^{} buffer size)解析输入XML,因此附件数据最多解码为48KB的原始数据块。在

我可能会扩展内容处理程序以获取目标目录,然后查找<t:Name>元素来提取文件名,然后使用该元素将数据提取到找到的每个附件的正确文件名中。您还需要验证您是否确实在处理GetAttachmentResponse文档,并处理错误响应。在

相关问题 更多 >

    热门问题