Разбор огромного XML-файла с помощью `pythons etree.iterparse ()` работает неправильно.Есть ли логическая ошибка в коде? - PullRequest
2 голосов
/ 17 мая 2011

Я хочу разобрать огромный файл xml-файла. Записи в этом огромном файле выглядят, например, как this . И вообще файл выглядит так

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE dblp SYSTEM "dblp.dtd">
<dblp>
    record_1
    ...
    record_n
</dblp>

Я написал некоторый код, который даст мне выбор записей из этого файла.

Если я разрешу запускать код (занимает около 50 минут, включая хранение в базе данных MySQL) Я замечаю, что есть запись, в которую записывается почти миллион авторов. Это должно быть неправильно , Я даже проверил его, заглянув в файл, чтобы убедиться, что в нем нет ошибок. В статье всего 5 или 6 авторов, так что все в порядке с dblp.xml. Поэтому я предполагаю логическую ошибку в моем коде. Но я не могу понять, где это может быть. Возможно, кто-нибудь подскажет, где ошибка?

Код останавливается в строке if len(auth) > 2000.

import sys
import MySQLdb
from lxml import etree


elements = ['article', 'inproceedings', 'proceedings', 'book', 'incollection']
tags = ["author", "title", "booktitle", "year", "journal"]


def fast_iter(context, cursor):
    mydict = {} # represents a paper with all its tags.
    auth = [] # a list of authors who have written the paper "together".
    counter = 0 # counts the papers

    for event, elem in context:
        if elem.tag in elements and event == "start":
            mydict["element"] = elem.tag
            mydict["mdate"] = elem.get("mdate")
            mydict["key"] = elem.get("key")

        elif elem.tag == "title" and elem.text != None:
            mydict["title"] = elem.text
        elif elem.tag == "booktitle" and elem.text != None:
            mydict["booktitle"] = elem.text
        elif elem.tag == "year" and elem.text != None:
            mydict["year"] = elem.text
        elif elem.tag == "journal" and elem.text != None:
            mydict["journal"] = elem.text
        elif elem.tag == "author" and elem.text != None:
            auth.append(elem.text)
        elif event == "end" and elem.tag in elements:
            counter += 1
            print counter
            #populate_database(mydict, auth, cursor)
            mydict.clear()
            auth = []
            if mydict or auth:
                sys.exit("Program aborted because auth or mydict was not deleted properly!")
        if len(auth) > 200: # There are up to ~150 authors per paper. 
            sys.exit("auth: It seams there is a paper which has too many authors.!")
        if len(mydict) > 50: # A paper can have much metadata.
            sys.exit("mydict: It seams there is a paper which has too many tags.")

        elem.clear()
        while elem.getprevious() is not None:
            del elem.getparent()[0]
    del context


def main():
        cursor = connectToDatabase()
        cursor.execute("""SET NAMES utf8""")

        context = etree.iterparse(PATH_TO_XML, dtd_validation=True, events=("start", "end"))
        fast_iter(context, cursor)

        cursor.close()


if __name__ == '__main__':
    main()

EDIT:

Когда я писал эту функцию, я был совершенно не прав. Я допустил огромную ошибку, упустив из виду, что при попытке пропустить некоторые нежелательные записи меня перепутали с некоторыми желаемыми записями. И в определенный момент в файле, где я пропустил почти миллион записей подряд, следующая требуемая запись была взорвана.

С помощью Джона и Пола мне удалось переписать мой код. Сейчас разбирается, и швы делают это хорошо. Я сообщу, если некоторые неожиданные ошибки остались нерешенными. В остальном спасибо всем за помощь! Я действительно оценил это!

def fast_iter2(context, cursor):
    elements = set([
        'article', 'inproceedings', 'proceedings', 'book', 'incollection',
        'phdthesis', "mastersthesis", "www"
        ])
    childElements = set(["title", "booktitle", "year", "journal", "ee"])

    paper = {} # represents a paper with all its tags.
    authors = []   # a list of authors who have written the paper "together".
    paperCounter = 0
    for event, element in context:
        tag = element.tag
        if tag in childElements:
            if element.text:
                paper[tag] = element.text
                # print tag, paper[tag]
        elif tag == "author":
            if element.text:
                authors.append(element.text)
                # print "AUTHOR:", authors[-1]
        elif tag in elements:
            paper["element"] = tag
            paper["mdate"] = element.get("mdate")
            paper["dblpkey"] = element.get("key")
            # print tag, element.get("mdate"), element.get("key"), event
            if paper["element"] in ['phdthesis', "mastersthesis", "www"]:
                pass
            else:
                populate_database(paper, authors, cursor)
            paperCounter += 1
            print paperCounter
            paper = {}
            authors = []
            # if paperCounter == 100:
            #     break
            element.clear()
            while element.getprevious() is not None:
                del element.getparent()[0]
    del context

Ответы [ 2 ]

3 голосов
/ 17 мая 2011

Пожалуйста, устраните один источник путаницы: вы на самом деле не сказали, что код, который вы показали, действительно сработал в одном из ваших тестов "количество вещей> 2000". Если нет, то проблема заключается в коде обновления базы данных (который вы нам не показали).

Если это так, отключите:

(1) Снизить пределы с 2000 до разумных значений (около 20 для auth и ровно 7 для mydict)

(2) Когда произойдет отключение, print repr(mydict); print; print repr(auth) и проанализируйте содержимое по сравнению с вашим файлом.

В сторону: с помощью iterparse () elem.text НЕ гарантированно анализируется, когда происходит событие «start». Чтобы сэкономить время выполнения, вы должны обращаться к elem.text только тогда, когда происходит событие «end». На самом деле, кажется, нет никакой причины, почему вы вообще хотите «запускать» события. Также вы определяете список tags, но никогда не используете его. Начало вашей функции можно написать гораздо более кратко:

def fast_iter(context, cursor):
    mydict = {} # represents a paper with all its tags.
    auth = [] # a list of authors who have written the paper "together".
    counter = 0 # counts the papers
    tagset1 = set(['article', 'inproceedings', 'proceedings', 'book', 'incollection'])
    tagset2 = set(["title", "booktitle", "year", "journal"])
    for event, elem in context:
        tag = elem.tag
        if tag in tagset2:
            if elem.text:
                mydict[tag] = elem.text
        elif tag == "author":
            if elem.text:
                auth.append(elem.text)
        elif tag in tagset1:
            counter += 1
            print counter
            mydict["element"] = tag
            mydict["mdate"] = elem.get("mdate")
            mydict["dblpkey"] = elem.get("key")
            #populate_database(mydict, auth, cursor)
            mydict.clear() # Why not just do mydict = {} ??
            auth = []
            # etc etc

Не забудьте исправить вызов iterparse () для удаления аргумента событий.

Также я вполне уверен, что elem.clear () должен выполняться только тогда, когда событие имеет значение «end» , и должно выполняться только тогда, когда tag in tagset1. Внимательно прочитайте соответствующие документы . Выполнение очистки в «стартовом» событии вполне может повредить вашему дереву.

3 голосов
/ 17 мая 2011

Добавьте операторы печати в блоки кода, где вы обнаруживаете начало и конец тега в элементах, чтобы убедиться, что вы обнаруживаете их правильно.Я подозреваю, что по какой-то причине вы не получаете код, который очищает список авторов.

Попробуйте закомментировать этот код (или, по крайней мере, переместить его в блок обработки "конец"):

    elem.clear()
    while elem.getprevious() is not None:
        del elem.getparent()[0]

Python должен позаботиться о том, чтобы очистить эти элементы для вас, когда вы просматриваете XML.«Del context» также является излишним.Пусть справочные счетчики сделают всю работу за вас здесь.

...