Читать большой XML-файл и извлечение необходимых элементов в MySQLdb


У меня есть справедливое понятие в Программирование (учусь), но не знаток программного кода на самом высоком уровне. Я пытаюсь читать Большой (100 МБ-2 ГБ) файл XML и парсить нужный элемент (атрибуты) из файла в MySQLdb. Код вполне рабочий, как я испытал в небольших размерах файла. Но, к сожалению, когда я получил большой размер сегодня (400мб-900Мб), он принимает неожиданное время.

В настоящее время, я ныряю в список понимания, генератор(), лямбда() и список методов сокращения.

Чтобы не сделать больше строк кода, но я исключил 3 или 4 элементов из моего списка (тегов) отсюда. Но, структура кода для остальных исключены элементы одинаковые.

Я использую: в Python 2.7, как lxml, в MySQL (1.2.3)

import os, sys
import stat
import getpass
import MySQLdb
from lxml import etree
import datetime, time
import dbconfig as config

# All global variables set
 #Namespace which is default in every mzML file
#taglist=['mzML','sourceFile','software','dataProcessing','instrumentConfiguration','selectedIon']
taglist=['mzML','sourceFile']
NS="{http://psi.hupo.org/ms/mzml}"

def fast_iter(context, func,args=[],kwargs={}):
    # fast_iter is useful if we need to free memory while iterating through a
    # very large XML file.
    #http://www.ibm.com/developerworks/xml/library/x-hiperfparse/
    # Author: Liza Daly

    for event, elem in context:
        func(elem,*args, **kwargs)
        elem.clear()
        while elem.getprevious() is not None:
            del elem.getparent()[0]
    del context

def process_element(elt):
    # global mzml_pk, sf_cv_fk,softw_fk_ID, softw_cv_fk_ID
    # Processing first element (mzML) and it's attributes (id, version, accession)
    # version attribute is required AND id & accession are optional
    if elt.tag==NS+taglist[0]:

        L= elt.keys()
        L1=['id','version','accession']

        # Checking whether element attributes (items L1) exist in a mzml element attributes (L)
        if L1[0] in L:
            mzmlID = elt.attrib['id']
        else:
            mzmlID = "-"
        if L1[1] in L:
            mzml_version = elt.attrib['version']
        else:
            mzml_version = "-"
        if L1[2] in L:
            mzml_accession = elt.attrib['accession']
        else:
            mzml_accession = "-"

        exp_id_fk = exp_PK

        sql = """INSERT INTO pmass_mzml (mzml_id,accession,version,exp_id)
        VALUES (%s, %s, %s, %s)"""
        try:
            # Execute the SQL command
            config.cursor.execute(sql,(mzmlID,mzml_accession,mzml_version,exp_id_fk))
            # Commit your changes in the database
            config.conn.commit()

            #mzml_pk= cursor.lastrowid
        except Exception as err:
            # logger.error(err)
            # Rollback in case there is any error
            config.conn.rollback()
        global mzml_pk
        mzml_pk = config.cursor.lastrowid
    # Processing second element (sourceFile) which has attributes (id, name and location)
    # SourceFile attributes id, name and location are required
    # Further, sourceFile has child element (cvParam) and attributes (cvRef, name, accession, value)
    # cvParam attributes: cvRef, name, accession are Required and value optional

    elif elt.tag==NS+taglist[1]:
        sf_keys = elt.keys()
        sf_need = ['id','name','location']
        if sf_need[0] in sf_keys:
            sf_id = elt.attrib['id']
        else:
            sf_id, "-"
        if sf_need[1] in sf_keys:
            sf_name = elt.attrib['name']
        else:
           sf_name, "-"
        if sf_need[2] in sf_keys:
            sf_location = elt.attrib['location']
        else:
            sf_location = "-"
        global sf_fk
        sf_fk = mzml_pk
        #print "Insert values into django sourceFile class data model"

        sql = """INSERT INTO pmass_source_file (sf_id,name,location,mzml_fk_id)
        VALUES (%s, %s, %s, %s)"""
        try:
            # Execute the SQL command
            config.cursor.execute(sql,(sf_id,sf_name,sf_location,sf_fk))
            # Commit your changes in the database
            config.conn.commit()

        except Exception as err:
            # logger.error(err)
            # Rollback in case there is any error
            config.conn.rollback()
        #sf_pk = cursor.lastrowid
        global sf_cv_fk
        sf_cv_fk= config.cursor.lastrowid

        for child in elt.getchildren():
            sf_child_keys =child.keys()
            sf_child_need = ['cvRef','name','accession','value']

            if sf_child_need[0] in sf_child_keys:
                sf_cv_cvref = child.attrib['cvRef']
            else:
                sf_cv_cvref = "-"
            if sf_child_need[1] in sf_child_keys:
                sf_cv_name = child.attrib['name']
            else:
                sf_cv_name = "-"
            if sf_child_need[2] in sf_child_keys:
                sf_cv_accession = child.attrib['accession']
            else:
                sf_cv_accession = "-"
            if sf_child_need[3] in sf_child_keys :
                if len(child.get('value'))>0:
                    sf_cv_value =  child.attrib['value']
                else:
                    sf_cv_value = "-"
            else:
                sf_cv_value = "-"

            sql = """INSERT INTO pmass_source_file_cv (ref, accession,name,value,sf_fk_id)
                    VALUES (%s, %s, %s, %s, %s)"""
            try:
                # Execute the SQL command
                config.cursor.execute(sql,(sf_cv_cvref,sf_cv_accession,sf_cv_name,sf_cv_value,sf_cv_fk))
                # Commit your changes in the database
                config.conn.commit()
            except Exception as err:
                # logger.error(err)
                # Rollback in case there is any error
                config.conn.rollback()

Я не клеил остальная часть кода для других элементов, потому что это больше, чем 300 линий, но одной и той же концепции.

def convert_bytes(bytes):
    bytes = float(bytes)
    if bytes >= 1099511627776:
        terabytes = bytes / 1099511627776
        size = '%.2fTB' % terabytes
    elif bytes >= 1073741824:
        gigabytes = bytes / 1073741824
        size = '%.2fGB' % gigabytes
    elif bytes >= 1048576:
        megabytes = bytes / 1048576
        size = '%.2fMB' % megabytes
    elif bytes >= 1024:
        kilobytes = bytes / 1024
        size = '%.2fKB' % kilobytes
    else:
        size = '%.2fb' % bytes
    return size


def main():
    global EXP_PK
    # Start time counter
    begin=time.clock()

    # Reading file from the configured directory
    file_dir = r'D:\files\111102_CA2.mzML'

    # Reactor so it will read all new files from configured directory
    file_name = os.path.basename(file_dir)

    # File name and extension
    (name, ext) = os.path.splitext(file_name)
    user= getpass.getuser()
    exp_name = name
    exp_type= ext[1:]
    exp_created_time = time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(os.path.getctime(file_dir)))
    exp_modi_time = time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(os.path.getmtime(file_dir)))
    exp_uploaddate= datetime.date.today()
    exp_uptime = str(datetime.datetime.now())[10:19]
    exp_size = convert_bytes(os.path.getsize(file_dir))
    #exp_located = r'C:\Users\Thaman\Documents\My Dropbox\Files\small1.mzML'
    exp_located = r'C:\My Documents\Dropbox\Files\plgs_example.mzML'
    #exp_located =r'D:\files\111102_CA2.mzML'

    sql = """INSERT INTO pmass_experiment (user,filename,filetype,createddate,modifieddate,uploaddate,time,size,located)
         VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)"""
    try:
        # Execute the SQL command
        config.cursor.execute(sql,(user,exp_name,exp_type,exp_created_time,exp_modi_time,exp_uploaddate,exp_uptime,exp_size,exp_located))
        # Commit your changes in the database
        config.conn.commit()
    except:
        #logger.error(err)
        # Rollback in case there is any error
        config.conn.rollback()
    global exp_PK
    exp_PK = config.cursor.lastrowid
    #Reading elements from file
    for intag in range(len(taglist)):
        context= etree.iterparse(file_dir,events=("end",),tag=NS+taglist[intag])
        fast_iter(context, process_element)

    tend= time.clock()
    print "\n\nTIME TAKEN TO COMPLETE JOB", tend-begin

if __name__ == '__main__':
    main()

Проблема для меня:

  • Я уверен, что мой подход к использованию слишком много если и для выражения проблемы, но не находят решение.
  • Файл 400 МБ еще и с более чем 1 час.

Обзор: данные, память, дружелюбный, скорость оптимизации структуры

Если кому-то нужен файл, я могу поделиться его на Dropbox.



3567
3
задан 3 ноября 2011 в 02:11 Источник Поделиться
Комментарии
1 ответ

import os, sys
import stat
import getpass
import MySQLdb
from lxml import etree
import datetime, time
import dbconfig as config

Они на Python стиль руководства рекомендует один импорт на линию. Я бы тоже, наверное, группа все вместе, т. е. стандартный импорт библиотеки 3-го импорт партии/ после импорта приложения. Но это не важно

# All global variables set
#Namespace which is default in every mzML file

taglist=['mzML','sourceFile']
NS="{http://psi.hupo.org/ms/mzml}"

Для глобальных констант, руководство по стилю Python рекомендует, что написано заглавными_буквами. Я рекомендую не использовать НС, а не буквам имен

def fast_iter(context, func,args=[],kwargs={}):

Принимая аргументы как в виде списка, и дикт странный, какой-либо причине вы не принимали их как *args и **kwargs?

    # fast_iter is useful if we need to free memory while iterating through a
# very large XML file.
#http://www.ibm.com/developerworks/xml/library/x-hiperfparse/
# Author: Liza Daly

for event, elem in context:
func(elem,*args, **kwargs)

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

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

Нет смысла удалять локальные имена в конце функции. Когда функция завершает все имена будут удалены в любом случае.

def process_element(elt):

Я рекомендую не использовать короткие аббревиатуры, такие как ЭЛТ, если они действительно общие.

    # global mzml_pk, sf_cv_fk,softw_fk_ID, softw_cv_fk_ID
# Processing first element (mzML) and it's attributes (id, version, accession)
# version attribute is required AND id & accession are optional
if elt.tag==NS+taglist[0]:

L= elt.keys()
L1=['id','version','accession']

# Checking whether element attributes (items L1) exist in a mzml element attributes (L)
if L1[0] in L:

Вы ничего не помогли хранение 'идентификатор' Л1[0], а затем снова ее носить. Просто использовать 'идентификатор' здесь.

            mzmlID = elt.attrib['id']
else:
mzmlID = "-"

Вы должны быть в состоянии сделать что-то вроде mzmlID = Эл.введите attrib.получить('идентификатор', '-') заменить весь, Если/блок else.

        if L1[1] in L:
mzml_version = elt.attrib['version']
else:
mzml_version = "-"
if L1[2] in L:
mzml_accession = elt.attrib['accession']
else:
mzml_accession = "-"

exp_id_fk = exp_PK

sql = """INSERT INTO pmass_mzml (mzml_id,accession,version,exp_id)
VALUES (%s, %s, %s, %s)"""
try:
# Execute the SQL command
config.cursor.execute(sql,(mzmlID,mzml_accession,mzml_version,exp_id_fk))
# Commit your changes in the database
config.conn.commit()

#mzml_pk= cursor.lastrowid

Не оставляйте мертвого кода в комментариях

        except Exception as err:
# logger.error(err)
# Rollback in case there is any error
config.conn.rollback()

Не просто игнорировать ошибки, которые происходят. Если вы ожидаете, произойдет ошибка, вы должны проверить, чтобы убедиться, что именно ошибки вы хотели, был пойман. В противном случае необходимо повторно поднять или хотя бы лог ошибки.

Вы также можете посмотреть с отчетами и менеджеры контекста. Среди прочего, они делают операции легче работать.

        global mzml_pk
mzml_pk = config.cursor.lastrowid

избегать использования глобальных переменных. Использовать возвращаемые значения/атрибуты объекта/почти все, что перед использованием глобальной переменной.

    # Processing second element (sourceFile) which has attributes (id, name and location)
# SourceFile attributes id, name and location are required
# Further, sourceFile has child element (cvParam) and attributes (cvRef, name, accession, value)
# cvParam attributes: cvRef, name, accession are Required and value optional

Сейчас, эта функция слишком долго. Его следует разбить на несколько функций.

    elif elt.tag==NS+taglist[1]:
sf_keys = elt.keys()
sf_need = ['id','name','location']
if sf_need[0] in sf_keys:
sf_id = elt.attrib['id']
else:
sf_id, "-"
if sf_need[1] in sf_keys:
sf_name = elt.attrib['name']
else:
sf_name, "-"
if sf_need[2] in sf_keys:
sf_location = elt.attrib['location']
else:
sf_location = "-"
global sf_fk
sf_fk = mzml_pk
#print "Insert values into django sourceFile class data model"

sql = """INSERT INTO pmass_source_file (sf_id,name,location,mzml_fk_id)
VALUES (%s, %s, %s, %s)"""
try:
# Execute the SQL command
config.cursor.execute(sql,(sf_id,sf_name,sf_location,sf_fk))
# Commit your changes in the database
config.conn.commit()

except Exception as err:
# logger.error(err)
# Rollback in case there is any error
config.conn.rollback()
#sf_pk = cursor.lastrowid
global sf_cv_fk
sf_cv_fk= config.cursor.lastrowid

Дежавю... вы должны увидеть, если вы можете выполнить рефакторинг simiairites между двумя очень похожие куски кода, как это.

        for child in elt.getchildren():
sf_child_keys =child.keys()
sf_child_need = ['cvRef','name','accession','value']

if sf_child_need[0] in sf_child_keys:
sf_cv_cvref = child.attrib['cvRef']
else:
sf_cv_cvref = "-"
if sf_child_need[1] in sf_child_keys:
sf_cv_name = child.attrib['name']
else:
sf_cv_name = "-"
if sf_child_need[2] in sf_child_keys:
sf_cv_accession = child.attrib['accession']
else:
sf_cv_accession = "-"
if sf_child_need[3] in sf_child_keys :
if len(child.get('value'))>0:
sf_cv_value = child.attrib['value']
else:
sf_cv_value = "-"
else:
sf_cv_value = "-"

sql = """INSERT INTO pmass_source_file_cv (ref, accession,name,value,sf_fk_id)
VALUES (%s, %s, %s, %s, %s)"""
try:
# Execute the SQL command
config.cursor.execute(sql,(sf_cv_cvref,sf_cv_accession,sf_cv_name,sf_cv_value,sf_cv_fk))
# Commit your changes in the database
config.conn.commit()
except Exception as err:
# logger.error(err)
# Rollback in case there is any error
config.conn.rollback()

# **Rest of the codes I have pasted because it's more then 300 lines but same concept reading other element**

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

def convert_bytes(bytes):

Эта функция не преобразует байт, формата количество байтов. Название могло быть лучше.

    bytes = float(bytes)

Почему вам преобразовывать байты на поплавок? Я предполагаю, что у вас нет дробными

    if bytes >= 1099511627776:
terabytes = bytes / 1099511627776
size = '%.2fTB' % terabytes
elif bytes >= 1073741824:
gigabytes = bytes / 1073741824
size = '%.2fGB' % gigabytes
elif bytes >= 1048576:
megabytes = bytes / 1048576
size = '%.2fMB' % megabytes
elif bytes >= 1024:
kilobytes = bytes / 1024
size = '%.2fKB' % kilobytes
else:
size = '%.2fb' % bytes
return size

Код повторяет ту же мысль несколько раз, я буду использовать данные на основе подхода

UNITS = [ 
(1024**4, 'TB'),
(1024**3, 'GB'),
(1024**2, 'MB'),
(1024**1, 'KB'),
(1024**0, 'b')
]

def convert_bytes(bytes):
for size, unit in UNITS:
if bytes > size:
return '%d%s' % (bytes / size, unit)

def main():
global EXP_PK
# Start time counter
begin=time.clock()

# Reading file from the configured directory
file_dir = r'D:\files\111102_CA2.mzML'

Это не каталог...

    # Reactor so it will read all new files from configured directory
file_name = os.path.basename(file_dir)

Реактор? Вы имели в виду рефакторинг? Потому что это не то, что это значит. Действительно, я не могу разобраться ваш комментарий

    # File name and extension
(name, ext) = os.path.splitext(file_name)
user= getpass.getuser()
exp_name = name
exp_type= ext[1:]

Комментарий, объясняющий почему вы не снимая первый символ расширения может быть приятно.

    exp_created_time = time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(os.path.getctime(file_dir)))
exp_modi_time = time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(os.path.getmtime(file_dir)))
exp_uploaddate= datetime.date.today()
exp_uptime = str(datetime.datetime.now())[10:19]
exp_size = convert_bytes(os.path.getsize(file_dir))
#exp_located = r'C:\Users\Thaman\Documents\My Dropbox\Files\small1.mzML'
exp_located = r'C:\My Documents\Dropbox\Files\plgs_example.mzML'
#exp_located =r'D:\files\111102_CA2.mzML'

sql = """INSERT INTO pmass_experiment (user,filename,filetype,createddate,modifieddate,uploaddate,time,size,located)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)"""
try:
# Execute the SQL command
config.cursor.execute(sql,(user,exp_name,exp_type,exp_created_time,exp_modi_time,exp_uploaddate,exp_uptime,exp_size,exp_located))
# Commit your changes in the database
config.conn.commit()
except:
#logger.error(err)
# Rollback in case there is any error
config.conn.rollback()
global exp_PK
exp_PK = config.cursor.lastrowid

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

    #Reading elements from file
for intag in range(len(taglist)):

Просто использовать для тегов в тегов: вы не используете индекс, поэтому нет никаких причин, чтобы использовать диапазон

        context= etree.iterparse(file_dir,events=("end",),tag=NS+taglist[intag])
fast_iter(context, process_element)

tend= time.clock()
print "\n\nTIME TAKEN TO COMPLETE JOB", tend-begin

if __name__ == '__main__':
main()

Редактировать

Вы должны сделать что-то подобное для того, чтобы обрабатывать различные типы тегов. Это будет быстрее и понятнее то, что вы делаете.

def process_foo_tag(element):
fetch element from foo
insert into database

def process_bar_tag(element):
fetch elements for bar tag
insert into database

TAGS = {
'bar' : process_bar_tag,
'foo' : process_foo_tag
}

def process_element(element):
TAGS[element.tag](element)

3
ответ дан 3 ноября 2011 в 03:11 Источник Поделиться