'ElementTree'를 통해 Python에서 네임 스페이스로 XML 구문 분석


163

파이썬을 사용하여 구문 분석하려는 다음 XML이 있습니다 ElementTree.

<rdf:RDF xml:base="http://dbpedia.org/ontology/"
    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    xmlns:owl="http://www.w3.org/2002/07/owl#"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
    xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
    xmlns="http://dbpedia.org/ontology/">

    <owl:Class rdf:about="http://dbpedia.org/ontology/BasketballLeague">
        <rdfs:label xml:lang="en">basketball league</rdfs:label>
        <rdfs:comment xml:lang="en">
          a group of sports teams that compete against each other
          in Basketball
        </rdfs:comment>
    </owl:Class>

</rdf:RDF>

모든 owl:Class태그 를 찾은 다음 그 rdfs:label안에 있는 모든 인스턴스 의 값을 추출하고 싶습니다 . 다음 코드를 사용하고 있습니다.

tree = ET.parse("filename")
root = tree.getroot()
root.findall('owl:Class')

네임 스페이스로 인해 다음과 같은 오류가 발생합니다.

SyntaxError: prefix 'owl' not found in prefix map

http://effbot.org/zone/element-namespaces.htm 에서 문서를 읽으려고했지만 위의 XML에 중첩 된 네임 스페이스가 여러 개 있기 때문에 여전히이 작업을 수행 할 수 없습니다.

모든 owl:Class태그 를 찾기 위해 코드를 변경하는 방법을 알려주십시오 .

답변:


226

ElementTree는 네임 스페이스에 대해 너무 똑똑하지 않습니다. 당신은 줄 필요 .find(), findall()iterfind()방법을 명시 적 네임 스페이스 사전. 이것은 잘 문서화되어 있지 않습니다 :

namespaces = {'owl': 'http://www.w3.org/2002/07/owl#'} # add more as needed

root.findall('owl:Class', namespaces)

접두사는 전달한 매개 변수 에서만 조회됩니다 namespaces. 이는 원하는 네임 스페이스 접 두부를 사용할 수 있음을 의미합니다. API는 owl:부분을 분리 하고 namespaces사전 에서 해당 네임 스페이스 URL을 찾은 다음 XPath 표현식을 찾도록 검색을 변경합니다 {http://www.w3.org/2002/07/owl}Class. 물론 동일한 구문을 직접 사용할 수 있습니다.

root.findall('{http://www.w3.org/2002/07/owl#}Class')

lxml라이브러리로 전환 할 수 있다면 더 좋습니다. 해당 라이브러리는 동일한 ElementTree API를 지원하지만 .nsmap요소 의 속성 에서 네임 스페이스를 수집합니다 .


7
감사합니다. 하드 코딩없이 네임 스페이스를 XML에서 직접 가져올 수있는 아이디어가 있습니까? 아니면 어떻게 무시할 수 있습니까? findall ( '{*} Class')을 시도했지만 내 경우에는 작동하지 않습니다.
코스타 노스

7
xmlns속성을 직접 트리에서 스캔해야합니다 . 이 질문에 명시된 바와 같이, lxml당신을 위해이 작업을 수행의 xml.etree.ElementTree모듈은하지 않습니다. 그러나 특정 (이미 하드 코딩 된) 요소를 일치시키려는 경우 특정 네임 스페이스의 특정 요소를 일치 시키려고합니다. 해당 네임 스페이스는 요소 이름 이상으로 문서간에 변경되지 않습니다. 요소 이름으로 하드 코딩 할 수도 있습니다.
Martijn Pieters

14
@Jon : register_namespace검색이 아닌 직렬화에만 영향을줍니다.
Martijn Pieters

5
유용 할 수있는 작은 추가 : 사용하는 경우 cElementTree대신 ElementTree, findall키워드 인수로 네임 스페이스를 가지고,하지만하지 않습니다 단순히 일반적인 인수, 즉 사용과 같은 ctree.findall('owl:Class', namespaces).
egpbos

2
@Bludwarf : 문서에서 언급했지만 (지금 작성하지 않은 경우)주의 깊게 읽어야합니다. 네임 스페이스를 사용 하여 XML 구문 분석 섹션을 참조하십시오. 인수 findall를 사용 하지 않고 사용하는 것과 대조되는 예제가 namespace있지만 인수는 Element 오브젝트 섹션 의 메소드 메소드에 대한 인수 중 하나로 언급되지 않았습니다 .
Wilson F

57

네임 스페이스를 하드 코딩하거나 텍스트를 스캔하지 않고 lxml로이를 수행하는 방법은 다음과 같습니다 (Martijn Pieters가 언급 한대로).

from lxml import etree
tree = etree.parse("filename")
root = tree.getroot()
root.findall('owl:Class', root.nsmap)

업데이트 :

5 년 후에도 여전히이 문제의 변형이 발생합니다. lxml은 위에 표시된 것처럼 도움이되지만 모든 경우에 도움이되지는 않습니다. 주석 작성자는 문서를 병합 할 때이 기술과 관련하여 유효한 지적을 할 수 있지만 대부분의 사람들은 단순히 문서를 검색하는 데 어려움을 겪고 있다고 생각합니다.

다른 경우와 내가 처리 한 방법이 있습니다.

<?xml version="1.0" ?><Tag1 xmlns="http://www.mynamespace.com/prefix">
<Tag2>content</Tag2></Tag1>

접두사가없는 xmlns는 접두사가없는 태그가이 기본 네임 스페이스를 가져옵니다. 이는 Tag2를 검색 할 때 네임 스페이스를 포함시켜야 태그를 찾을 수 있음을 의미합니다. 그러나 lxml은 None을 키로 사용하여 nsmap 항목을 만들고 검색 방법을 찾을 수 없습니다. 그래서 나는 이와 같은 새로운 네임 스페이스 사전을 만들었습니다.

namespaces = {}
# response uses a default namespace, and tags don't mention it
# create a new ns map using an identifier of our choice
for k,v in root.nsmap.iteritems():
    if not k:
        namespaces['myprefix'] = v
e = root.find('myprefix:Tag2', namespaces)

3
전체 네임 스페이스 URL 하드 코딩해야하는 네임 스페이스 식별자입니다. 로컬 접두사 ​​( owl)는 파일에서 파일로 변경할 수 있습니다. 따라서이 답변이 제안하는 것을하는 것은 정말 나쁜 생각입니다.
Matti Virkkunen

1
올빼미 정의가 파일에서 파일로 변경 될 수있는 경우 정확히 @MattiVirkkunen, 우리는 하드 코딩 대신 각 파일에 정의 된 정의를 사용해서는 안됩니까?
Loïc Faure-Lacroix 3

@ LoïcFaure-Lacroix : 일반적으로 XML 라이브러리를 사용하면 해당 부분을 추상화 할 수 있습니다. 파일 자체에 사용 된 접두사를 알거나 신경 쓸 필요가 없으며 구문 분석을 위해 고유 한 접두사를 정의하거나 전체 네임 스페이스 이름 만 사용하면됩니다.
Matti Virkkunen

이 답변은 적어도 찾기 기능을 사용할 수 있도록 도와주었습니다. 고유 한 접두사를 만들 필요가 없습니다. 방금 key = list (root.nsmap.keys ()) [0]하고 키를 접두사로 추가했습니다. root.find (f '{key} : Tag2', root.nsmap)
Eelco van Vliet

30

참고 : 이것은 하드 코딩 된 네임 스페이스를 사용하지 않고 Python의 ElementTree 표준 라이브러리에 유용한 답변입니다.

XML 데이터에서 네임 스페이스의 접두사 및 URI를 추출하려면 네임 ElementTree.iterparse스페이스 시작 이벤트 ( start-ns ) 만 구문 분석 하여 함수 를 사용할 수 있습니다 .

>>> from io import StringIO
>>> from xml.etree import ElementTree
>>> my_schema = u'''<rdf:RDF xml:base="http://dbpedia.org/ontology/"
...     xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
...     xmlns:owl="http://www.w3.org/2002/07/owl#"
...     xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
...     xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
...     xmlns="http://dbpedia.org/ontology/">
... 
...     <owl:Class rdf:about="http://dbpedia.org/ontology/BasketballLeague">
...         <rdfs:label xml:lang="en">basketball league</rdfs:label>
...         <rdfs:comment xml:lang="en">
...           a group of sports teams that compete against each other
...           in Basketball
...         </rdfs:comment>
...     </owl:Class>
... 
... </rdf:RDF>'''
>>> my_namespaces = dict([
...     node for _, node in ElementTree.iterparse(
...         StringIO(my_schema), events=['start-ns']
...     )
... ])
>>> from pprint import pprint
>>> pprint(my_namespaces)
{'': 'http://dbpedia.org/ontology/',
 'owl': 'http://www.w3.org/2002/07/owl#',
 'rdf': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
 'rdfs': 'http://www.w3.org/2000/01/rdf-schema#',
 'xsd': 'http://www.w3.org/2001/XMLSchema#'}

그런 다음 사전을 검색 함수에 인수로 전달할 수 있습니다.

root.findall('owl:Class', my_namespaces)

1
이는 lxml에 액세스 할 수없고 네임 스페이스를 하드 코딩하지 않으려는 사용자에게 유용합니다.
delrocco

1
ValueError: write to closed이 줄에 대한 오류가 있습니다 filemy_namespaces = dict([node for _, node in ET.iterparse(StringIO(my_schema), events=['start-ns'])]). 어떤 아이디어가 잘못되고 싶습니까?
Yuli

아마도 오류는 ASCII 문자열을 거부하는 클래스 io.StringIO와 관련이 있습니다. Python3으로 레시피를 테스트했습니다. 샘플 문자열에 유니 코드 문자열 접두사 'u'를 추가하면 Python 2 (2.7)에서도 작동합니다.
Davide Brunato

대신 dict([...])dict comprehension을 사용할 수도 있습니다.
Arminius

대신 StringIO(my_schema)XML 파일의 파일 이름을 넣을 수도 있습니다.
JustAC0der

6

나는 이것과 비슷한 코드를 사용하고 있으며 평소와 같이 항상 문서를 읽을 가치가 있음을 발견했습니다!

findall ()은 현재 태그의 직접적인 하위 요소 만 찾습니다 . 따라서 실제로는 아닙니다.

특히 하위 하위 요소 등이 포함되도록 크고 복잡한 xml 파일을 처리하는 경우 코드에서 다음 작업을 수행하는 것이 좋습니다. XML의 요소가 어디에 있는지 알고 있다면 괜찮을 것입니다. 이것이 기억할 가치가 있다고 생각했습니다.

root.iter()

참조 : https://docs.python.org/3/library/xml.etree.elementtree.html#finding-interesting-elements "Element.findall ()은 현재 요소의 직접적인 자식 인 태그가있는 요소 만 찾습니다. Element.find ()는 특정 태그가있는 첫 번째 자식을 찾고 Element.text는 요소의 텍스트 내용에 액세스합니다. Element.get ()은 요소의 속성에 액세스합니다. "


6

네임 스페이스 형식으로 네임 스페이스를 가져 오려면 예를 들어 {myNameSpace}다음을 수행 할 수 있습니다.

root = tree.getroot()
ns = re.match(r'{.*}', root.tag).group(0)

이런 식으로 나중에 코드에서 나중에 노드를 찾는 데 사용할 수 있습니다 (예 : 문자열 보간 (Python 3)).

link = root.find(f"{ns}link")

0

내 솔루션은 @Martijn Pieters의 의견을 기반으로합니다.

register_namespace 검색이 아닌 직렬화에만 영향을줍니다.

따라서 여기서 속임수는 직렬화 및 검색에 다른 사전을 사용하는 것입니다.

namespaces = {
    '': 'http://www.example.com/default-schema',
    'spec': 'http://www.example.com/specialized-schema',
}

이제 구문 분석 및 작성을위한 모든 네임 스페이스를 등록하십시오.

for name, value in namespaces.iteritems():
    ET.register_namespace(name, value)

검색을 위해 ( find(), findall(), iterfind()) 우리는 비어 있지 않은 접두사가 필요합니다. 이러한 함수를 수정 된 사전에 전달하십시오 (여기서는 원래 사전을 수정하지만 네임 스페이스가 등록 된 후에 만 ​​작성해야 함).

self.namespaces['default'] = self.namespaces['']

이제 find()제품군 의 기능을 default접두사 와 함께 사용할 수 있습니다 .

print root.find('default:myelem', namespaces)

그러나

tree.write(destination)

기본 네임 스페이스의 요소에 접두사를 사용하지 않습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.