开发者社区> 问答> 正文

增量式解析大型XML文件

你想使用尽可能少的内存从一个超大的XML文档中提取数据。

展开
收起
哦哦喔 2020-04-17 13:17:34 812 0
1 条回答
写回答
取消 提交回答
  • 任何时候只要你遇到增量式的数据处理时,第一时间就应该想到迭代器和生成器。 下面是一个很简单的函数,只使用很少的内存就能增量式的处理一个大型XML文件:
    
    from xml.etree.ElementTree import iterparse
    
    def parse_and_remove(filename, path):
        path_parts = path.split('/')
        doc = iterparse(filename, ('start', 'end'))
        # Skip the root element
        next(doc)
    
        tag_stack = []
        elem_stack = []
        for event, elem in doc:
            if event == 'start':
                tag_stack.append(elem.tag)
                elem_stack.append(elem)
            elif event == 'end':
                if tag_stack == path_parts:
                    yield elem
                    elem_stack[-2].remove(elem)
                try:
                    tag_stack.pop()
                    elem_stack.pop()
                except IndexError:
                    pass
    为了测试这个函数,你需要先有一个大型的XML文件。 通常你可以在政府网站或公共数据网站上找到这样的文件。 例如,你可以下载XML格式的芝加哥城市道路坑洼数据库。 在写这本书的时候,下载文件已经包含超过100,000行数据,编码格式类似于下面这样:
    
    <response>
        <row>
            <row ...>
                <creation_date>2012-11-18T00:00:00</creation_date>
                <status>Completed</status>
                <completion_date>2012-11-18T00:00:00</completion_date>
                <service_request_number>12-01906549</service_request_number>
                <type_of_service_request>Pot Hole in Street</type_of_service_request>
                <current_activity>Final Outcome</current_activity>
                <most_recent_action>CDOT Street Cut ... Outcome</most_recent_action>
                <street_address>4714 S TALMAN AVE</street_address>
                <zip>60632</zip>
                <x_coordinate>1159494.68618856</x_coordinate>
                <y_coordinate>1873313.83503384</y_coordinate>
                <ward>14</ward>
                <police_district>9</police_district>
                <community_area>58</community_area>
                <latitude>41.808090232127896</latitude>
                <longitude>-87.69053684711305</longitude>
                <location latitude="41.808090232127896"
                longitude="-87.69053684711305" />
            </row>
            <row ...>
                <creation_date>2012-11-18T00:00:00</creation_date>
                <status>Completed</status>
                <completion_date>2012-11-18T00:00:00</completion_date>
                <service_request_number>12-01906695</service_request_number>
                <type_of_service_request>Pot Hole in Street</type_of_service_request>
                <current_activity>Final Outcome</current_activity>
                <most_recent_action>CDOT Street Cut ... Outcome</most_recent_action>
                <street_address>3510 W NORTH AVE</street_address>
                <zip>60647</zip>
                <x_coordinate>1152732.14127696</x_coordinate>
                <y_coordinate>1910409.38979075</y_coordinate>
                <ward>26</ward>
                <police_district>14</police_district>
                <community_area>23</community_area>
                <latitude>41.91002084292946</latitude>
                <longitude>-87.71435952353961</longitude>
                <location latitude="41.91002084292946"
                longitude="-87.71435952353961" />
            </row>
        </row>
    </response>
    假设你想写一个脚本来按照坑洼报告数量排列邮编号码。你可以像这样做:
    
    from xml.etree.ElementTree import parse
    from collections import Counter
    
    potholes_by_zip = Counter()
    
    doc = parse('potholes.xml')
    for pothole in doc.iterfind('row/row'):
        potholes_by_zip[pothole.findtext('zip')] += 1
    for zipcode, num in potholes_by_zip.most_common():
        print(zipcode, num)
    这个脚本唯一的问题是它会先将整个XML文件加载到内存中然后解析。 在我的机器上,为了运行这个程序需要用到450MB左右的内存空间。 如果使用如下代码,程序只需要修改一点点:
    
    from collections import Counter
    
    potholes_by_zip = Counter()
    
    data = parse_and_remove('potholes.xml', 'row/row')
    for pothole in data:
        potholes_by_zip[pothole.findtext('zip')] += 1
    for zipcode, num in potholes_by_zip.most_common():
        print(zipcode, num)
    结果是:这个版本的代码运行时只需要7MB的内存–大大节约了内存资源。
    
    2020-04-17 13:17:46
    赞同 展开评论 打赏
问答排行榜
最热
最新

相关电子书

更多
神龙云服务器产品及技术深度解析 立即下载
弹性创造价值:基于ECS的最佳性价比实践解析 立即下载
又快又稳:阿里云下一代虚拟交换机解析 立即下载

相关镜像