Замечание. Вся трилогия (часть 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 одно поле в строчке, и если присмотреться это обрезанное имя какого-то ЮЛ (точне его организационо-правовая форма), а 5 ошибка другая: у нас получилось 5 ячеек для записи, и здесь мы видим, что имя_ЮЛ разделилось на две колонки.
Отлично, идем дальше.
Перемещаем файлы с ошибками в отдельные директории
Мы получили имена файлов и чтобы было удобнее дальше искать причины ошибок скопируем файлы с ошибками в отдельную директорию 'csv_err':
кодим, перемещаем:
import os
import shutil
def create_dir(dir_name):
# Create target Directory if don't exist
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/csv2/'
new_dir = 'D:/2021_8_16_oborot/csv_err/'
list_files_err = ['VO_OTKRDAN5_9965_9965_20210729_0773d892-d1e5-4000-a5e9-48f009a36802.csv',
'VO_OTKRDAN5_9965_9965_20210729_58b559b1-b92a-4a72-aee7-a16a43341fec.csv',
'VO_OTKRDAN5_9965_9965_20210729_a8e70040-cca9-4197-a402-55898861465b.csv',
'VO_OTKRDAN5_9965_9965_20210729_cb1ffb92-e932-46b8-982f-5a2b01ec49fa.csv',
'VO_OTKRDAN5_9965_9965_20210729_e25030ca-21c1-4ebb-bfcd-8416a65c8e8d.csv']
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/csv_err'
create_dir(dir_name)
copy_files_by_names(list_files_err)
сразу сделаем то же самое и для этих же файлов, но xml из директории 'ish_unziped'. Если помните названия файлов csv и xml у нас совпадают, только расширение нужно поменять. Раз в csv есть ошибки, то скорее, всего эта же ошибка была изначально и xml. Есть еще, конечно, ненулевой вариант, что причина ошибки не xml, а свойства прокладки между стулом и клавой (но очень хочется надеяться, что это не так и в результате поиска ошибок мы не выйдем сами на себя).
```python
import os
import shutil
def create_dir(dir_name):
# Create target Directory if don't exist
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()
# Create table
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, добавить аналитические модули, импортироват данные из других подобных источников, сделать анализ данных в разных разрезах)