在Python中解析XML的帮助

1 投票
5 回答
535 浏览
提问于 2025-04-16 04:36

我有一个XML文件,里面包含了数百个文档。每个文档的结构大概是这样的:

<DOC>
<DOCNO> FR940104-2-00001 </DOCNO>
<PARENT> FR940104-2-00001 </PARENT>
<TEXT>

<!-- PJG FTAG 4703 -->

<!-- PJG STAG 4703 -->

<!-- PJG ITAG l=90 g=1 f=1 -->

<!-- PJG /ITAG -->

<!-- PJG ITAG l=90 g=1 f=4 -->
Federal Register
<!-- PJG /ITAG -->

<!-- PJG ITAG l=90 g=1 f=1 -->
 / Vol. 59, No. 2 / Tuesday, January 4, 1994 / Notices
<!-- PJG 0012 frnewline -->

<!-- PJG /ITAG -->

<!-- PJG ITAG l=01 g=1 f=1 -->
Vol. 59, No. 2
<!-- PJG 0012 frnewline -->

<!-- PJG /ITAG -->

<!-- PJG ITAG l=02 g=1 f=1 -->
Tuesday, January 4, 1994
<!-- PJG 0012 frnewline -->

<!-- PJG 0012 frnewline -->

<!-- PJG /ITAG -->

<!-- PJG /STAG -->

<!-- PJG /FTAG -->
</TEXT>
</DOC>

我想把这个XML文档加载到一个字典里,字典的名字叫Text。字典的键(Key)是DOCNO,值(Value)是标签里面的文本。而且这个文本里不能包含所有的评论。比如说,Text['FR940104-2-00001']应该包含的内容是Federal Register / Vol. 59, No. 2 / Tuesday, January 4, 1994 / Notices Vol. 59, No. 2 Tuesday, January 4, 1994。这是我写的代码。

L = doc.getElementsByTagName("DOCNO")
for node2 in L:
    for node3 in node2.childNodes:
        if node3.nodeType == Node.TEXT_NODE:            
            docno.append(node3.data);
        #print node2.data
L = doc.getElementsByTagName("TEXT")
i = 0
for node2 in L:
    for node3 in node2.childNodes:
        if node3.nodeType == Node.TEXT_NODE:
            Text[docno[i]] = node3.data
    i = i+1

让我惊讶的是,运行我的代码后,Text['FR940104-2-00001']的结果却是u'\n',这怎么回事呢?我该怎么才能得到我想要的内容呢?

5 个回答

1

使用 lxml

import lxml.etree as le
with open('test.xml') as f:
    doc=le.parse(f)

texts={}
for docno in doc.xpath('DOCNO'):
    docno_text=docno.text.strip()    
    text=' '.join([t.strip() 
          for t in  docno.xpath('following-sibling::TEXT[1]/text()')
          if t.strip()])
    texts[docno.text]=text

print(texts)
# {'FR940104-2-00001': 'Federal Register / Vol. 59, No. 2 / Tuesday, January 4, 1994 / Notices Vol. 59, No. 2 Tuesday, January 4, 1994'}

这个版本比我之前的 lxml 解决方案简单一些。它可以处理多个 DOCNO 和 TEXT 节点。DOCNO 和 TEXT 节点应该是交替出现的,但无论如何,DOCNO 都是和它后面最近的 TEXT 节点关联在一起的。

2

跟unutbu的回答有点像,不过我觉得可以更简单一些:

from lxml import etree
with open('test.xml') as f:
    doc=etree.parse(f)

result={}
for elm in doc.xpath("/DOC[DOCNO]"):
    key = elm.xpath("DOCNO")[0].text.strip()
    value = "".join(t.strip() for t in elm.xpath("TEXT/text()") if t.strip())
    result[key] = value

在这个例子中,用来找到DOC元素的XPath需要根据你的实际文档进行调整。比如,如果所有的DOC元素都有一个共同的顶层元素,你就需要把它改成/*/DOC。这个XPath中的条件会跳过那些没有DOCNO子元素的DOC元素,否则在设置键的时候会出错。

4

你可以通过使用 xml.sax.handler 来避免对文档进行两次循环:

import xml.sax.handler
import collections


class DocBuilder(xml.sax.handler.ContentHandler):
    def __init__(self):
        self.state=''
        self.docno=''
        self.text=collections.defaultdict(list)
    def startElement(self, name, attrs):
        self.state=name
    def endElement(self, name):
        if name==u'TEXT':
            self.docno=''
    def characters(self,content):        
        content=content.strip()
        if content:
            if self.state==u'DOCNO':
                self.docno+=content
            elif self.state==u'TEXT':
                if content:
                    self.text[self.docno].append(content)


with open('test.xml') as f:
    data=f.read()            
builder = DocBuilder()
xml.sax.parseString(data, builder)
for key,value in builder.text.iteritems():
    print('{k}: {v}'.format(k=key,v=' '.join(value)))
# FR940104-2-00001: Federal Register / Vol. 59, No. 2 / Tuesday, January 4, 1994 / Notices Vol. 59, No. 2 Tuesday, January 4, 1994

撰写回答