好 久没有更新博客了吧,主要是最近比较忙,到了新的环境需要学习的东西还挺多的,像我这么好学的人当然就一心扑在学习进步的道路上了。不过既然我都登上来了,那就写写最近碰到的一个case吧。
事情是需要处理一个客户提供的数据源,格式是xml。要说处理xml,在Python的世界里首先还是会先想到lxml吧。不过这次的情况有点特殊,这个xml的大小比较bt,gzip压缩后大小1.9G,未压缩状态11G…… 所以,通常用lxml建立element tree的方法是不行了。这点本人已亲身尝试,本以为服务器16G的内存应该够用,结果从开始时内存就迅速飙升,最后悲剧,本人向System Administrator掩面表示很傻很天真。
好吧,碰到这样的情况,大概也就只能想到像SAX那样的事件解析了,翻了一下lxml的文档,果然有类似的解析方法── etree.iterparse() ,默认是在所有元素闭合的时候会触发,不过可以指定event和tag来减少噪音。
OK,立马操刀上阵:
#! /usr/bin/env python # test.py import sys from lxml import etree counter = 0 for action, elem in etree.iterparse(sys.stdin, tag='Item'): counter += 1 print counter
这段脚本从stdin读取数据,然后当每次发现一个Item元素闭合的时候,累加计数器,最后输出整个文件所有的Item个数。脚本是很简单的,接下来只需要将输入流重定向到这个脚本即可。但不知是不是我这儿的gzcat有问题,反正用 gzcat test.xml.gz | less 这样的命令机器的内存还是会崩掉的,所以最终用了gunzip输出到stdout的方式:
gunzip -c test.xml.gz | test.py
可是很快SA又来找我了,因为内存再次悲剧…… 痛定思痛,突然醒悟,我这和直接建etree有啥区别? 解析出来的元素不还是存在内存里么…… 要处理这么大的xml,看来还是需要手动做一下gc才行,如此便有了如下版本:
#! /usr/bin/env python # test.py import sys from lxml import etree counter = 0 for action, elem in etree.iterparse(sys.stdin, tag='Item'): counter += 1 elem.clear() if elem.getprevious() is not None: del elem.getparent()[0] print counter
这儿有两次gc处理,一次是清空Item元素内部的数据,但此时由于无法删除Item本身,所以交由后面的那段 if 段来处理,在下一个元素事件触发是进行清理。由于我这个xml是一个比较规整的结构,Item的平级元素没有其他类型,所以我这儿只需删除前一个前一个平级元素即可,如果需要解析的xml像我这样指定了tag事件,但是相应tag又有其他类型的平级元素,那么可以将 if 改成 while 来清除那些被忽略的元素。
好了,解释到此结束,再次运行该脚本,脚本欢快了,内存欢快了,我也欢快了,嗯。
最后来个每篇一吐槽,用啥xml嘛,那么多文本数据格式,可读性也只有比xml好嘛。想起上回弄SOAP客户端,看各种库帮助半天,烦得要死,最后还是直接建HTTP连接手动发数据收数据了,把简单的东西复杂化,这也是xml功不可没的一点了吧。


标准的没事找抽型!!
纯文本才是正道
当然了,楼主最后醒悟还是很提倡的
展开回复(1)
确实,11G的东西,我提出来的数据也就100M而已……
用sed抽取数据可以么?
展开回复(1)
sed以行为单位处理,对包含多行值的xml元素没有很好的支持。如果都是单行的话,确实可以用grep+sed来搞定的。
正好需要,赞一个。据说基于树模式的模块解析方法,所需内存是原文件的10倍。