将维基百科的所有数据加载到MongoDB中?

3
在2013年的MongoNYC会议上,有一位演讲者提到他们使用维基百科的副本来测试全文搜索。我尝试复制这个操作,但由于文件大小和格式的原因,发现这并不容易。
以下是我的操作步骤:
$ wget http://download.wikimedia.org/enwiki/latest/enwiki-latest-pages-articles.xml.bz2
$ bunzip2 enwiki-latest-pages-articles.xml.bz2 
$ python
>>> import xml.etree.ElementTree as ET
>>> tree = ET.parse('enwiki-latest-pages-articles.xml')
Killed

当我尝试使用标准XML解析器解析文件时,Python在XML文件的大小上出现了错误。 有没有其他建议将9GB的XML文件转换为JSON格式以便加载到mongoDB中呢?
更新1:
根据Sean的建议,我也尝试了迭代元素树:
>>> import xml.etree.ElementTree as ET
>>> context = ET.iterparse('enwiki-latest-pages-articles.xml', events=("start", "end"))
>>> context = iter(context)
>>> event, root = context.next()
>>> for i in context[0:10]:
...     print(i)
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: '_IterParseIterator' object has no attribute '__getitem__'
>>> for event, elem in context[0:10]:
...     if event == "end" and elem.tag == "record":
...             print(elem)
...             root.clear()
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: '_IterParseIterator' object has no attribute '__getitem__'

同样,没有运气。

更新 2

根据Asya Kamsky下面的建议进行跟进。

尝试使用xml2json

$ git clone https://github.com/hay/xml2json.git
$ ./xml2json/xml2json.py -t xml2json -o enwiki-latest-pages-articles.json enwiki-latest-pages-articles.xml
Traceback (most recent call last):
  File "./xml2json/xml2json.py", line 199, in <module>
    main()
  File "./xml2json/xml2json.py", line 181, in main
    input = open(arguments[0]).read()
MemoryError

这里介绍一下xmlutils
$ pip install xmlutils
$ xml2json --input "enwiki-latest-pages-articles.xml" --output "enwiki-latest-pages-articles.json"
xml2sql by Kailash Nadh (http://nadh.in)
    --help for help


Wrote to enwiki-latest-pages-articles.json

但是这些内容只有一条记录,没有迭代。

xmltodict也很有前途,因为它使用迭代的Expat并且非常适合维基百科。但是在20分钟左右后,它也会耗尽内存:

>>> import xmltodict
>>> f = open('enwiki-latest-pages-articles.xml')
>>> doc = xmltodict.parse(f)
Killed

更新3

这是作为对Ross下面答案的回应,模仿他提到的链接来建立我的解析器:

from lxml import etree

file = 'enwiki-latest-pages-articles.xml'

def page_handler(page):
    try:
        print page.get('title','').encode('utf-8')
    except:
        print page
        print "error"

class page_handler(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

def fast_iter(context, func):
    for event, elem in context:
        print(elem)
        elem.clear()
        while elem.getprevious() is not None:
            del elem.getparent()[0]
    del context

process_element = etree.XMLParser(target = page_handler())

context = etree.iterparse( file, tag='item' )
fast_iter(context,process_element)

错误为:
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in fast_iter
  File "iterparse.pxi", line 484, in lxml.etree.iterparse.__next__ (src/lxml/lxml.etree.c:112653)
  File "iterparse.pxi", line 537, in lxml.etree.iterparse._read_more_events (src/lxml/lxml.etree.c:113223)
  File "parser.pxi", line 596, in lxml.etree._raiseParseError (src/lxml/lxml.etree.c:83186)
lxml.etree.XMLSyntaxError: Extra content at the end of the document, line 22, column 1

这是Python配置中的问题还是硬件限制?如果是硬件限制,考虑寻找云服务提供商来完成此任务。 - Nick Maroulis
这个链接是stackoverflow上的一个问题,讨论如何在Python中快速解析大型XML文档。可能对你有用。 - sean
只要它超过了您可用的内存,它就会被终止,我不明白为什么您需要转换为JSON。 - PepperoniPizza
@marabutt,我现在正在使用Amazon实例,但它没有9GB的任务需求,我希望在使用更大的硬件之前先找到正确的解决方案。 - Mittenchops
1
@PepperoniPizza 我只对将其转换为JSON格式感兴趣,以便将对象插入MongoDB。如果您知道一种直接从XML进行迭代的方法,请告诉我。 =) - Mittenchops
我在互联网上找到了一个名为xmltojson.py的文件 - 如果你在谷歌上搜索它,你应该能够使用它 - 我用它来转换一些大文件... - Asya Kamsky
2个回答

1

1
你需要使用iterparse进行迭代,而不是将整个文件加载到内存中。关于如何转换为json甚至Python对象以便存储在数据库中,请参见:https://github.com/knadh/xmlutils.py/blob/master/xmlutils/xml2json.py

更新

使用iterparse并保持低内存占用的示例:

尝试使用Liza Daly的fast_iter的变体。在处理元素elem后,它调用elem.clear()来删除后代,并删除前面的兄弟节点。

from lxml import etree

def fast_iter(context, func):
    # http://www.ibm.com/developerworks/xml/library/x-hiperfparse/
    # Author: Liza Daly
    for event, elem in context:
        print(elem)
        elem.clear()
        while elem.getprevious() is not None:
            del elem.getparent()[0]
    del context

context = etree.iterparse( MYFILE, tag='item' )
fast_iter(context,process_element)

达利的文章是一篇很好的阅读材料,特别是当你处理大型XML文件时。

更新1中的示例使用的是iterparse而不是parse - Mittenchops
我更新了 - 在 Update1 中你有一个错误,这就是为什么它失败的原因。不要通过 context[0:10] 调用 __getitem__ - 只需迭代即可。 - Ross
我认为这指引了我正确的方向,但您能解释一下process_element函数的形式吗?我正在更新我的答案,说明我尝试过什么以及哪些方法不起作用。 - Mittenchops

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接