Уведомления

Группа в Telegram: @pythonsu

#1 Дек. 13, 2018 19:48:57

Rafik
Зарегистрирован: 2018-09-04
Сообщения: 231
Репутация: +  27  -
Профиль   Отправить e-mail  

Перенос данных из excel в access, используя pandas

sevlovan, вечер добрый.
Если вся куча файлов всегда будет в одной и той же папке, то можно её жестко задать. Можно сделать немного хитрее, пусть скрипт может принимать хотя бы 1 аргумент - путь к папке с файлами xls. Если запуск без параметра, то будет искать в жестко заданной папке. Если совсем по-колхозному, то можно сваять что-то такое:

 import sys
import os 
from glob import glob
# Далее или ранее все остальные импорты. Расписывать не буду их.
if len(sys.argv) == 1:  # запущен скрипт без параметра
    # Зададим как сырую строку с префиксом r, чтобы не было проблем при работе скрипта
    zdes_xls = r'D:\ОБУЧЕНИЕ\py'  
else:
    # Полученный параметр приводим к виду, годному для дальнейшей работы с ним
    zdes_xls = os.path.normpath(sys.argv[1])
# итак, у нас есть путь к папке, используем glob для получения
# списка файлов. На основе списка организуем цикл.
for fl_name in glob(os.path.join(zdes_xls, '*.xls')):
    # Внутри цикла размещаем всё остальное, начиная с 
    with xlrd.open_workbook(fl_name) as wb:
    sh = wb.sheet_by_index(0)    
   #  Далее будет твой код как есть, только имена файлов csv и mdb надо будет продумать
   # к примеру, можно просто добавить в конец имени fl_name расширение '.csv' простой конкатенацией
   # Тогда имя файла будет примерно таким 'Имя_исходного_файла.xls.csv'
   # Можно с помощью os.path.splitext избавиться от первоначального расширения и присоединить 
   # новое расширение опять-таки конкатенацией. Имя fl_name - это  строка.  

Отредактировано Rafik (Дек. 13, 2018 19:52:29)

Офлайн

#2 Дек. 13, 2018 22:30:47

sevlovan
От: СПб
Зарегистрирован: 2018-12-05
Сообщения: 14
Репутация: +  0  -
Профиль   Отправить e-mail  

Перенос данных из excel в access, используя pandas

Rafik, в принципе все понятно, но зачем с именами файлов возиться… пишу и понимаю, что код создаёт таблицу на базе экселя.. соответственно с каждого экселя будет изготовлен csv и таблица в mdb, правильно? Можно конечно потом в access запросом прогрузить все одну таблицу, но согласись это не элегантно..)
Или я что то не так понял?

Офлайн

#3 Дек. 14, 2018 06:42:55

Rafik
Зарегистрирован: 2018-09-04
Сообщения: 231
Репутация: +  27  -
Профиль   Отправить e-mail  

Перенос данных из excel в access, используя pandas

В принципе, при каждой итерации можно организовать создание csv под одним и тем же именем. С него грузить данные в БД. Тогда точно не придётся возиться с именами файлов csv.
Кстати, вчера с устатку, проворонил одну важную вещь: соединение с БД надо сделать до цикла, а его закрытие вынести за цикл. При каждой итерации дергать БД - это нехорошо. Насколько я понял, ты сам догадался, что это нехорошо.
Возможно, имеет смысл вынести commit за цикл. Тогда запись БД на диск будет только один раз. Это может ускорить работу скрипта. Есть недостаток такого подхода - это потеря всех изменений при закрытии консоля во время работы скрипта. Если commit будет внутри цикла, то в этом случае потери будут только для текущего файла xls, который обрабатывался на момент прерывания работы скрипта.
Придётся подумать над именем БД: либо жёстко его задать в коде (это не совсем хорошо), либо передавать скрипту как второй параметр. В этом случае проверка входных параметров дополнится строками:

 if len(sys.argv) == 1:  # запущен скрипт без параметров
    zdes_xls = r'D:\ОБУЧЕНИЕ\py'  
    fl_mdb = r'фиксированное_имя.mdb'
else:  # Задан(ы) параметр(ы)
    zdes_xls = os.path.normpath(sys.argv[1])
    try:
        fl_mdb = os.path.normpath(sys.argv[2])
    except:
        fl_mdb = r'фиксированное_имя.mdb'
   

Офлайн

#4 Дек. 24, 2018 13:04:37

sevlovan
От: СПб
Зарегистрирован: 2018-12-05
Сообщения: 14
Репутация: +  0  -
Профиль   Отправить e-mail  

Перенос данных из excel в access, используя pandas

Rafik, приветствую!

Я вернулся к “нашим баранам”, точнее к моим, которых я отчаянно пытаюсь навязать..

код я насколько смог допилил. схема стандартная - работает но не по замыслу, берет из папки токмо один файл, его в csv и в mdb.

 import xlrd
import csv
import os
import pyodbc
import sys
import os
from glob import glob
#"соединение с БД надо сделать до цикла"
access_path = "D:\ОБУЧЕНИЕ\py\\svod.mdb"
con = pyodbc.connect("DRIVER={{Microsoft Access Driver (*.mdb, *.accdb)}};DBQ={};" \
                     .format(access_path))
if len(sys.argv) == 1:  # запущен скрипт без параметра
    # Зададим как сырую строку с префиксом r, чтобы не было проблем при работе скрипта
    zdes_xls = r'D:\ОБУЧЕНИЕ\py\do_cat'
    fl_mdb = r'D:\ОБУЧЕНИЕ\py\svod.mdb'
else:
    # Полученный параметр приводим к виду, годному для дальнейшей работы с ним
    zdes_xls = os.path.normpath(sys.argv[1])
    # Итак, у нас есть путь к папке, используем glob для получения списка файлов. На основе списка организуем цикл.
for fl_name in glob(os.path.join(zdes_xls, '*.xls')):
    # Внутри цикла размещаем всё остальное, начиная с
    with xlrd.open_workbook(fl_name) as wb:
        sh = wb.sheet_by_index(0)
   #  Далее будет твой код как есть, только имена файлов csv и mdb надо будет продумать
        with open('gtt.csv', 'w', newline="") as f:
            c = csv.writer(f)
            for r in range(sh.nrows):
                c.writerow(sh.row_values(r))
        with open('D:\ОБУЧЕНИЕ\py\gtt.csv', 'r') as reader, open('D:\ОБУЧЕНИЕ\py\gtt_clean.csv', 'w') as writer:
            read_csv = csv.reader(reader);
            write_csv = csv.writer(writer, lineterminator='\n')
            for line in read_csv:
                if len(line[1]) > 0:
                    write_csv.writerow(line)
strSQL = "SELECT * INTO [set_s] FROM [text;HDR=Yes;FMT=Delimited(,);" + \
                 "Database=D:\ОБУЧЕНИЕ\py].gtt_clean.csv;"
   # к примеру, можно просто добавить в конец имени fl_name расширение '.csv' простой конкатенацией
   # Тогда имя файла будет примерно таким 'Имя_исходного_файла.xls.csv'
   # Можно с помощью os.path.splitext избавиться от первоначального расширения и присоединить
   # новое расширение опять-таки конкатенацией. Имя fl_name - это  строка.
cur = con.cursor()
cur.execute(strSQL)
con.commit()

понимаю, что не все в порядке с вложенностью в цикле и мало что из этого я учел:

В принципе, при каждой итерации можно организовать создание csv под одним и тем же именем. С него грузить данные в БД. Тогда точно не придётся возиться с именами файлов csv.
Кстати, вчера с устатку, проворонил одну важную вещь: соединение с БД надо сделать до цикла, а его закрытие вынести за цикл. При каждой итерации дергать БД - это нехорошо. Насколько я понял, ты сам догадался, что это нехорошо.
Возможно, имеет смысл вынести commit за цикл. Тогда запись БД на диск будет только один раз. Это может ускорить работу скрипта. Есть недостаток такого подхода - это потеря всех изменений при закрытии консоля во время работы скрипта. Если commit будет внутри цикла, то в этом случае потери будут только для текущего файла xls, который обрабатывался на момент прерывания работы скрипта.
Придётся подумать над именем БД: либо жёстко его задать в коде (это не совсем хорошо), либо передавать скрипту как второй параметр. В этом случае проверка входных параметров дополнится строками:

но это все на что меня хватило …

Офлайн

#5 Дек. 24, 2018 20:19:16

Rafik
Зарегистрирован: 2018-09-04
Сообщения: 231
Репутация: +  27  -
Профиль   Отправить e-mail  

Перенос данных из excel в access, используя pandas

Есть несколько ошибок:
а) строка начинающаяся на strSQL = "SELECT * вынесена за цикл, а не перед циклом и не внутри цикла. Если имя файла для импорта в БД неизменно, то строку запроса нужно формировать до цикла обхода файлов. Если в процессе обхода будет меняться имя файла для импорта в БД, то внутри цикла формировать команду sql. Для твоего текущего кода можно задать строку strSQL до цикла;
б) курсор тоже надо задать до цикла, т.е. строку cur = con.cursor() поместить до цикла.

Теперь самое главное. Весь секрет фокуса в том, что запуск sql

 cur.execute(strSQL)
надо поместить внутрь цикла, после завершения инструкций with. Сейчас, пока строка запуска sql находится за циклом, в БД попадает только последний файл из всего списка, т.к. занесение данных в БД идёт только после завершения цикла. Команду commit имеет смысл оставить за циклом, чтобы при каждой итерации не дергать БД для записи на диск.

По аналогии с текстовым редактором:
связка команд connect(), strSQL = “что-там” и con.cursor() предположим, эквивалетно открытию текстового файла. Тогда твой код внутри цикла - это что-то типа “скопировать что-то в буфер обмена”,
твоя команда cur.execute(strSQL) выполняет что-то типа “вставить текст из буфера обмена”, а команда commit - аналог кнопки save текстового редактора.
Если следовать этой аналогии, то, пока, у тебя получается как много раз загоняем в буфер обмена разные фрагменты, а вставка делается только один раз, в самом конце. В итоге получаем в редакторе только последний фрагмент из буфера обмена, а не все фрагменты, которые копировали в буфер.

Офлайн

#6 Дек. 25, 2018 10:40:47

sevlovan
От: СПб
Зарегистрирован: 2018-12-05
Сообщения: 14
Репутация: +  0  -
Профиль   Отправить e-mail  

Перенос данных из excel в access, используя pandas

Rafik, спасибо за разъяснения! буду разбираться..

Офлайн

#7 Дек. 28, 2018 16:21:14

sevlovan
От: СПб
Зарегистрирован: 2018-12-05
Сообщения: 14
Репутация: +  0  -
Профиль   Отправить e-mail  

Перенос данных из excel в access, используя pandas

Rafik, привет!
Поразбирался.. код теперь в таком виде:

 access_path = "D:\ОБУЧЕНИЕ\py\\svod.mdb"
con = pyodbc.connect("DRIVER={{Microsoft Access Driver (*.mdb, *.accdb)}};DBQ={};" \
                     .format(access_path))
if len(sys.argv) == 1:
    zdes_xls = r'D:\ОБУЧЕНИЕ\py\do_cat'
    fl_mdb = r'D:\ОБУЧЕНИЕ\py\svod.mdb'
else:
    zdes_xls = os.path.normpath(sys.argv[1])
strSQL = "SELECT * INTO [set_s] FROM [text;HDR=Yes;FMT=Delimited(,);" + \
                     "Database=D:\ОБУЧЕНИЕ\py].gtt_clean.csv;"
cur = con.cursor()
for fl_name in glob(os.path.join(zdes_xls, '*.xls')):
    with xlrd.open_workbook(fl_name) as wb:
        sh = wb.sheet_by_index(0)
        with open('gtt.csv', 'w', newline="") as f:
            c = csv.writer(f)
            for r in range(sh.nrows):
                c.writerow(sh.row_values(r))
        with open('D:\ОБУЧЕНИЕ\py\gtt.csv', 'r') as reader, open('D:\ОБУЧЕНИЕ\py\gtt_clean.csv', 'w') as writer:
            read_csv = csv.reader(reader);
            write_csv = csv.writer(writer, lineterminator='\n')
            for line in read_csv:
                if len(line[1]) > 0:
                    write_csv.writerow(line)
    cur.execute(strSQL)
con.commit()

со strSQL и cursor проблем вроде не возникло, перенес их перед циклом..

С cur.execute(strSQL) все сложно: при ее постановке как в указанном выше примере выдается ошибка:
     cur.execute(strSQL)
pyodbc.ProgrammingError: ('42S01', "[42S01] [Microsoft][Драйвер ODBC Microsoft Access] Таблица 'set_s' уже существует. (-1303) (SQLExecDirectW)")

если ставлю так:
 for fl_name in glob(os.path.join(zdes_xls, '*.xls')):
    with xlrd.open_workbook(fl_name) as wb:
        sh = wb.sheet_by_index(0)
        with open('gtt.csv', 'w', newline="") as f:
            c = csv.writer(f)
            for r in range(sh.nrows):
                c.writerow(sh.row_values(r))
        with open('D:\ОБУЧЕНИЕ\py\gtt.csv', 'r') as reader, open('D:\ОБУЧЕНИЕ\py\gtt_clean.csv', 'w') as writer:
            read_csv = csv.reader(reader);
            write_csv = csv.writer(writer, lineterminator='\n')
            for line in read_csv:
                if len(line[1]) > 0:
                    write_csv.writerow(line)
                    cur.execute(strSQL)
то ошибка:
 pyodbc.Error: ('HY000', '[HY000] [Microsoft][Драйвер ODBC Microsoft Access] Ядро СУБД Microsoft Access более не может открыть файл "|" или записать в него данные.  Файл уже открыт другим пользователем для монопольного доступа, либо требуется разрешение на просмотр и запись данных. (-1032) (SQLExecDirectW)')

скромно предположу, что в первом случае экзекут стоит верно, но ему мешает некорректно составленная команда
 strSQL = "SELECT * INTO [set_s] FROM
которая предполагает создание таблицы, вот он и пишет, что она дескать уже имеется..
то есть должно быть, вероятно INSERT INTO VALUES (но тогда ее-таблицу надо предварительно создать) или чтобы в буфер обмена все из разных файлов собралось и в создаваемую таблицу записалось…

Отредактировано sevlovan (Дек. 28, 2018 19:45:46)

Офлайн

#8 Дек. 28, 2018 20:20:17

Rafik
Зарегистрирован: 2018-09-04
Сообщения: 231
Репутация: +  27  -
Профиль   Отправить e-mail  

Перенос данных из excel в access, используя pandas

К сожалению у меня нет на работе MS office, из офисных пакетов стоит только либреофис. Дома под линем тоже только либре. Поэтому ничего путного про sql строку для access сказать не могу.
Однако, почему бы отдельной командой не проверить имеется ли таблица set_s и, если её нет, создать? Это наверняка можно сделать соответствующими командами sql. Далее вставлять в уже имеющуюся таблицу данные командой insert into…
Проверка наличия таблицы в sqlite можно сделать так: “SELECT name FROM sqlite_master WHERE type='table'” - получаем список всех имеющихся в БД таблиц, далее проверим имя таблицы на вхождение в список и всего-то делов. Для создания новой таблицы в sqlite имеется команда create. Наверняка и в access тоже есть нечто подобное.

Хочу предложить немного переделать скрипт, “отделить мух от котлет”. Необходимо сбор данных выделить в отдельную функцию, чтобы:
а) не “загрязнял” основной код своим присутствием ;
б) в дальнейшем, если понадобится получение данных из других источников типа xlsx, doc, docx, ods или odt или разных комбинаций перечисленных , то можно легко найти что править, куда добавить дополнительные возможности не трогая основной код.
Предлагаю такой вариант:

 # -*- coding: UTF-8 -*-
import xlrd
import csv
import os
import pyodbc
import sys
import os
from glob import glob
def sbor_dannix(path_to_xls, fl_rezult):
    """ Соберём данные из всех файлов в один файл заданный параметром fl_rezult
    результирующий файл и промежуточные результаты будем писать туда же,
    где находятся сами xls """
    writer = open(os.path.join(path_to_xls, fl_rezult), 'w')  # для сбора данных
    # Пройдёмся по всем xls.
    for fl_name in glob(os.path.join(path_to_xls, '*.xls')):
        with xlrd.open_workbook(fl_name) as wb:
            sh = wb.sheet_by_index(0)
            with open(os.path.join(path_to_xls, 'gtt.csv'), 'w', newline="") as f:
                c = csv.writer(f)
                for r in range(sh.nrows):
                    c.writerow(sh.row_values(r))
            with open(os.path.join(path_to_xls, 'gtt.csv'), 'r') as reader:
                read_csv = csv.reader(reader)
                write_csv = csv.writer(writer, lineterminator='\n')
                for line in read_csv:
                    if len(line[1]) > 0:
                        write_csv.writerow(line)
    writer.close()  # Закроем собранные данные
# Проверка входных параметров скрипта
try:
    # Есть ли первый параметр ?
    zdes_xls = os.path.normpath(sys.argv[1])
    # На всякий случай проверим путь к файлам xls
    if not os.path.exists(zdes_xls):
        print("Путь '{}' НЕ существует!".format(zdes_xls))
        exit(-1)
    # path_for_select = sys.argv[1]
except IndexError:
    # нет параметра
    zdes_xls = r'D:\ОБУЧЕНИЕ\py\do_cat'
try:
    # Есть ли второй параметр ?
    access_path = os.path.normpath(sys.argv[2])
    if not os.path.exists(access_path):  # проверим есть ли файл БД
        print("БД '{}' НЕ найдена!".format(access_path))
        exit(-1)
    # access_path = sys.argv[2]
except IndexError:
    # нет
    access_path = r"D:\ОБУЧЕНИЕ\py\svod.mdb"
kuda_sobiraem = 'gtt_clean.csv'  # имя файла для сбора данных
# Сбор данных уберём отсюда в функцию sbor_dannix, чтобы не мельтешил здесь.
sbor_dannix(zdes_xls, kuda_sobiraem)  # вызов сбора данных
# Данные собраны. Теперь можно подключаться к БД
# и одним махом записать всё что смогли собрать.
# ---
# Необходимо проверить с двумя параметрами.
# скушает ли connect путь к БД после нормализации или подавится?
# Если не примет, то попробовать раскоментировать строку access_path = sys.argv[2]
con = pyodbc.connect("DRIVER={{Microsoft Access Driver (*.mdb, *.accdb)}};DBQ={};".format(access_path))
# внимательно просмотреть строку sql.
# Нужно ли заключать имя файла в квадратные скобки [gtt_clean.csv] ?
# если не надо, то .[{}]; привести к виду .{};
# Проверить: примет ли select путь zdes_xls при запуске с параметром - путь к xls
# Если не примет, то попробовать раскоментировать path_for_select = sys.argv[2]
# и заменить zdes_xls на path_for_select
strSQL = "SELECT * INTO [set_s] FROM [text;HDR=Yes;FMT=Delimited(,);" + \
                 "Database={}].[{}];".format(zdes_xls, kuda_sobiraem)
cur = con.cursor()
cur.execute(strSQL)
con.commit()
Насчёт sql команд просмотри документацию по access.

Офлайн

#9 Дек. 28, 2018 21:59:28

sevlovan
От: СПб
Зарегистрирован: 2018-12-05
Сообщения: 14
Репутация: +  0  -
Профиль   Отправить e-mail  

Перенос данных из excel в access, используя pandas

Наличие живых примеров безусловно радует.
Но это не значит, что я не пытаюсь вникнуть в теорию. Это тоже весьма кстати.

Отредактировано sevlovan (Дек. 28, 2018 21:59:55)

Офлайн

#10 Дек. 29, 2018 07:32:47

Rafik
Зарегистрирован: 2018-09-04
Сообщения: 231
Репутация: +  27  -
Профиль   Отправить e-mail  

Перенос данных из excel в access, используя pandas

Можно сделать так: перед добавлением данных в БД сделать попытку создания таблицы. Создание таблицы завернуть в try и далее сделать insert into. Если таблица уже есть, то выкинет ошибку, которую мы “обработаем” и код будет работать дальше.
Примерно по такому шаблону:

 zapros = "INSERT INTO бла-бла-бла"
try:
    cur.execute("""CREATE TABLE Table1 (
                     ID autoincrement,
                     Col1 varchar(50),
                     Col2 double,
                     Col3 datetime);""")
except:
    print("Таблица уже есть и готова принять данные")
# этот фрагмент вставить после того, как данные будут готовы к вставке в БД
cur.execute(zapros)
con.commit()

Отредактировано Rafik (Дек. 29, 2018 07:38:01)

Офлайн

Board footer

Модераторировать

Powered by DjangoBB

Lo-Fi Version