簡體   English   中英

解析 XML:在沒有循環的情況下查找元素子樹

[英]Parsing XML: find element sub-tree without a loop

我正在使用ElementTree解析 XML 負載。 我無法共享確切的代碼或文件,因為它共享敏感信息。 我能夠通過迭代一個元素(如 ElementTree 文檔中所見)並將輸出附加到列表來成功提取我需要的信息。 例如:

list_col_name = []
list_col_value = []

for col in root.iter('my_table'):
    # get col name
    col_name = col.find('col_name').text
    list_col_name.append(col_name
    # get col value
    col_value = col.find('col_value').text
    list_col_value.append(col_value)

我現在可以將這些放入字典中,然后繼續剩下的工作:

dict_ = dict(zip(list_col_name, list_col_value))

但是,我需要盡快發生這種情況,並且想知道是否有一種方法可以立即提取list_col_name (即,使用findall()或類似方法)。 如果可能的話,只是想知道如何提高 xml 解析的速度。 感謝所有答案/建議。 先感謝您。

我的建議是使用基於iterparse方法的源文件的“增量”解析。 原因是你實際上:

  • 不需要任何完整解析的 XML 樹,
  • 在增量解析過程中,您可以丟棄已處理的元素,因此對內存的需求也較小。

另一個提示是使用lxml庫,而不是ElementTree 原因是雖然兩個庫中存在iterparse方法,但lxml版本有額外的tag參數,所以你可以“限制”循環只處理感興趣的標簽。

作為我使用的源文件(類似):

<root>
  <my_table id="t1">
    <col_name>N1</col_name>
    <col_value>V1</col_value>
    <some_other_stuff>xx1</some_other_stuff>
  </my_table>
  <my_table id="t2">
    <col_name>N2</col_name>
    <col_value>V2</col_value>
    <some_other_stuff>xx1</some_other_stuff>
  </my_table>
  <my_table id="t3">
    <col_name>N3</col_name>
    <col_value>V3</col_value>
    <some_other_stuff>xx1</some_other_stuff>
  </my_table>
</root>

實際上,我的源文件:

  • 包括9 個my_table元素(不是3 個),
  • some_other_stuff重復 8 次(在每個my_table ),以模擬每個my_table包含的其他元素。

我使用%timeit進行了 3 次測試:

  1. 您的循環,預先解析源 XML 文件:

     from lxml import etree as et def fn1(): root = et.parse('Tables.xml') list_col_name = [] list_col_value = [] for col in root.iter('my_table'): col_name = col.find('col_name').text list_col_name.append(col_name) col_value = col.find('col_value').text list_col_value.append(col_value) return dict(zip(list_col_name, list_col_value))

    執行時間為 1.74 毫秒。

  2. 我的循環,基於iterparse ,只處理“必需”元素:

     def fn2(): key = '' dict_ = {} context = et.iterparse('Tables.xml', tag=['my_table', 'col_name', 'col_value']) for action, elem in context: tag = elem.tag txt = elem.text if tag == 'col_name': key = txt elif tag == 'col_value': dict_[key] = txt elif tag == 'my_table': elem.clear() elem.getparent().remove(elem) return dict_

    我假設在每個my_table元素中col_name出現col_value之前,並且每個my_table只包含一個名為col_namecol_value 的元素

    另請注意,上述函數清除每個my_table元素並將其從解析的 XML 樹中刪除( getparent函數僅在lxml版本中可用)。

    另一個改進是我“直接”將每個/對添加到此函數返回的字典中,因此不需要zip

    執行時間為 1.33 毫秒。 不是很快,但至少可以看到一些時間增益。

  3. 您還可以讀取所有col_namecol_value元素,調用findall然后調用zip

     def fn3(): root = et.parse('Tables.xml') list_col_name = [] for elem in root.findall('.//col_name'): list_col_name.append(elem.text) list_col_value = [] for elem in root.findall('.//col_value'): list_col_value.append(elem.text) return dict(zip(list_col_name, list_col_value))

    執行時間為 1.38 毫秒。 也比您的原始解決方案更快,但與我的第一個解決方案( fn2 )沒有顯着差異。

當然,最終結果很大程度上取決於:

  • 輸入文件的大小,
  • 每個my_table元素包含多少“其他東西”。

考慮使用findall進行列表理解以避免列表初始化/追加和顯式for循環,這可能會略微提高性能

# FINDALL LIST COMPREHENSION
list_col_name = [e.text for e in root.findall('./my_table/col_name')]
list_col_value = [e.text for e in root.findall('./my_table/col_value')]

dict(zip(list_col_name, list_col_value))

或者,使用完全支持 XPath 1.0 的lxml (第三方庫),考慮xpath()可以將解析輸出直接分配給列表,同時避免初始化/追加和for循環:

import lxml.etree as et
...

# XPATH LISTS
list_col_name = root.xpath('my_table/col_name/text()')
list_col_value = root.xpath('my_table/col_value/text()')

dict(zip(list_col_name, list_col_value))

不知道有沒有你想要的

from simplified_scrapy import SimplifiedDoc
html = '''
<?xml version="1.0"?>
<data>
    <country name="Liechtenstein">
        <rank>1</rank>
        <year>2008</year>
        <gdppc>141100</gdppc>
        <neighbor name="Austria" direction="E"/>
        <neighbor name="Switzerland" direction="W"/>
    </country>
    <country name="Singapore">
        <rank>4</rank>
        <year>2011</year>
        <gdppc>59900</gdppc>
        <neighbor name="Malaysia" direction="N"/>
    </country>
    <country name="Panama">
        <rank>68</rank>
        <year>2011</year>
        <gdppc>13600</gdppc>
        <neighbor name="Costa Rica" direction="W"/>
        <neighbor name="Colombia" direction="E"/>
    </country>
</data>
'''
doc = SimplifiedDoc(html)
ranks = doc.selects('country>(rank>text())')
print (ranks)
ranks = doc.selects('country>rank()')
print (ranks)
ranks = doc.selects('country>children()')
print (ranks)

結果:

['1', '4', '68']
[{'tag': 'rank', 'html': '1'}, {'tag': 'rank', 'html': '4'}, {'tag': 'rank', 'html': '68'}]
[[{'tag': 'rank', 'html': '1'}, {'tag': 'year', 'html': '2008'}, {'tag': 'gdppc', 'html': '141100'}, {'name': 'Austria', 'direction': 'E', 'tag': 'neighbor'}, {'name': 'Switzerland', 'direction': 'W', 'tag': 'neighbor'}], [{'tag': 'rank', 'html': '4'}, {'tag': 'year', 'html': '2011'}, {'tag': 'gdppc', 'html': '59900'}, {'name': 'Malaysia', 'direction': 'N', 'tag': 'neighbor'}], [{'tag': 'rank', 'html': '68'}, {'tag': 'year', 'html': '2011'}, {'tag': 'gdppc', 'html': '13600'}, {'name': 'Costa Rica', 'direction': 'W', 'tag': 'neighbor'}, {'name': 'Colombia', 'direction': 'E', 'tag': 'neighbor'}]]

這里有更多例子: https : //github.com/yiyedata/simplified-scrapy-demo/tree/master/doc_examples

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM