【问题记录】解析大型XML(爆内存解决过程记录)

硬件条件:内存8G,可用内存7.76G,四核

解析目标:每个文件均为.xml.gz

尝试调库解析XML

从头到尾尝试过了使用pandas.read_xml和lxml进行解析。这两个库都很方便,并且可以直接对于.xml.gz进行解析(还挺高级的),但是在遇到大文件的时候,程序会自动停止。

import pandas as pd
import os

file_name1 = 'smallfile.xml.gz'
file_name2 = 'bigfile.xml.gz'

#short file
print(os.path.getsize(file_name2)/1024/1024,'MB')
test1 = pd.read_xml(file_name2)
print(file_name2,'done!')
#long file 
print(os.path.getsize(file_name1)/1024/1024,'MB')
test2 = pd.read_xml(file_name1)
print(file_name1,'done!')

#返回结果
0.09057044982910156 MB
smallfile.xml.gz done!
87.88075542449951 MB
#xml解析
import pandas as pd
import os

file_name1 = 'smallfile.xml.gz'
file_name2 = 'bigfile.xml.gz'

#short file
print(os.path.getsize(file_name2)/1024/1024,'MB')
test1 = etree.parse(file_name2)
print(file_name2,'done!')
#long file 
print(os.path.getsize(file_name1)/1024/1024,'MB')
test2 = etree.parse(file_name1)
print(file_name1,'done!')

#返回结果
0.09057044982910156 MB
smallfile.xml.gz done!
87.88075542449951 MB

直接调库应该是不行了,所以得重新看看数据

解压后处理

首先是先把XML能解析了再说,这个时候意识到是压缩文件了,所以就试试解压之后处理行不行

import os
import gzip

file_name2 = '/home/laimingshu/airflow/dataset/AIndexEODPrices_backup/706804130#3#AIndexEODPrices_D_20240619_181751000247.xml.gz'

#long file 
print(os.path.getsize(file_name1)/1024/1024,'MB')
test2 = gzip.GzipFile(file_name1).read().decode('utf-8')
print(file_name1,'done!')
with open('test.txt','w') as f:
    f.write(test2)

print(os.path.getsize('test.txt'))
print(file_name1,'done!')

#返回结果
87.88075542449951 MB
bigfile.xml.gz done!
777.7352848052979 MB
import os
import lxml
import pandas as pd
import numpy as np
from lxml import etree
from pprint import pp
import gzip
import re

class GetXmlData:
    def __init__(self,file_root,columns):
        self.result_dict = {}
        self.file_root = file_root
        self.file_list = os.listdir(file_root)
        self.tmp_dict = dict()
        self.analyze_result = []
        for col in columns:
            self.tmp_dict[col] = ''

        for file in self.file_list:
            # method1
            # tmp_content = self._analyze(os.path.join(self.file_root,file))
            # tmp_content = re.findall('<Product[^>]*>[\w\W]*<\/Product>',tmp_content)
            # if len(tmp_content)>0:
            #     tmp_content = tmp_content[0].split('</Product>')
            #     self.analyze_result.append(tmp_content)

            # method2
            tmp_content = self._analyze(os.path.join(self.file_root,file))
            tmp_content = tmp_content.split('\n')[57::]
            start,end,length = 0,0,len(tmp_content)
            for tmp in tmp_content:
                if '<Product>' in tmp:start==end
                elif '</Product>' in tmp:
                    self.analyze_result.append(tmp_content[start+1:end])
                else: end += 1
            print(file)
        
    def _analyze(self,file):
        return gzip.GzipFile(filename=file,mode='r').read().decode('utf-8')

if __name__ == '__main__':
    file_path = "bigfile.xml.gz"
    test_xml = GetXmlData('folder_root',answer_columns)
    
#return result

在这里插入图片描述

压缩率真高(此时只是中等大小的数据文件)
用htop 命令看一下内存占用情况,看来还是会内存爆掉,最多就是能解析出来xml了,所以找了外援问一下怎么解决

流式加载

问了外援之后,继续解决。

内存不够用的话,解决方式有两种,一种是分块加载,一种是流式加载

目标文件的结构大致分成三个部分:文件信息、内容信息和数据内容,前两个部分的固定行数的头文件,第三部分是每个块以及总长度都不固定的数据内容。

而分块加载,需要每次加载固定长度的内容,流式加载可以进行每次加载的量的控制。

所以对于这份数据,每个块长不固定的情况而言,流式加载更加合适。

import pandas as pd
import gzip
from pathlib import Path

class GetXmlData:
    def __init__(self,file_root,columns=''):
        self.result_dict = {}
        self.file_root = file_root
        self.file_list = Path(file_root).glob("*.gz")
        self.tmp_dict = dict()
        self.analyze_result = []
        for col in columns:
            self.tmp_dict[col] = ''
        self.__debug_freq = 0

    def do(self):
        for file in self.file_list:
            gzip_file = gzip.GzipFile(filename=file,mode='r')
            line_number = -1
            gzip_line_swap = []
            gzip_swap_enable_flag = False
            gzip_convert_hook_flag = False
            while True:
                gzip_line = gzip_file.readline().decode()
                line_number += 1
                if not gzip_line:
                    break
                if line_number < 57:
                    continue
                if gzip_line.strip() == "<Product>":
                    gzip_swap_enable_flag = True
                    continue
                if gzip_line.strip() == "</Product>":
                    gzip_swap_enable_flag = False
                    gzip_convert_hook_flag = True
                if gzip_swap_enable_flag:
                    gzip_line_swap.append(gzip_line.strip())
                if gzip_convert_hook_flag:
                    self._convert_hook(gzip_line_swap)
                    gzip_convert_hook_flag = False
                    gzip_line_swap = []

    def  _convert_hook(self, gzip_line_swap: list):
        if self.__debug_freq < 10:
            print(gzip_line_swap)
            self.__debug_freq += 1
        else:
            pass
        

if __name__ == '__main__':
		folder_root = ''
    file_path = "bigfile.xml.gz"
    test_xml = GetXmlData(folder_root,answer_columns)
    test_xml.do()

在这里插入图片描述

外援指导还是不一样,哎~最高的内存消耗都没有上过4G,基本都是稳定在3G左右

后续就是我自己处理数据了。

多线程

    sing_process = multiprocessing.Process(target=test_xml.do(),group=None) 
    sing_process.start()

函数执行的时候加一个多线程会更快
同一个文件,单线程处理需要六分钟+,多线程会降低到两分半,所以还是很有效的。

  • 7
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值