1

Python 3.4,使用 etree.iterparse 解析 GB++ 大小的 XML 维基百科转储文件。我想在当前匹配的<page>元素中测试它的<ns>值,然后根据后一个值我想导出整个<page>对象的源 XML 及其所有内容,包括嵌套在其中的任何元素,即整篇文章的 XML。

我可以迭代<page>对象并找到我想要的对象,但是所有可用的函数似乎都想读取文本/属性值,而我只需要源文件的 XML 代码的 utf8 字符串副本,用于完整的范围<page>对象。这可能吗?

XML 的精简版本如下所示:

<mediawiki xmlns="http://www.mediawiki.org/xml/export-0.10/" xml:lang="en">
  <page>
    <title>Some Article</title>
    <ns>0</ns>
    <revision>
      <timestamp>2017-07-27T00:59:41Z</timestamp>
      <text xml:space="preserve">some text</text>
    </revision>
  </page>
  <page>
    <title>User:Wonychifans</title>
    <ns>2</ns>
    <revision>
      <text xml:space="preserve">blah blah</text>
    </revision>
  </page>
</mediawiki>

让我进行价值测试的 python 代码在<ns>这里:

``from lxml import etree

# store namespace string for all elements (only one used in Wikipedia XML docs)
NAMESPACE = '{http://www.mediawiki.org/xml/export-0.10/}'
ns = {'wiki' : 'http://www.mediawiki.org/xml/export-0.10/'}

context = etree.iterparse('src.xml', events=('end',))
for event, elem in context:
  # at end of parsing each
  if elem.tag == (NAMESPACE+'page') and event == 'end':
    tagNs = elem.find('wiki:ns',ns)
    if tagNs is not None:
      nsValue = tagNs.text
      if nsValue == '2':
        # export the current <page>'s XML code

在这种情况下,我只想提取第二个元素的 XML 代码<page>一个字符串:

  <page>
    <title>User:Wonychifans</title>
    <ns>2</ns>
    <revision>
      <text xml:space="preserve">blah blah</text>
    </revision>
  </page>

编辑:小错字和更好的标记

4

2 回答 2

1

你可以这样做。

>>> from lxml import etree
>>> mediawiki = etree.iterparse('mediawiki.xml')
>>> page_content = {}
>>> for ev, el in mediawiki:
...     if el.tag=='page':
...         if page_content['ns']=='2':
...             print (page_content)
...         page_content = {}
...     else:
...         page_content[el.tag.replace('{http://www.mediawiki.org/xml/export-0.10/}', '')] = \
...             el.text.strip() if el.text else None
... 
>>> page_content
{'mediawiki': '', 'revision': '', 'timestamp': '2017-07-27T00:59:41Z', 'title': 'User:Wonychifans', 'page': '', 'text': 'blah blah', 'ns': '2'}

因为输出 xml 的结构非常简单,所以从字典中构造它应该没有困难。

编辑:虽然这种方法需要两次通过 xml 文件,但它可能更快,并且确实恢复了所需的 xml。

首先,寻找page元素的起始行。

>>> from lxml import etree
>>> mediawiki = etree.iterparse('mediawiki.xml', events=("start", "end"))
>>> for ev, el in mediawiki:
...     tag = el.tag[1+el.tag.rfind('}'):]
...     if ev=='start' and tag=='page':
...         keep=False
...     if ev=='start' and tag=='ns' and el.text=='2':
...         keep=True
...     if ev=='end' and tag=='page' and keep:
...         print (el.sourceline)
... 
10

再次浏览 xml 以page使用起点找到完整的条目。

>>> with open('mediawiki.xml') as mediawiki:
...     for _ in range(9):
...         r = next(mediawiki)
...     for line in mediawiki:
...         print (line.strip())
...         if '</page>' in line:
...             break
...         
<page>
<title>User:Wonychifans</title>
<ns>2</ns>
<revision>
<text xml:space="preserve">blah blah</text>
</revision>
</page>
于 2017-11-09T21:57:56.507 回答
1

我已将 Bill Bell 的答案标记为已接受,因为它有助于我找到最终解决方案,其核心如下。外部循环让我可以循环超过 50 个源 XML 文件。

由于某些源很大,因此代码会在循环中检查复制的源数据是否超过 1GB。如果是这样,则将数据写入文件并清除缓冲区字符串变量。否则,所有提取的数据都将在读取源文件结束时写入。

进一步的改进是监控输出文件的大小并在超过给定大小时切换输出源。在这种情况下,每次运行脚本时只扫描整个源集的一部分会更容易。

为简洁起见,我删除了一些日志记录和打印语句:

<!-- language: lang-python -->

import sys

dataSourceStr = '/Users/x/WP-data/'
outputDataStr = '/Users/x/WP-data/ns-data/'
headfile = open("header.txt","r")
headStr = headfile.read()
headfile.close()
footStr = '</mediawiki>'
matchCount = 0
strPage = ''
strPage = headStr
fileNum = 20 
nameSpaceValue = 4
startNum = 41 # starting file number
lastNum = 53 # ending file number
endNum = lastNum + 1
outputDataFile = outputDataStr + 'ns' + str(nameSpaceValue) + '.xml'

for fileNum in range (startNum , endNum):
  with open(dataSourceStr + str(fileNum) + '.xml') as mediawiki:
    lineNum = 44
    blnKeep = False
    strPage = ''
    strItem = ''
    loopMatchCount = 0
    for _ in range(lineNum):
      r = next(mediawiki)
    for line in mediawiki:
      if '<ns>' + str(nameSpaceValue) + '</ns>' in line:
        blnKeep = True
        matchCount = matchCount + 1
        loopMatchCount = loopMatchCount + 1
      strItem = strItem + line
      lineNum = lineNum + 1
      if '</page>' in line:
        if blnKeep:
          strPage = strPage + strItem
          strItem = ''
          blnKeep = False
          strPageSize = sys.getsizeof(strPage)
          if strPageSize > 1073741824:
            file = open(outputDataFile,"a")
            file.write(strPage)
            file.close()
            strPage = ''
        else:
          strItem = ''

  mediawiki.close
  file = open(outputDataFile,"a")
  file.write(strPage)
  file.close()

file = open(outputDataFile,"a")
file.write(footStr)
file.close()

我相信这可能会更优雅,但我希望这有助于任何非专家来到这里并尝试做这类事情。

于 2017-11-28T14:44:25.983 回答