Форум сайта python.su
27
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)
Офлайн
0
Rafik, в принципе все понятно, но зачем с именами файлов возиться… пишу и понимаю, что код создаёт таблицу на базе экселя.. соответственно с каждого экселя будет изготовлен csv и таблица в mdb, правильно? Можно конечно потом в access запросом прогрузить все одну таблицу, но согласись это не элегантно..)
Или я что то не так понял?
Офлайн
27
В принципе, при каждой итерации можно организовать создание 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'
Офлайн
0
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, который обрабатывался на момент прерывания работы скрипта.
Придётся подумать над именем БД: либо жёстко его задать в коде (это не совсем хорошо), либо передавать скрипту как второй параметр. В этом случае проверка входных параметров дополнится строками:
Офлайн
27
Есть несколько ошибок:
а) строка начинающаяся на strSQL = "SELECT * вынесена за цикл, а не перед циклом и не внутри цикла. Если имя файла для импорта в БД неизменно, то строку запроса нужно формировать до цикла обхода файлов. Если в процессе обхода будет меняться имя файла для импорта в БД, то внутри цикла формировать команду sql. Для твоего текущего кода можно задать строку strSQL до цикла;
б) курсор тоже надо задать до цикла, т.е. строку cur = con.cursor() поместить до цикла.
Теперь самое главное. Весь секрет фокуса в том, что запуск sql
cur.execute(strSQL)
Офлайн
0
Rafik, спасибо за разъяснения! буду разбираться..
Офлайн
0
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()
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
Отредактировано sevlovan (Дек. 28, 2018 19:45:46)
Офлайн
27
К сожалению у меня нет на работе 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()
Офлайн
0
Наличие живых примеров безусловно радует.
Но это не значит, что я не пытаюсь вникнуть в теорию. Это тоже весьма кстати.
Отредактировано sevlovan (Дек. 28, 2018 21:59:55)
Офлайн
27
Можно сделать так: перед добавлением данных в БД сделать попытку создания таблицы. Создание таблицы завернуть в 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)
Офлайн