IBM®
메인 컨텐츠로 가기
    Korea [국가변경]    이용약관
 
 
   
        제품    서비스 & 솔루션    고객지원 & 다운로드    회원 서비스    
메인 컨텐츠로 가기

한국 developerWorks  >  XML | 오픈 소스  >

파이썬에서 lxml로 XML 구문분석 성능 높이기

lxml이 제공하는 XML 구문분석/직렬화 기능을 최대한 활용하자

developerWorks
문서 옵션

JavaScript가 필요한 문서 옵션은 디스플레이되지 않습니다.

영어원문

영어원문


제안 및 의견
피드백

난이도 : 중급

Liza Daly, software engineer and owner, Threepress Consulting Inc.

옮긴이 : 박재호 이해영 dwkorea@kr.ibm.com

원문 게재일 : 2008 년 10 월 28 일
번역 게재일 : 2009 년 1 월 06 일

lxml은 XML을 빠르고 유연하게 처리하는 파이썬 라이브러리입니다. XPath(XML Path Language)와 XSLT(Extensible Stylesheet Language Transformation)를 지원하며, 많이 쓰이는 ElementTree API를 구현합니다. 이 기사에서는 아주 큰 XML 자료를 처리할 때 lxml이 제공하는 사용 편의성과 성능에 초점을 맞춥니다.
자주 쓰이는 약어
  • API: application programming interface
  • DOM: Document Object Model
  • HTML: Hypertext Markup Language
  • SAX: Simple API for XML
  • XML: Extensible Markup Language
  • XPath: XML Path Language
  • XSLT: Extensible Stylesheet Language Transformations

lxml 소개

파이썬이 제공하는 XML 라이브러리는 아주 많다. 버전 2.0부터 xml.dom.minidom 모듈, pulldom 모듈, SAX(Simple API for XML) 모듈을 제공했다. 버전 2.4부터는 인기 있는 ElementTree API도 지원한다. 게다가 파이썬스러운 고차원 인터페이스를 제공하는 타사 라이브러리도 많다.

간단한 XML 파일이라면 DOM을 사용하든 SAX를 사용하든 상관이 없다. 하지만 점차로 웹 서비스에서 실시간으로 XML을 분석하거나 대규모 XML 정보를 분석할 필요가 생겨났다. 또한 숙련된 XML 개발자는 크기가 작고 표현이 자유롭다는 이유로 XPath나 XSLT 같은 고유 XML 언어를 선호한다. 그러므로 파이썬이 제공하는 범용 기능을 그대로 유지하면서 XPath가 제공하는 선언적 문법을 지원한다면 이상적일 것이다.

이 기사에서 사용한 소프트웨어 버전과 예제 자료
  • lxml 2.1.2
  • libxml2 2.7.1
  • libxslt 1.1.24
  • cElementTree 1.0.5
  • 구글이 제공하는 미국 저작권 갱신법 자료
  • 오픈 디렉터리 RDF(Resource Description Framework) 내용

각 소프트웨어와 자료는 참고자료 절에서 링크를 제공한다.

나는 펜티엄 M 1.86GHz, 씽크패드 T43, 2GM RAM, 우분투에서 파이썬 timeit 명령을 사용하여 벤치마크를 수행했다. 이 기사에서 제공하는 자료는 각 방법을 비교할 목적으로만 참조하기 바란다. 일반적인 소프트웨어 벤치마킹 자료로 여겨서는 안 된다는 사실을 강조한다.

lxml은 1) 고성능이고, 2) XPath 1.0, XSLT 1.0, 사용자 정의 엘리먼트 클래스를 지원하며, 3) 심지어 파이썬스러운 자료 바인딩 인터페이스까지 지원하는 최초의 파이썬 라이브러리다. libxml2libxslt라는 C 라이브러리를 기반으로 구현했으며, 이 두 가지 라이브러리가 구문분석과 직렬화와 변환을 수행하는 주요 엔진이다.

lxml에서 어떤 기능을 사용할지는 개발자에게 달렸다. XPath를 능숙하게 사용하는가? 파이썬스러운 객체를 사용하고 싶은가? 대형 트리를 저장할 시스템에 메모리가 충분한가?

이 기사는 lxml 전부를 다루지 않는다. 매우 큰 XML 파일을 효율적으로 처리하는 기술, 즉 속력을 높이고 메모리 사용량을 줄이는 방법에 초점을 맞춘다. 예제로는 파일 두 개를 사용한다. 하나는 U.S. 저작권 갱신법 자료로 구글이 XML로 변환했다. 다른 하나는 오픈 디렉터리 RDF 파일이다.

여기서는 lxml을 cElementTree와만 비교한다. 다른 파이썬 라이브러리와 비교하지 않는다. cElementTree를 선택한 이유는 cElementTree가 파이썬 2.5에서 기본으로 제공되며 (lxml과 마찬가지로) C 라이브러리에 기반을 두기 때문이다.

아주 큰 자료가 무슨 문제일까?

흔히 XML 라이브러리는 간단한 예제 파일을 대상으로 설계하고 테스트한다. 사실 대다수 실생활 프로젝트가 실생활 자료를 확보하지 못한 상태에서 시작된다. 프로그래머는 몇 주 또는 몇 달 동안 예제 자료를 사용하여 Listing 1과 같은 코드를 구현한다.

cElementTree

간단한 구문분석 기능만 필요하다면 cElementTree 모듈을 권장한다. cElementTree 모듈은 파이썬 2.5에서 기본으로 제공된다. cElementTree가 사용하는 C 모듈 expat는 문서 전체를 구문분석하는 기능이 다른 어떤 라이브러리보다 뛰어나다. 그러나 API는 ElementTree보다 제한적이며, 대다수 기능이 (특히 직렬화가) lxml보다 느리다.


Listing 1. 간단한 구문분석

from lxml import etree
doc = etree.parse('content-sample.xml')

lxml parse 메서드는 문서 전체를 읽어 메모리에 트리를 만든다. 그런데 lxml 트리는 cElementTree보다 훨씬 크다. (부모 노드를 가리키는 정보 등) 각 노드가 더 많은 정보를 포함하기 때문이다. 크기가 2G인 문서를 Listing 1처럼 분석하면 시스템이 2G 메모리를 당장 스왑하기 시작한다. 따라서 시스템 성능이 크게 떨어진다. 트리 전체가 메모리에 상주한다는 가정 아래 응용 프로그램을 작성했다면 심각한 리팩터링이 필요해진다.

반복적인 구문분석

메모리에 트리 전체를 저장할 필요가 없다거나 이 방법이 실용적이지 못하다면, 파일 전체를 한꺼번에 읽어들이지 않는 구문분석 기법을 사용한다. 이를 반복적인 구문분석(iterative parsing)이라고 부른다. lxml이 제공하는 방법은 두 가지다.

  • 목적형 구문분석기 구현하기
  • iterparse 함수 사용하기

목적형 구문분석기 구현하기

SAX 이벤트 구동 코드에 익숙한 개발자라면 목적형 구문분석기 방법을 쉽게 이해하리라. 목적형 구문분석기는 다음 함수를 제공하는 클래스다.

  1. start 엘리먼트를 열 때 호출된다. 엘리먼트 내용과 자식은 아직 모른다.
  2. end 엘리먼트를 닫을 때 호출된다. 텍스트 노드를 포함하여 엘리먼트 자식 노드를 모두 안다.
  3. data 텍스트 자식에서 호출된다. 텍스트를 안다.
  4. close 구문분석을 완료했을 때 호출된다.

Listing 2는 필요한 메서드를 예제로 구현한 목적형 구문분석기 클래스 TitleTarget이다. 이 구문분석기는 Title 엘리먼트에서 텍스트 자식을 추출하여 내부 리스트 self.text에 저장한다. 그런 다음, close() 함수에서 최종 목록을 반환한다.


Listing 2. Title 태그의 모든 텍스트 자식 목록을 반환하는 목적형 구문분석기
        
class TitleTarget(object):
    def __init__(self):
        self.text = []
    def start(self, tag, attrib):
        self.is_title = True if tag == 'Title' else False
    def end(self, tag):
        pass
    def data(self, data):
        if self.is_title:
            self.text.append(data.encode('utf-8'))
    def close(self):
        return self.text

parser = etree.XMLParser(target = TitleTarget())

# 구글 저작권 자료를 읽는다.
infile = 'copyright.xml'

results = etree.parse(infile, parser)

# 반복이 끝나면 'results'가 parser의 close() 메서드가 반환한 결과값을 포함할 것이다.

out = open('titles.txt', 'w')
out.write('\n'.join(results))
out.close()

이 코드는 저작권 자료를 돌리는 과정에서 54초를 기록했다. 목적형 구문분석은 합리적인 수준에서 빠르며, 메모리를 소비하는 구문분석 트리를 생성하지 않는다. 하지만 자료에 속한 모든 엘리먼트에 대한 이벤트가 발생한다. 이번 예제처럼 아주 큰 문서에서 흥미있는 엘리먼트가 몇 개뿐이라면 그다지 바람직하지 않다. 선택된 태그에 대해서만 처리를 제한해 좀 더 나은 성능을 얻는 방법은 없을까?

iterparse 함수 사용하기

lxml이 제공하는 iterparse 함수는 ElementTree API의 확장 기능이다. iterparse는 선택한 엘리먼트 정보를 포함하는 파이썬 반복기(iterator)를 반환한다. 함수는 유용한 인수 두 개를 쌍으로 받는데, 하나는 감시할 이벤트고 다른 하나는 태그 이름이다. 우리 예제에서는 (end 이벤트에 도달하면 사용 가능한) <Title> 엘리먼트에 든 텍스트 자식만 추출하므로 코드는 Listing 3과 같다. Listing 3Listing 2와 똑같은 결과를 반환한다. 하지만 lxml이 내부적으로 이벤트 처리를 최적화하므로 Listing 2보다 훨씬 빠르다. 코드도 훨씬 간단하다.


Listing 3. 특정 이벤트에서 태그를 반복적으로 참조하기

context = etree.iterparse(infile, events=('end,'), tag='Title')

for event, elem in context:
       out.write('%s\n' % elem.text.encode('utf-8'))

위 코드를 실행하여 결과를 기다리면 처음에는 출력이 즉각적이지만 점점 느려지기 시작한다. top 명령을 돌려보면 컴퓨터가 스왑 모드로 돌입했다는 사실이 드러난다.

PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                
170 root      15  -5     0    0    0 D  3.9  0.0   0:01.32 kswapd0

무슨 영문일까? 처음에 iterparse는 파일 전체를 읽어들이지는 않는다. 하지만 루프를 돌면서 이미 참조한 노드를 해제하지 않는다. 나중에 문서 전체를 반복적으로 참조할 작정이라면 유용한 기능이다. 하지만 여기서는 이미 참조한 메모리를 해제하는 편이 낫다. Listing 4는 현재 노드의 자식 노드나 텍스트 노드 그리고 현재 노드의 이전 형제 노드를 모두 해제한다. (참고로, 현재 노드의 형제 노드 정보는 루트 노드에 저장된다.)


Listing 4. 반복 과정에서 불필요한 노드 참조 제거하기
for event, elem in context:
    out.write('%s\n' % elem.text.encode('utf-8'))

    # 어떤 자식에게도 접근이 가능하지 않기에 여기서 안전하게 clear()를 호출한다.
    elem.clear()

    # 또한 루트 노드에서 <Title>로 이제 비어있는 참조를 제거한다.
    while elem.getprevious() is not None:
        del elem.getparent()[0]

편의상 Listing 4 코드를 함수로 정의했다. 이 함수는 현재 노드에 수행할 함수 func를 인수로 받는다. 코드는 Listing 5를 참조한다. 이후 예제에서 Listing 5 함수를 사용한다.


Listing 5. 태그에서 루프를 돌면서 func를 호출한 후 불필요한 노드를 제거하는 함수
def fast_iter(context, func):
    for event, elem in context:
        func(elem)
        elem.clear()
        while elem.getprevious() is not None:
            del elem.getparent()[0]
    del context

성능 비교

Listing 4에서 구현한 iterparseListing 2에서 구현한 목적형 구문분석기와 똑같은 결과를 내놓는다. 하지만 구문분석에 걸리는 시간은 Listing 4가 두 배나 빠르다. 이벤트와 태그 이름을 구체적인 값으로 제한하면 Listing 4는 cElementTree보다도 빨라진다. (하지만 일반적으로 구문분석을 수행할 때는 cElementTree가 lxml보다 훨씬 더 빠르다.)

표 1은 각 구문분석 기법에 걸리는 시간을 비교한 결과다. 앞서 언급한 컴퓨터 사양에서 저작권 자료를 분석했다.


표 1. 반복적인 구문분석 기법 비교: <Title> 태그에서 text() 추출하기
XML 라이브러리방법 구문분석에 걸린 평균 시간(초)
cElementTreeIterparse32
lxml목적형 구문분석기54
lxml최적화된 iterparse25

확장 가능한가?

Listing 4 코드(iterparse)로 오픈 디렉터리 RDF를 분석하면 평균 122초가 걸린다. 위 결과보다 대략 다섯 배 정도가 더 걸린다. 오픈 디렉터리 파일 크기(1.9GB)가 저작권 자료보다 다섯 배 정도 크다는 사실을 감안하건대 성능은 대략적으로 파일 크기에 비례한다고 보아도 되겠다. 파일이 아주 커져도 마찬가지다.




위로


직렬화

XML 파일에서 노드 하나만 찾아서 텍스트 정보를 추출할 목적이라면 간단한 정규표현식(regular expression)이 XML 구문분석기보다 빠를지도 모른다. 하지만 파일이 아주 복잡하다면 정규표현식을 정확하게 작성하기가 거의 불가능해지므로 이런 방식은 추천하지도 않는다. 따라서 진짜 복잡한 자료를 조작해야 한다면 XML 라이브러리가 가장 낫다.

lxml은 XML을 문자열이나 파일로 직렬화하는 기능이 아주 뛰어나다. libxml2라는 C 라이브러리를 사용하기 때문이다. 프로그램에서 직렬화가 필요하다면 당연히 lxml을 권장한다. 하지만 성능을 최대한 높이려면 몇 가지 기교가 필요하다.

하위 트리를 직렬화하려면 deepcopy를 사용하라

lxml은 자식 노드와 부모 노드 사이 관계를 저장한다. 그래서 lxml에서는 자식 노드에 부모 노드는 단 하나만 존재한다(cElementTree는 부모 노드라는 개념이 없다).

Listing 6은 저작권 파일을 분석하면서 각 <Record> 노드를 발견할 때마다 제목과 저작권 정보만 포함하는 간단한 레코드를 출력한다.


Listing 6. <Record> 엘리먼트의 자식 직렬화하기
from lxml import etree
import deepcopy 

def serialize(elem):
    # 다음과 같은 새로운 트리를 출력한다.
    # <SimplerRecord>
    #   <Title>This title</Title>
    #   <Copyright><Date>date</Date><Id>id</Id></Copyright>
    # </SimplerRecord>
    
    # 새로운 루트 노드를 생성한다.
    r = etree.Element('SimplerRecord')

    # 새로운 자식을 생성한다.
    t = etree.SubElement(r, 'Title')

    # 자식 텍스트 속성을 <Title>의 원본 텍스트 내용으로 설정한다.
    t.text = elem.iterchildren(tag='Title').next().text

    # 자식 트리를 deepcopy한다.
    for c in elem.iterchildren(tag='Copyright'):
        r.append( deepcopy(c) )
    return r

out = open('titles.xml', 'w')
context = etree.iterparse('copyright.xml', events=('end',), tag='Record')

# 빠른 반복 메서드를 사용해 개별 <Record> 노드를 순회한다.
fast_iter(context, 
          # 각 <Record>마다 단순화된 버전을 직렬화해 출력 파일에 기록한다.
          lambda elem: 
              out.write(
                 etree.tostring(serialize(elem), encoding='utf-8')))

단순히 노드 하나에서 텍스트를 복제한다면 deepcopy를 사용할 필요가 없다. 노드를 새로 생성해 텍스트 속성을 직접 복사한 다음 직렬화하는 편이 훨씬 빠르다. Listing 6에서 <Title> 엘리먼트와 <Copyright> 엘리먼트에 모두 deepcopy를 호출했더니 코드가 15%나 느려졌다. deepcopy는 커다란 하위 트리를 직렬화할 때 성능 향상 효과가 가장 크다.

Listing 7은 cElementTree로 Listing 6과 같은 작업을 수행한다. 성능을 비교하면 Listing 6이 Listing 7보다 거의 두 배가 빠르다(Listing 6은 50초가 걸렸고, Listing 7은 95초가 걸렸다).


Listing 7. cElementTree로 직렬화하기
def serialize_cet(elem):
    r = cet.Element('Record')

    # 동일한 텍스트 자식으로 새 엘리먼트를 생성한다.
    t = cet.SubElement(r, 'Title')
    t.text = elem.find('Title').text

    # ElementTree는 어버이 참조를 저장하지 않는다.
    # 엘리먼트는 다중 트리에 존재할 수 있다. 여기서 deepcopy를 사용할 필요는 없다.
    for c in elem.findall('Copyright'):
       r.append(h)
    return r

context = cet.iterparse('copyright.xml', events=('end','start'))
context = iter(context)
event, root = context.next()

for event, elem in context:
    if elem.tag == 'Record' and event =='end':
        result = serialize_cet(elem)
        out.write(cet.tostring(result, encoding='utf-8'))
        root.clear()

위와 같이 반복적으로 구문분석을 수행하는 방식이 더 궁금하다면 ElementTree 문서에서 "Incremental Parsing"을 살펴본다(참고자료에서 링크를 제공한다).




위로


엘리먼트 빠르게 찾기

XML 문서를 구문분석한 다음에 트리 내에서 특정한 정보를 찾는 작업을 가장 흔히 수행한다. lxml은 간단한 검색에서 XPath 1.0까지 다양한 방법을 제공한다. 개발자는 방법마다 성능과 최적화 기법에 관련된 특성을 알아두어야 한다.

findfindall은 피하라

ElementTree API에서 나온 findfindall 함수는 (XPath를 단순화한 형태인) ElementPath라는 언어를 사용하여 하위 트리를 검색한다. ElementTree을 사용하던 개발자라면 find/ElementPath 문법을 자연스럽게 그대로 사용할 가능성이 높다.

하지만 lxml은 하위 트리를 검색하는 방법 두 가지를 제공한다. 하나가 iterchildren/iterdescendants이고, 다른 하나가 진짜 XPath다. 단순히 노드 이름으로 검색하는 경우라면 iterchildren이나 iterdescendants에 태그 이름을 지정하는 쪽이 find나 findall에 ElementPath 패턴을 지정하는 쪽보다 훨씬 (경우에 따라서는 두 배나) 빠르다.

좀 더 복잡한 패턴으로 검색하는 경우라면 XPath 클래스를 사용하여 검색 패턴을 미리 컴파일한다. etree.XPath("child::Title")처럼 간단한 패턴은 iterchildren에 태그 이름을 지정한 만큼이나 효율적으로 돌아간다. 여기서는 사전 컴파일이 중요하다. 루프를 돌면서 패턴을 컴파일하거나 엘리먼트에서 xpath()를 호출한다면 (구체적인 방법은 참고자료에서 제공하는 lxml 문서를 참조한다) 미리 컴파일한 후 패턴을 반복해서 사용하는 경우보다 거의 두 배나 느려진다.

lxml이 XPath를 해석하는 속력은 아주 빠르다. 노드 일부만 직렬화하겠다면 처음부터 XPath 패턴을 정확히 지정하여 노드를 제한하는 방법이 모든 노드를 일일이 확인하는 방법보다 훨씬 낫다. 예를 들어, Listing 8에서 보듯이, XPath를 사용하여 night라는 단어를 포함하는 Title 엘리먼트로만 노드를 제한한 후 직렬화를 수행하면 전체 노드를 확인하면서 필요한 노드만 직렬화할 때보다 60%나 빨라진다.


Listing 8. XPath 클래스를 사용하여 조건적으로 직렬화하기

def write_if_node(out, node):
    if node is not None:
        out.write(etree.tostring(node, encoding='utf-8'))

def serialize_with_xpath(elem, xp1, xp2):
    '''<Record> 엘리먼트를 기준으로 두 가지 선행 컴파일 XPath 클래스를 적용한다.
    첫 번째 표현식이 일치하는 경우에만 노드를 반환한다.
    '''
    r = etree.Element('Record')

    t = etree.SubElement(r, 'Title')
    x = xp1(elem)
    if x:
        t.text = x[0].text
        for c in xp2(elem):
            r.append(deepcopy(c))
        return r

xp1 = etree.XPath("child::Title[contains(text(), 'night')]")
xp2 = etree.XPath("child::Copyright")
out = open('out.xml', 'w')
context = etree.iterparse('copyright.xml', events=('end',), tag='Record')
fast_iter(context, 
   lambda elem: write_if_node(out, serialize_with_xpath(elem, xp1, xp2)))

문서 미리 보고 노드 찾기

iterparse를 사용하는 경우에도 XPath로 문서를 미리 내다볼 수 있다. 현재 노드 직후부터 제목에 night가 들어가는 <Record> 노드를 모두 찾으려면 다음 코드를 실행한다.

etree.XPath("Title[contains(../Record/following::Record[1]/Title/text(), 'night')]")

하지만 Listing 4에서 설명한 메모리 절약 방식을 사용하는 경우라면 위 명령은 아무런 결과도 반환하지 않는다. 구문분석을 진행하면서 이전 노드를 모두 지워버렸기 때문이다.

etree.XPath("Title[contains(../Record/preceding::Record[1]/Title/text(), 'night')]")

위 문제를 좀 더 효율적으로 해결하는 알고리즘도 가능하다. 그러려면 (특히 문서 내에 노드가 불규칙적으로 흩어진 경우라면) 노드 전체를 분석해야 하는데, 이런 작업에는 보통 (eXist와 같이) XQuery를 사용하는 XML 데이터베이스가 더욱 적합하다.




위로


성능을 높이는 다른 방법

xml 라이브러리 내에서 성능을 높이는 방법도 있지만, 라이브러리 밖에서 프로그램 성능을 높이는 방법도 있다. 간단하게 코드를 고치는 방법부터 대규모 자료 처리 문제를 새로운 각도에서 접근하는 방법까지 구체적인 방법은 여러 가지다.

Psyco

Psyco 모듈은 작은 노력으로도 파이썬 응용 프로그램의 속력을 크게 높여준다. 하지만 그 유용성에 비해 흔히 간과되는 모듈이다. 순수한 파이썬 프로그램이라면 평균 두 배에서 네 배까지 성능이 좋아진다. 하지만 lxml은 C 모듈이 대다수 작업을 수행하므로 Psyco 모듈을 사용해도 성능은 별로 차이나지 않는다. 실제로 Psyco를 사용하여 Listing 4를 실행했더니 47.3초에서 43.9초로 3초가 줄었을 뿐이었다. Psyco는 메모리 부하가 크다. 즉, 시스템이 스왑을 시작한다면 스왑으로 느려지는 속력이 Psyco로 빨라지는 속력을 상쇄한다.

XML 구문분석 프로그램이라도 자주 실행하는 순수 파이썬 코드가 있다면 (예를 들어, 텍스트 노드에서 복잡한 문자열 조작을 수행한다면) Psyco로 성능이 좋아질 여지가 있다. Psyco에 관한 자세한 내용은 참고자료를 살펴본다.

스레드

응용 프로그램이 주로 lxml 기능만 사용한다면, 즉 내부적으로 C 라이브러리를 많이 사용한다면, 다중 프로세서 환경에서 스레드 응용 프로그램으로 실행하는 방법도 고려한다. 스레드를 시작할 때는 (특히 XSLT를 사용할 때는) 제약이 있다. 자세한 내용은 lxml 문서에서 스레드와 관련한 FAQ 절을 참조한다.

분할정복

문서가 아주 거대하다면 하위 트리로 나눠 분석하는 방법을 고려한다. lxml의 빠른 직렬화 기능을 사용하여 문서를 하위 트리로 분할한 후 여러 컴퓨터에서 각 하위 트리를 분석하는 방법도 좋다. 온 디맨드 가상 서버는 CPU 사용량이 높은 오프라인 작업을 수행하는 방법으로 점차 인기를 끌고 있다. 파이썬 프로그래머를 대상으로 아마존의 가상 EC2(Elastic Compute Cloud) 클러스터를 설정하고 관리하는 방법을 단계적으로 설명하는 자료도 있다. 자세한 내용은 참고자료를 살펴본다.




위로


대규모 XML을 처리하는 일반적인 전략

여기서 소개한 구체적인 코드 예제가 독자들의 프로젝트에 적용하기 어려울지도 모르겠다. 하지만 크기가 기가바이트(GB) 이상인 XML 정보를 처리해야 한다면 다음 원칙을 고려하기 바란다. 직접 테스트를 수행하고 lxml 문서를 읽으면서 얻은 지혜다.

  • 반복적 구문분석 전략을 사용하여 대규모 문서를 점진적으로 처리한다.
  • 전체 문서를 임의의 순서로 검색해야 한다면 색인화된 XML 데이터베이스를 사용한다.
  • 자료를 선택할 때는 아주 보수적인 방법을 취한다. 특정한 노드만 관심이 있다면 노드 이름으로 해당 노드만 선택한다. 서술 문법을 기술하겠다면 XPath 클래스와 메서드를 권장한다.
  • 수행할 작업과 개발자 숙련도를 고려한다. 성능에 신경 쓸 필요가 없다면 lxml의 objectify나 Amara 같은 객체 모델이 파이썬 개발자에게 더 편할지도 모른다. cElementTree는 구문분석을 수행하는 경우에만 성능을 높여준다.
  • 간단하더라도 벤치마킹을 수행한다. 수백만 개에 이르는 레코드를 처리한다면 작은 차이가 모여 큰 차이를 만든다. 가장 효율적인 방법이 무엇인지 분명하지 않을 때가 많다.



위로


결론

많은 소프트웨어 제품은 '두 개만 선택하라'는 권고가 따라온다. 무슨 뜻이냐면 개발자가 속력, 유연성, 안정성 중에서 두 개만 선택해야 한다는 소리다. 하지만 주의 깊게만 사용하면 lxml은 세 마리 토끼를 모두 잡을 수 있다. DOM 성능이나 SAX의 사건 구동 모델로 곤란을 겪던 XML 개발자는 이제 파이썬스러운 고차원 라이브러리를 사용할 기회가 생겼다. XML이 낯선 파이썬 프로그래머는 XPath와 XSLT의 표현력을 탐험할 쉬운 방법이 생겼다. lxml 응용 프로그램은 두 가지 구현 스타일을 모두 포용한다.

lxml은 여기서 소개한 내용보다 훨신 많은 장점을 제공한다. XML 구문분석이 필요 없는 작은 응용 프로그램이라면 lxml.objectify 모듈이 아주 유용하다. HTML 문서가 문법에 어긋날지도 모른다면 lxml.html 모듈과 BeautifulSoup 구문분석기를 권장한다. XSLT에서 호출이 가능한 파이썬 모듈을 작성하거나, 파이썬 확장 기능을 구현하거나, C 확장 기능을 구현한다면, lxml 자체를 확장해도 좋다. 여기서 언급한 모든 내용은 lxml 문서에서 좀 더 자세하게 설명한다. 링크는 참고자료에서 제공한다.



참고자료

교육

제품 및 기술 얻기
  • lxml: lxml 문서는 명료하며 대단히 꼼꼼하다. 특히 FAQ와 벤치마킹 절에 아주 유용한 정보가 많다.

  • Google U.S. copyright renewal data: 미 정부 저작권 갱신법 자료다. 구글이 XML로 변환했다. 압축 파일은 371MB이고, 총 레코드 수는 42만 6907개다.

  • Open Directory RDF content: 오픈 디렉터리 데이터베이스 RDF 자료다. 압축 파일은 1.9GB이고, 총 레코드 수는 535만 4663개다.

  • eXist: XQuery를 사용하는 오픈 소스 데이터베이스 관리 시스템이다.

  • Psyco: 파이썬 코드의 실행 속력을 크게 높여주는 파이썬 확장 기능이다.

  • Amara: 풍부한 기능과 파이썬스러운 API를 제공하는 파이썬 XML 라이브러리다. Amara는 lxml이나 cElementTree만큼 성능이 우수하진 않으나 대다수 XML 작업에 매우 유용하다.

  • IBM 평가판 소프트웨어: developeWorks에서 평가판 소프트웨어를 내려받아 다음 프로젝트에 활용하자. 평가판 소프트웨어는 DB2®, Lotus®, Rational®, Tivoli®, WebSphere® 등과 같은 개발 도구와 미들웨어 제품을 제공한다.


토론


필자소개

Photo of Liza Daly

Liza Daly는 출판업계에서 사용하는 응용 프로그램을 개발하는 전문적인 소프트웨어 엔지니어다. Liza Daly는 옥스포드 대학교 출판부, 오라일리 미디어, 기타 출판사를 위한 주요 온라인 제품에 참여하여 개발을 이끌었다. 현재는 컨설턴트로 독립했으며, eBook 응용 프로그램을 개발하는 오픈 소스 프로젝트인 Threepress를 창립했다.




기사에 대한 평가


보다 나은 서비스를 제공하기 위함이오니 잠시 짬을 내어 이 양식을 제출하여 주십시오.



 


 


 


이 문서 북마킹 하기

mar.gar.in mar.gar.in naver naver eolin eolin del.icio.us del.icio.us





위로


Adobe, the Adobe logo, PostScript, and the PostScript logo are either registered trademarks or trademarks of Adobe Systems Incorporated in the United States, and/or other countries. IBM, the IBM logo, ibm.com, DB2, developerWorks, Lotus, Rational, Tivoli, WebSphere, and pureXML are trademarks of IBM Corporation in the United States, other countries, or both. Other company, product, or service names may be trademarks or service marks of others. 기타 회사, 제품, 및 서비스명은 다른 상표나 서비스 마크일 수 있습니다.

developerWorks 콘텐트를 다른 사이트에 전재하기:
developerWorks 콘텐트에 대한 저작권은 IBM에 있습니다. IBM의 서면 허가나 원본 저자의 허락이 없이는 전재를 금합니다. 저희 콘텐트를 전재하시려면 IBM developerWorks 담당자 에게 문의하십시오.
    IBM 소개 개인정보 보호정책 문의