python-爬蟲基礎-lxml.etree(4)-從字符串和文件中解析

Etree 支持從所有重要來源(即字符串、文件、 url (http / ftp)和類似文件的對象)以多種方式解析 XML。 主要的解析函數是 fromstring ()和 parse () ,它們都是以 source 作爲第一個參數來調用的。 默認情況下,它們使用標準的解析器,但是您總是可以將不同的解析器作爲第二個參數傳遞。

(1)The fromstring() function

函數是解析字符串最簡單的方法:

>>> some_xml_data = "<root>data</root>"

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

(2)The XML() function

函數的行爲類似於 fromstring ()函數,但通常用於將 XML 文本直接寫入源代碼中

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

Html 文本還有一個相應的函數 HTML ()。

>>> root = etree.HTML("<p>data</p>")
>>> etree.tostring(root)
b'<html><body><p>data</p></body></html>'

(3)The parse() function

作爲這種類文件對象的示例,下面的代碼使用 BytesIO 類從字符串而不是外部文件進行讀取。 這個類來自 Python 2.6及更高版本中的 io 模塊。 在較早的 Python 版本中,必須使用 StringIO 模塊中的 StringIO 類。 但是,在現實生活中,您顯然應該避免一起執行這些操作,而是使用上面提到的字符串解析函數。

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

>>> tree = etree.parse(some_file_or_file_like_object)

>>> etree.tostring(tree)
b'<root>data</root>'

注意 parse ()返回一個 ElementTree 對象,而不是作爲字符串解析器函數的 Element 對象:

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

這種差異背後的原因是 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("<root>  <a/>   <b>  </b>     </root>", parser)

>>> etree.tostring(root)
b'<root><a/><b>  </b></root>'

注意,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'<root><a/><b/></root>

查看 help (etree.XMLParser)以瞭解可用的解析器選項。

(5)增量解析(Parser objects)

Etree 提供了兩種逐步增量解析的方法。 一種是通過類似於文件的對象,其中它反覆調用 read ()方法。 這最好用於數據從諸如 urllib 這樣的源或任何其他類似文件的對象(可以根據請求提供數據)到達的地方。 注意,在這種情況下,解析器會阻塞並等待,直到數據可用:

>>> class DataSource:
...     data = [ b"<roo", b"t><", 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'<root><a/></root>'

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

>>> parser = etree.XMLParser()

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

>>> root = parser.close()

>>> etree.tostring(root)
b'<root><a/></root>'

在這裏,您可以隨時中斷解析過程,稍後通過調用 feed ()方法繼續解析過程。 如果你想避免阻塞對解析器的調用,比如像 Twisted 這樣的框架,或者當數據緩慢或者大塊地進入時,你想在等待下一個數據塊的同時做其他事情,這就很方便了。

在調用 close ()方法之後(或者當解析器引發異常時) ,您可以通過再次調用解析器的 feed ()方法來重用解析器:

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

(6)事件驅動的解析(Event-driven parsing)

有時候,您只需要從一個文檔中獲得一小部分內容,因此將整個樹解析到內存中、遍歷和刪除它的開銷可能太大。 Etree 通過兩個事件驅動的解析器接口支持這個用例,一個在構建樹(iterparse)時生成解析器事件,另一個根本不構建樹,而是以類似 sax 的方式對目標對象調用反饋方法。

下面是一個簡單的 iterparse ()示例:

>>> some_file_like = BytesIO(b"<root><a>data</a></root>")

>>> 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"<root><a>data</a></root>")

>>> 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"<root><a><b>data</b></a><a><b/></a></root>")

>>> 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'''\
... <root>
...   <a><b>ABC</b><c>abc</c></a>
...   <a><b>MORE DATA</b><c>more data</c></a>
...   <a><b>XYZ</b><c>xyz</c></a>
... </root>''')

>>> 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('<root test="true"/>', 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('<root test="true"/>', parser)
>>> print(parser_target.close_count)
2
>>> events = etree.fromstring('<root test="true"/>', parser)
>>> print(parser_target.close_count)
3
>>> events = etree.fromstring('<root test="true"/>', 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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章