
Замечание. Вся трилогия (часть 1 тут, часть 2 тут) о велосипедостроении с sqlite, xml, csv только для совсем маленьких Питоньих кодеров. Не для крутых кодеров, они умрут от скуки в нашем опусе и ничего нового не увидят. В третьей части заканчиваем все, что начали ранее
Начинаем изыски причины и местонахождения ошибки
Итак: правильный ответ: ошибки в коде нет. Ну точнее ошибка возникает при работе кода из-за ошибки в данных, которые обрабатывает наш код.
Чтобы убедиться, что это так, добавим в код обработку исключений:
вместо:
for row in rows: cur.execute('INSERT INTO oborot_2019_fns13 VALUES (?, ?, ?, ?)', row)
запишем:
for row in rows: try: cur.execute('INSERT INTO oborot_2019_fns14 VALUES (?, ?, ?, ?)', row) except Exception as e: print(e.__class__)
Мы изменили совсем немного, что это нам дало? В случае возникновения исключения при работе кода мы выводим ее причину в терминал, в остальных случаях код успешно отрабатывает и записывает данные в таблицу sqlite.
Запускаем код. В терминале получаем 5 раз одну и ту же строчку:
<class 'sqlite3.ProgrammingError'>
Не очень информативно, но можно предположить, что причиной ошибки явился не наш код, а данные. Причем, вероятно ошибка возникает 5 раз. Но как понять, где именно в данных произошли эти пять ошибок. И какова их причина?
Идем дальше. Меняем код так:
вместо:
except Exception as e: print(e.__class__)
пишем:
except Exception as e: print(e.__class__, ':', e.args[0], '::', x, ':::', row)
Что это нам дает? Мы получаем имя файлов, в которых случились ошибки и как выглядит та строчка, где случилась ошибка.
Запускаем исправленный код.
Получаем вывод в терминале:
D:\2021_8_16_oborot>python d:/2021_8_16_oborot/part2/30.10.2021_check2_error_with_import_csv_dir_to_sqlite_new_delimi.py <class 'sqlite3.ProgrammingError'> : Incorrect number of bindings supplied. The current statement uses 4, and there are 1 supplied. :: VO_OTKRDAN5_9965_9965_20210729_0773d892-d1e5-4000-a5e9-48f009a36802.csv ::: ['ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ '] <class 'sqlite3.ProgrammingError'> : Incorrect number of bindings supplied. The current statement uses 4, and there are 1 supplied. :: VO_OTKRDAN5_9965_9965_20210729_58b559b1-b92a-4a72-aee7-a16a43341fec.csv ::: ['ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ'] <class 'sqlite3.ProgrammingError'> : Incorrect number of bindings supplied. The current statement uses 4, and there are 1 supplied. :: VO_OTKRDAN5_9965_9965_20210729_a8e70040-cca9-4197-a402-55898861465b.csv ::: ['АКЦИОНЕРНОЕ ОБЩЕСТВО "НАУЧНО-ПРОИЗВОДСТВЕННОЕ'] <class 'sqlite3.ProgrammingError'> : Incorrect number of bindings supplied. The current statement uses 4, and there are 1 supplied. :: VO_OTKRDAN5_9965_9965_20210729_cb1ffb92-e932-46b8-982f-5a2b01ec49fa.csv ::: ['ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ'] <class 'sqlite3.ProgrammingError'> : Incorrect number of bindings supplied. The current statement uses 4, and there are 5 supplied. :: VO_OTKRDAN5_9965_9965_20210729_e25030ca-21c1-4ebb-bfcd-8416a65c8e8d.csv ::: ['ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ "ВЕДА ', ' ЮРИДИЧЕСКИЙ АУТСОРСИНГ"', '7729790649', '25185000.00', '24415000.00']
Отлично. Получили имена файлов. И 4 ошибки о том, что в исходниках csv одно поле в строчке, и если присмотреться это обрезанное имя какого-то ЮЛ, а пятая ошибка другая, мы видим, что по какой-то причине 4 значения для импорта в таблицу превратились в 5: имя ЮЛ разделилось на две части.
Отлично, идем дальше.
Перемещаем файлы с ошибками в отдельные директории
Мы получили имена файлов и чтобы было удобнее дальше искать причины ошибок скопируем файлы с ошибками в отдельную директорию 'csv_err'.
Кодим, перемещаем:
Сразу сделаем то же самое и для этих же файлов, но xml из директории 'ish_unziped'. Если помните названия файлов csv и xml у нас совпадают, только расширение нужно поменять. Раз в csv есть ошибки, то скорее всего эта же ошибка была изначально и xml. Есть еще, конечно, ненулевой вариант, что причина ошибки не xml, а свойства прокладки между стулом и клавой (но очень хочется надеяться, что это не так, и в результате поиска ошибок мы не выйдем сами на себя).
import os import shutil def create_dir(dir_name): if not os.path.exists(dir_name): os.mkdir(dir_name) print("Directory " , dir_name , " Created ") else: print("Directory " , dir_name , " already exists") def copy_files_by_names(list_files_err): old_dir = 'D:/2021_8_16_oborot/ish_unziped/' new_dir = 'D:/2021_8_16_oborot/xml_err/' list_files_err = ['VO_OTKRDAN5_9965_9965_20210729_0773d892-d1e5-4000-a5e9-48f009a36802.xml', 'VO_OTKRDAN5_9965_9965_20210729_58b559b1-b92a-4a72-aee7-a16a43341fec.xml', 'VO_OTKRDAN5_9965_9965_20210729_a8e70040-cca9-4197-a402-55898861465b.xml', 'VO_OTKRDAN5_9965_9965_20210729_cb1ffb92-e932-46b8-982f-5a2b01ec49fa.xml', 'VO_OTKRDAN5_9965_9965_20210729_e25030ca-21c1-4ebb-bfcd-8416a65c8e8d.xml'] for t in list_files_err: with open(new_dir + t, 'a+', encoding='utf-8') as f: try: shutil.copy(old_dir+t, new_dir+t) print('All copy to new_dir:', t) except Exception as e: print('Somethin goes to bad:', e, ':', t) if __name__ == '__main__': dir_name = 'D:/2021_8_16_oborot/xml_err' create_dir(dir_name) copy_files_by_names(list_files_err)
Теперь искать ошибки стало удобнее в 5 файлах, чем в 12 тысячах.
Вскрытие покажет
В принципе можно пять файлов и просто открыть и посмотреть что с ними не так. Но раз уж мы закоренелые велосипедостроители и тут пишем код питонячий. Единственное мы не стали перебирать 5 файлов, а по-одному обрабатывали - искали место и причиную ошибки. Так как в принципе ошибка может быть разная, а торопиться в этот раз мы не хотим. Код для одного из файлов. Для поиска других ошибок нужно только поменять имя файла и запустить.
import sqlite3 import csv import os def create_db_table(): conn = sqlite3.connect('D:/2021_8_16_oborot/UL17.db') c = conn.cursor() c.execute('''CREATE TABLE IF NOT EXISTS oborot_2019_fns18 (name_UL text, inn_UL int, oborot_2019 real, rashod_2019 real)''') conn.commit() conn.close() def import_csv_to_sqlite(): con = sqlite3.connect('D:/2021_8_16_oborot/UL17.db') cur = con.cursor() with open('D:/2021_8_16_oborot/csv_err/VO_OTKRDAN5_9965_9965_20210729_e25030ca-21c1-4ebb-bfcd-8416a65c8e8d.csv', 'r', encoding='utf-8') as f_open_csv: rows = csv.reader(f_open_csv, delimiter="|") count = 0 for row in rows: try: cur.execute('INSERT INTO oborot_2019_fns18 VALUES (?, ?, ?, ?)', row) print(count, row, ':', 'good') except Exception as e: print(e.__class__, ':', e.args[0], ':::', row, ':', count, 'e25030ca-21c1-4ebb-bfcd-8416a65c8e8d.csv' ) print(count, row, ':', 'bad') finally: count += 1 con.commit() con.close() if __name__ == '__main__': create_db_table() import_csv_to_sqlite()
Вывод получаем такой (приведены только строчки с ошибкой и на одну выше и ниже):
7 ['ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ "МЕРКУРИЙ РИЭЛТ"', '1655306626', '170000.00', '177000.00'] : good <class 'sqlite3.ProgrammingError'> : Incorrect number of bindings supplied. The current statement uses 4, and there are 5 supplied. ::: ['ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ "ВЕДА ', ' ЮРИДИЧЕСКИЙ АУТСОРСИНГ"', '7729790649', '25185000.00', '24415000.00'] : 8 e25030ca-21c1-4ebb-bfcd-8416a65c8e8d.csv 8 ['ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ "ВЕДА ', ' ЮРИДИЧЕСКИЙ АУТСОРСИНГ"', '7729790649', '25185000.00', '24415000.00'] : bad 9 ['ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ "УТИТ"', '9204019863', '296000.00', '328000.00'] : good
Имя базы данных мы тоже поменяли, чтобы не мешать базу с ошибками с большой базой.
Копаем дальше
Отлично видим, что ошибка в данном случае в 8 строчке у записи с ИНН = '7729790649', так как вероятность низка, что у нас двоятся ИНН-ки (в конце, кстати, надо не забыть проверить это), то открываем наш xml файл (в директории 'xml_err') c этим же именем и смотрим на запись с ИНН = '7729790649'. Нас интересуют данные перед этим блоком там находится имя ЮЛ, которое неправильно обрабатывается. Находим, копируем в любой редактор (VScode, word_MS, блокнот).
Ну или прямо в том редакторе, где xml исходный смотрим.
Видим:
'<СведНП НаимОрг="ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ "ВЕДА | ЮРИДИЧЕСКИЙ АУТСОРСИНГ"" ИННЮЛ="7729790649"/>'
Слегка нечитаемый вид.
Можно расшифровать онлайн, можно самим написать питоний код. Но сегодня у нас лень, поэтому мы просто вживую видим, что в записи присутствует знак '|', который мы использовали в качестве разделителя. То есть этот знак изначально присутствовал в данных и естественно при обработке xml разделил имя ЮЛ на две части и вместо 4 значений для 4 колонок получилось 5, которые не влазят в четыре.
Другие четыре ошибки можете сами найти по приведенному методу, ну или придумайте свой (расскажите в обсуде, интересно же!).
Но, можете поверить нам на слово, что в остальных файлах примерно такая же ошибка, но только вместо "знака трубопровода" - там знак перевода строки ('
'). Он по-другому испортил импорт. Вместо пяти значений с разделителями в одну строчку создал две строки, в одной части имени (одно значение) для 4 колонок, которое ломало импорт и следующая строка, где почти нормальные четыре значения для четырех колонок таблицы, но имя немного обрезанное на ту часть, которая была в первой строке, а именно у трех отрезана организационно-правовая форма 'ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ', а у четвертой отрезано часть имени: 'АКЦИОНЕРНОЕ ОБЩЕСТВО "НАУЧНО-ПРОИЗВОДСТВЕННОЕ')
Как исправить эти ошибки? Можно поправить код и проверять на присутствие-отсутствие этих плохих разделителей, переводчиков каретки, но мы сделали чистку данных руками. Удалили эти 5 ошибок из xml и записали подправленные xml файлы обратно в общую директорию. Затем переобработали (перезаписали) только эти файлы в директорию csv2. Затем импортировали обновленную директорию csv2 в sqlite.
Код перемещалки "улучшенных файлов" xml обратно в общую директорию 'ish_unziped'
import os import shutil def copy_files_by_names(): old_dir = 'D:/2021_8_16_oborot/xml_err/' new_dir = 'D:/2021_8_16_oborot/ish_unziped/' list_files_err = ['VO_OTKRDAN5_9965_9965_20210729_0773d892-d1e5-4000-a5e9-48f009a36802.xml', 'VO_OTKRDAN5_9965_9965_20210729_58b559b1-b92a-4a72-aee7-a16a43341fec.xml', 'VO_OTKRDAN5_9965_9965_20210729_a8e70040-cca9-4197-a402-55898861465b.xml', 'VO_OTKRDAN5_9965_9965_20210729_cb1ffb92-e932-46b8-982f-5a2b01ec49fa.xml', 'VO_OTKRDAN5_9965_9965_20210729_e25030ca-21c1-4ebb-bfcd-8416a65c8e8d.xml'] for t in list_files_err: with open(new_dir + t, 'a+', encoding='utf-8') as f: try: shutil.copy(old_dir+t, new_dir+t) print('All copy to new_dir:', t) except Exception as e: print('Somethin goes to bad:', e, ':', t) if __name__ == '__main__': copy_files_by_names()
Финишные вишенки
Окей, таблица получена.
Посчитаем сколько у нас получилось ИННок ЮЛ в таблице sqlite.
Если совсем без питона, а запросами SQL (например в любом редакторе sqlite), то так можем посчитать количество строчек в таблице:
SELECT count(*) FROM oborot_2019_fns20;
А так сосчитать какое количество ИНН_ЮЛ получается без двойников по ИНН.
SELECT COUNT(DISTINCT inn_UL) as count FROM oborot_2019_fns20;
Тоже самое на Питоне:
#D:\2021_8_16_oborot\part3\08.11.2021_count_rows_i_INN_UL_without_dublicates_sqlite.py import sqlite3 def count_rows_i_zdINN(): conn = sqlite3.connect('D:/2021_8_16_oborot/UL19.db') c = conn.cursor() c.execute("select count(*) from oborot_2019_fns20") results = c.fetchone() print('count_rows :', results[0]) c.execute("select count(distinct inn_UL) as count from oborot_2019_fns20") results = c.fetchone() print('count_inn_UL_without_dublicates :', results[0]) c.close() conn.close() if __name__ == '__main__': count_rows_i_zdINN()
Получили вывод терминала, который говорит, что количество дубликатов равно нулю.
count_rows : 2174433 count_inn_UL_without_dublicates : 2174433
Теперь убедимся, что в начальных данных и было такое количество ИНН_ЮЛ.
Выведем в 2 файла в один просто ИНН_ЮЛ, а во втором еще и пронумеруем каждую запись. Так же в терминал выводим результаты подсчета ИНН_ЮЛ. Код отрабатывает достаточно долго. Но нас, как истинных велосипедостроителей оное не должно пугать.
import os import xml.etree.ElementTree as Xet def parse_dii(): path = 'D:/2021_8_16_oborot/ish_unziped/' fileList = os.listdir(path) inn_count = 1 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/part2/3all_inn_ul.csv', 'a+', encoding='utf-8') as f: with open('D:/2021_8_16_oborot/part2/3all_inn_ul_with_count.csv', 'a+', encoding='utf-8') as f1: xmlparse = Xet.parse('D:/2021_8_16_oborot/ish_unziped/' + t) root = xmlparse.getroot() for i in root: for i2 in i.findall('СведНП'): try: f1.write(str(inn_count)) f1.write(':') f1.write(i2.attrib['ИННЮЛ']) f1.write('\n') f.write(i2.attrib['ИННЮЛ']) f.write('\n') except Exception as e: print('Else bad', e, ':', e.args[0], '::', inn_count) finally: print('inn_count :', inn_count) inn_count += 1 if __name__ == '__main__': parse_dii()
В итоге получаем в терминале:
inn_count : 2174433
А открыв записанные файлв в обеих видим в последней строчке тоже самое число: 2 174 433.
Подведем итоги нашего велосипедостроения:
1. Как нам кажется неплохо повелосипедили.
2. В сети не так много практических примеров подобного велосипедостроения (сугубо моя оценка и по отношению к Питону только).
3. С полученной таблицей уже можно наделать много других велосипедов (прикрутить GUI, добавить аналитические модули, импортировать данные из других подобных источников, соединить таблицы из разные источников, сделать анализ данных в разных разрезах)
Всем пис и насингфаунд фореве!
