python-爬虫基础-lxml.etree(4)-从字符串和文件中解析

Etree 支持从所有重要来源(即字符串、文件、 url (http / ftp)和类似文件的对象)以多种方式解析 XML。 主要的解析函数是 fromstring ()和 parse () ,它们都是以 source 作为第一个参数来调用的。 默认情况下,它们使用标准的解析器,但是您总是可以将不同的解析器作为第二个参数传递。

(1)The fromstring() function

函数是解析字符串最简单的方法:

>>> some_xml_data = "data"

>>> root = etree.fromstring(some_xml_data)
>>> print(root.tag)
root
>>> etree.tostring(root)
b'data

(2)The XML() function

函数的行为类似于 fromstring ()函数,但通常用于将 XML 文本直接写入源代码中

>>> root = etree.XML("data")
>>> print(root.tag)
root
>>> etree.tostring(root)
b'data'

Html 文本还有一个相应的函数 HTML ()。

>>> root = etree.HTML("

data

") >>> etree.tostring(root) b'

data

'

(3)The parse() function

作为这种类文件对象的示例,下面的代码使用 BytesIO 类从字符串而不是外部文件进行读取。 这个类来自 Python 2.6及更高版本中的 io 模块。 在较早的 Python 版本中,必须使用 StringIO 模块中的 StringIO 类。 但是,在现实生活中,您显然应该避免一起执行这些操作,而是使用上面提到的字符串解析函数。

>>> from io import BytesIO
>>> some_file_or_file_like_object = BytesIO(b"data")

>>> tree = etree.parse(some_file_or_file_like_object)

>>> etree.tostring(tree)
b'data'

注意 parse ()返回一个 ElementTree 对象,而不是作为字符串解析器函数的 Element 对象:

>>> root = tree.getroot()
>>> print(root.tag)
root
>>> etree.tostring(root)
b'data'

这种差异背后的原因是 parse ()从文件返回一个完整的文档,而字符串解析函数通常用于解析 XML 片段。

函数支持以下任何来源:

  • an open file object (make sure to open it in binary mode) 一个打开的文件对象(确保以二进制模式打开它)
  • a file-like object that has a 一个类似文件的对象.read(byte_count) . read (字节计数) method returning a byte string on each call 方法在每次调用时返回一个字节字符串
  • a filename string 文件名字符串
  • an HTTP or FTP URL string 一个 HTTP 或 FTP URL 字符串

注意,传递文件名或 URL 通常比传递打开的文件或类似文件的对象更快。 然而,libxml2中的 HTTP / ftp 客户端相当简单,因此像 HTTP 身份验证这样的事情需要一个专用的 URL 请求库,例如 urllib2或请求。 这些库通常为结果提供一个类似于文件的对象,当响应流输入时可以从中进行分析。

(4)解析器对象(Parser objects)

默认情况下,lxml.etree 使用默认设置的标准解析器。 如果你想配置解析器,你可以创建一个新的实例:

>>> parser = etree.XMLParser(remove_blank_text=True) # lxml.etree only!

这将创建一个解析器,在解析时移除标记之间的空文本,这样可以减小树的大小,并避免在知道纯空格内容对数据没有意义时悬空尾文本。 举个例子:

>>> root = etree.XML("            ", parser)

>>> etree.tostring(root)
b'  '

注意,b 标记中的空白内容没有被删除,因为 leaf 元素中的内容往往是数据内容(即使是空白)。 你可以通过遍历这棵树来轻松地移除它:

>>> for element in root.iter("*"):
...     if element.text is not None and not element.text.strip():
...         element.text = None

>>> etree.tostring(root)
b'

查看 help (etree.XMLParser)以了解可用的解析器选项。

(5)增量解析(Parser objects)

Etree 提供了两种逐步增量解析的方法。 一种是通过类似于文件的对象,其中它反复调用 read ()方法。 这最好用于数据从诸如 urllib 这样的源或任何其他类似文件的对象(可以根据请求提供数据)到达的地方。 注意,在这种情况下,解析器会阻塞并等待,直到数据可用:

>>> class DataSource:
...     data = [ b"<", b"a/", b"><", b"/root>" ]
...     def read(self, requested_size):
...         try:
...             return self.data.pop(0)
...         except IndexError:
...             return b''

>>> tree = etree.parse(DataSource())

>>> etree.tostring(tree)
b''

第二种方法是通过 feed 解析器接口,由 feed (data)和 close ()方法提供:

>>> parser = etree.XMLParser()

>>> parser.feed(">> parser.feed("t><")
>>> parser.feed("a/")
>>> parser.feed("><")
>>> parser.feed("/root>")

>>> root = parser.close()

>>> etree.tostring(root)
b''

在这里,您可以随时中断解析过程,稍后通过调用 feed ()方法继续解析过程。 如果你想避免阻塞对解析器的调用,比如像 Twisted 这样的框架,或者当数据缓慢或者大块地进入时,你想在等待下一个数据块的同时做其他事情,这就很方便了。

在调用 close ()方法之后(或者当解析器引发异常时) ,您可以通过再次调用解析器的 feed ()方法来重用解析器:

>>> parser.feed("")
>>> root = parser.close()
>>> etree.tostring(root)
b''

(6)事件驱动的解析(Event-driven parsing)

有时候,您只需要从一个文档中获得一小部分内容,因此将整个树解析到内存中、遍历和删除它的开销可能太大。 Etree 通过两个事件驱动的解析器接口支持这个用例,一个在构建树(iterparse)时生成解析器事件,另一个根本不构建树,而是以类似 sax 的方式对目标对象调用反馈方法。

下面是一个简单的 iterparse ()示例:

>>> some_file_like = BytesIO(b"data")

>>> for event, element in etree.iterparse(some_file_like):
...     print("%s, %4s, %s" % (event, element.tag, element.text))
end,    a, data
end, root, None

默认情况下,iterparse ()只在解析一个元素时生成事件,但是你可以通过 events 关键字参数来控制:

>>> some_file_like = BytesIO(b"data")

>>> for event, element in etree.iterparse(some_file_like,
...                                       events=("start", "end")):
...     print("%5s, %4s, %s" % (event, element.tag, element.text))
start, root, None
start,    a, data
  end,    a, data
  end, root, None

请注意,接收开始事件时,Element 的文本、尾部和子元素不一定存在。 只有结束事件才能保证元素被完全解析。

它还允许你。 清除()或修改元素的内容以节省内存。 因此,如果您解析一个大的树并希望保持内存使用量较小,那么您应该清理树中不再需要的部分。 保持尾部为真的参数。 Clear ()确保当前元素后面的(tail)文本内容不会被修改。 对于解析器可能还没有完全读完的任何内容,最好不要修改。

>>> some_file_like = BytesIO(
...     b"data")

>>> for event, element in etree.iterparse(some_file_like):
...     if element.tag == 'b':
...         print(element.text)
...     elif element.tag == 'a':
...         print("** cleaning up the subtree")
...         element.clear(keep_tail=True)
data
** cleaning up the subtree
None
** cleaning up the subtree

Iterparse ()的一个非常重要的用例是解析生成的大型 XML 文件,例如数据库转储。 通常,这些 XML 格式只有一个主数据项元素,该元素直接挂在根节点的下方,并且要重复数千次。 在这种情况下,最好的做法是让 lxml.etree 构建树,并且只截取这一个 Element,使用普通的树 API 进行数据提取。

>>> xml_file = BytesIO(b'''\
... 
...   ABCabc
...   MORE DATAmore data
...   XYZxyz
... ''')

>>> for _, element in etree.iterparse(xml_file, tag='a'):
...     print('%s -- %s' % (element.findtext('b'), element[1].text))
...     element.clear(keep_tail=True)
ABC -- abc
MORE DATA -- more data
XYZ -- xyz

如果出于某种原因,根本不需要构建树,那么可以使用 lxml.etree 的目标解析器接口。 它通过调用目标对象的方法来创建类似 sax 的事件。 通过实现部分或全部这些方法,您可以控制生成哪些事件:

>>> class ParserTarget:
...     events = []
...     close_count = 0
...     def start(self, tag, attrib):
...         self.events.append(("start", tag, attrib))
...     def close(self):
...         events, self.events = self.events, []
...         self.close_count += 1
...         return events

>>> parser_target = ParserTarget()

>>> parser = etree.XMLParser(target=parser_target)
>>> events = etree.fromstring('', parser)

>>> print(parser_target.close_count)
1

>>> for event in events:
...     print('event: %s - tag: %s' % (event[0], event[1]))
...     for attr, value in event[2].items():
...         print(' * %s = %s' % (attr, value))
event: start - tag: root
 * test = true

您可以随心所欲地重用解析器及其目标,因此应该注意。 Close ()方法实际上将目标重置为可用的状态(在出错的情况下也是如此) .

>>> events = etree.fromstring('', parser)
>>> print(parser_target.close_count)
2
>>> events = etree.fromstring('', parser)
>>> print(parser_target.close_count)
3
>>> events = etree.fromstring('', parser)
>>> print(parser_target.close_count)
4

>>> for event in events:
...     print('event: %s - tag: %s' % (event[0], event[1]))
...     for attr, value in event[2].items():
...         print(' * %s = %s' % (attr, value))
event: start - tag: root
 * test = true

 

你可能感兴趣的:(Python)