Примечание. Как и первая часть эта тоже для совсем маленьких кодеров-велосипедостроителей на Питоне. Для прожженных кодеров будет скучно. Изначально хотели внести исправления сразу в первую статью по мере нахождения ошибок, но после некоторого раздумия решили, что это неудобно. Ошибки исчезнут совсем, а именно ошибки приносят максимальную пользу для начинающего кодера. А посему ошибки оставляем в первой части, а в этой начинаем от них избавляться.
Начало
Сначала создадим в основном проекте новую папку, куда будем складывать весь код из этой статьи. Заходим в командную строку виндовс под админом:
D:\2021_8_16_oborot>mkdir part2
Далее идем по порядку. Мы хотели проверить соответствуют ли ВСЕ
исходные xml-файлы своему xsd описанию.
Проверяем все исходные xml файлы
Что делаем? Сравниваем все файлы из исходной директории с xml-файлами (…/ish_unziped) с xsd схемой. Если все гуд записываем его имя в текстовый файл для хороших файлов, иначе к плохим.
#D:\2021_8_16_oborot\part2\14.09.2021_check_all_xml_files_in_dir.py import lxml from lxml import etree import os import xml.etree.ElementTree as Xet def s_errors(): path = 'D:/2021_8_16_oborot/ish_unziped/' filelist = os.listdir(path) enu = 0 mis = 0 with open('D:/2021_8_16_oborot/part2/xml_not_valid_errors_2.txt', 'a+', encoding='utf-8') as ef: with open('D:/2021_8_16_oborot/part2/xml_is_valid_2.txt', 'a+', encoding='utf-8') as va_f: for t in filelist: enu = enu + 1 with open('D:/2021_8_16_oborot/ish_unziped/' + t, 'r', encoding='utf-8') as ft: xml_file = lxml.etree.parse(ft) xml_validator = lxml.etree.XMLSchema(file='D:/2021_8_16_oborot/structure-20180110.xsd') is_valid = xml_validator.validate(xml_file) if is_valid == True: enu1 = str(enu) va_f.write(enu1) va_f.write(':::') va_f.write(t) va_f.write('|') va_f.write('file_matches_xsd') va_f.write('\n') else: enu1 = str(enu) mis = mis + 1 mis_str = str(mis) ef.write(enu1) ef.write(':::') ef.write(t) ef.write('the_file_does_not_match_the_xsd_schema') ef.write('\n') if __name__ == '__main__': s_errors()
Запустили. Получаем результат: «плохой» файл пустой, в хорошем все 12060 +1 файл (один тестовый файл мы же добавляли!!!), итого 12061 гуд файл.
Вид записей такой (последние 5 строчек)
12057:::VO_OTKRDAN5_9965_9965_20210729_ffdf4a61-dc58-451e-8c76-46d63b534694.xml|file_matches_xsd 12058:::VO_OTKRDAN5_9965_9965_20210729_ffe07802-f596-46a2-aff3-24293c5162c0.xml|file_matches_xsd 12059:::VO_OTKRDAN5_9965_9965_20210729_ffe103cb-571e-4c05-9395-6d97814e6100.xml|file_matches_xsd 12060:::VO_OTKRDAN5_9965_9965_20210729_ffe3f7a0-c0db-4c6a-a231-98a429c51314.xml|file_matches_xsd 12061:::VO_OTKRDAN5_9965_9965_20210729_ffec8ca1-4e5d-42ea-ad3e-2ac45146da0d.xml|file_matches_xsd
Примечание. Как оказалось позже этот метод проверки не защищает от наличия «битых» данных, которые могут испортить в дальнейшем нормальный импорт в csv и далее в таблицу sqlite.
Пишем импорт xml в csv и далее в sqlite на Питоне
В первой части мы сильно торопились и не стали выяснять что мы там не то навелосипедили в Питоне во время попытки импорта csv в sqlite, а просто ‘перепрыгнули’ дальше к финишу. Исправляемся. В качестве разделителя используем символ пайп-pipe (который в свое время Фигурнов перевел, как ‘символ водопровода’): "|".
Подумали (как позже оказалось зря), что вряд-ли такой символ может встретиться в исходных данных. Как говорится «никогда не говори ‘никогда’. По хорошему, конечно, это надо было бы проверить программно, но иногда возникает лень в самых неподходящих моментах. Почему не стали использовать в качестве разделителя обычно используемую запятую (",")
? Сначала попробовали, но оказалось, что в именах ЮЛ в данной базе данных достаточно много запятых присутствует изначально. И при импорте в sqlite эти запятые в данных воспринимались тоже как разделители и ломали весь импорт. Потому будет пайп в качестве разделителя. Сама схема преобразований данных осталась старой, как в первой части. Сначала трансформируем xml в csv, а потом csv импортируем в sqlite. Только разделитель другой. Сначала протестируем код на одиночных файлах. Если что-то пойдет не так — быстро можем исправить код и ошибки.
Тестовый импорт xml в csv
Написали импорт одного файла из xml в csv. Запустили.
#D:\2021_8_16_oborot\part2\20.10.2021_mini_test_one_file_xml_to_csv_with_new_delimiter.py import xml.etree.ElementTree as Xet f = open('18_your_csv_file.csv', 'w', encoding='utf-8') xmlparse = Xet.parse('D:/2021_8_16_oborot/ish_unziped/test1.xml') root = xmlparse.getroot() for i in root: for i2 in i.findall('СведНП'): f.writelines(i2.attrib['НаимОрг']) f.write('|') f.writelines(i2.attrib['ИННЮЛ']) f.write('|') #f.write('\n') for i2 in i.findall('СведДохРасх'): f.writelines(i2.attrib['СумДоход']) f.write('|') f.writelines(i2.attrib['СумРасход']) f.write('\n') f.close()
Результат норм, такой:
ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ "ФИБРОТЕК"|7816394401|0.00|0.00 ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ "ИЗДАТЕЛЬСКИЙ ДОМ "ТВЕРСКАЯ ЖИЗНЬ"|6901026686|0.00|0.00 ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ "МРАВ"|7726503712|20800000.00|14102000.00 ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ "АГРОРЕСУРС"|6901026703|470000.00|461000.00
Тестируем импорт csv в table sqlite
Далее то же самое тест получившегося одного (маленького) csv файла в таблицу sqlite.
#D:\2021_8_16_oborot\part2\25.10.2021_mini_test_csv_to_sqlite_with_new_delimiter.py import sqlite3 import csv import os def create_db_table(): conn = sqlite3.connect('D:/2021_8_16_oborot/UL11.db') c = conn.cursor() # Create table c.execute('''CREATE TABLE IF NOT EXISTS oborot_2019_fns12 (name_UL text, inn_UL int, oborot_2019 real, rashod_2019 real)''') # ??? real==>>int??? #commit the changes to db conn.commit() #close the connection conn.close() def import_one_file_csv_to_sqlite(): con = sqlite3.connect('D:/2021_8_16_oborot/UL11.db') cur = con.cursor() with open('D:/2021_8_16_oborot/part2/19_your_csv_file.csv', 'r', encoding='utf-8') as f_open_csv: rows = csv.reader(f_open_csv, delimiter="|") for row in rows: cur.execute('INSERT INTO oborot_2019_fns12 VALUES (?, ?, ?, ?)', row) con.commit() con.close() if __name__ == '__main__': create_db_table() import_one_file_csv_to_sqlite()
Все гуд, данные записываются корректно. Данные в своих полях, не лезут в чужие.
Импорт всех xml в csv2 директорию
Теперь делаем тоже самое, но читаем все файлы из директории xml (…/ish_unziped) в директорию csv. Чтобы не путаться с директорией из первой части создадим новую директорию под csv файлы из текущей (второй части).
Назовем ее csv2
И теперь полный код, который импортирует данные из директории xml в директорию csv2 (с нашим новым разделителем)
# D:\2021_8_16_oborot\part2\2021.10.20_dir_xml_to_dir_csv_with_check_name_cell.py import os import xml.etree.ElementTree as Xet def parse_dii(): path = 'D:/2021_8_16_oborot/ish_unziped/' fileList = os.listdir(path) for t in fileList: with open('D:/2021_8_16_oborot/ish_unziped/' + t, 'r', encoding='utf-8') as ft: base = os.path.splitext(t)[0] with open(('D:/2021_8_16_oborot/csv2/' + base + '.csv'), 'w+', encoding='utf-8') as f: xmlparse = Xet.parse('D:/2021_8_16_oborot/ish_unziped/' + t) root = xmlparse.getroot() for i in root: for i2 in i.findall('СведНП'): f.writelines(i2.attrib['НаимОрг']) f.write('|') f.writelines(i2.attrib['ИННЮЛ']) f.write('|') for i2 in i.findall('СведДохРасх'): f.writelines(i2.attrib['СумДоход']) f.write('|') f.writelines(i2.attrib['СумРасход']) f.write('\n') if __name__ == '__main__': parse_dii()
Импорт всех csv в sqlite
Ну, а теперь в цикле открываем все получившиеся csv файлы из директории csv2 и записываем данные в таблицу SQLite.
#D:\2021_8_16_oborot\part2\20.10.2021_import_dir_csv_to_sqlite_with_new_delimiter.py import sqlite3 import csv import os def create_db_table(): conn = sqlite3.connect('D:/2021_8_16_oborot/UL12.db') c = conn.cursor() # Create table c.execute('''CREATE TABLE IF NOT EXISTS oborot_2019_fns13 (name_UL text, inn_UL int, oborot_2019 real, rashod_2019 real)''') # ??? real==>>int??? #commit the changes to db conn.commit() #close the connection conn.close() def import_csv_to_sqlite(): con = sqlite3.connect('D:/2021_8_16_oborot/UL12.db') cur = con.cursor() path_D = 'D:/2021_8_16_oborot/csv2' file_List = os.listdir(path_D) for x in file_List: with open('D:/2021_8_16_oborot/csv2/'+x, 'r', encoding='utf-8') as f_in: rows = csv.reader(f_in, delimiter="|") for row in rows: cur.execute('INSERT INTO oborot_2019_fns13 VALUES (?, ?, ?, ?)', row) con.commit() con.close() if __name__ == '__main__': create_db_table() import_csv_to_sqlite()
И вот тут нас ожидает БОЛЬШОЙ СЮРПРИЗ! Код вылетает с неприятным предупреждением: sqlite3.ProgrammingError: Incorrect number of bindings supplied. The current statement uses 4, and there are 1 supplied.
Что в переводе на русский означает примерно следующее: «программная ошибка sqlite3: Поставляется неправильное число привязок. В текущем операторе используется 4 (четыре), а предоставлено 1(одно).
Вообще непонятно, почему это предоставлено 1(одно) значение, мы же даем 4 значения для 4 колонок в sqlite?
И да, постойте, но мы же только что проверяли код и делали подобное преобразование для одного файла. Тот же самый код (за исключением итерации по директории) и там все было гуд. Как такое может быть? Надо заметить, что у нас гугление и понимание, что же это такое за ошибка заняло достаточно много времени. А все оказалось просто до невозможности.
А теперь очень хороший учебный вопрос для правильных велосипедостроителей: где же тут ошибка и как от нее избавиться? Есть идеи? Решение приведем в следующей части.
И как говорят в конце правильных фильмов: «Продолжение следует…»
P.S. Большая просьба (насколько это возможно) для догадавшихся или знающих в чем причина ошибки: пишите решение мне в личку. А то остальным велосипедостроителям будет не интересно гуглить, думать и решать задачку, когда решение уже известно.
Всем добра!
С уважением, Ваш nasingfaund.
Ссылка на Часть1. «Парсим базу юриков ФНС (велосипедостроение с xml, csv, SQLite и Питоном)»
ссылка на оригинал статьи https://habr.com/ru/articles/585592/
Добавить комментарий