Замечание. Вся трилогия (часть 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 исходный смотрим.

Видим:

 '<СведНП НаимОрг="&#x41E;&#x411;&#x429;&#x415;&#x421;&#x422;&#x412;&#x41E; &#x421; &#x41E;&#x413;&#x420;&#x410;&#x41D;&#x418;&#x427;&#x415;&#x41D;&#x41D;&#x41E;&#x419; &#x41E;&#x422;&#x412;&#x415;&#x422;&#x421;&#x422;&#x412;&#x415;&#x41D;&#x41D;&#x41E;&#x421;&#x422;&#x42C;&#x42E; &quot;&#x412;&#x415;&#x414;&#x410; | &#x42E;&#x420;&#x418;&#x414;&#x418;&#x427;&#x415;&#x421;&#x41A;&#x418;&#x419; &#x410;&#x423;&#x422;&#x421;&#x41E;&#x420;&#x421;&#x418;&#x41D;&#x413;&quot;" ИННЮЛ="7729790649"/>'

Слегка нечитаемый вид. 

Можно расшифровать онлайн, можно самим написать питоний код. Но сегодня у нас лень,  поэтому мы просто вживую видим, что в записи присутствует знак '|',  который мы использовали в качестве разделителя. То есть этот знак изначально присутствовал в данных и естественно при обработке xml разделил  имя ЮЛ на две части  и вместо 4 значений для 4 колонок получилось 5, которые не влазят в четыре. 

Другие четыре ошибки можете сами найти по приведенному методу, ну или придумайте  свой (расскажите в обсуде, интересно же!). 

Но, можете поверить нам на слово, что в остальных файлах примерно такая же ошибка, но только вместо "знака трубопровода" - там знак перевода строки ('&#10').  Он по-другому испортил импорт. Вместо пяти значений с разделителями в одну строчку создал две строки, в одной части имени (одно значение) для 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, добавить аналитические модули, импортировать данные из других подобных  источников, соединить таблицы из разные источников, сделать анализ данных  в разных разрезах)

Всем пис и насингфаунд фореве!

Комментарии (7)


  1. gecube
    09.11.2021 15:19
    +1

    долистал до опечаток, до кривых отступов в коде и статью закрыл

    Прости, автор! Я, правда, старался... но не получилось прочитать

    Позови, когда все будет хорошо )))

    Твой верный читатель ))))


    1. nasingfaund Автор
      09.11.2021 15:25

      Печалька, у автора мозгов не хватает как нормально создать текст и без молотка и зубила сразу засунуть нормально в редактор. Самый большой прикол, в том, что текст в редакторе выглядит по-одному, а в статье совсем по-другому, особенно любят ломаться отступы в коде, что для питона мягко говоря критично.


      1. AntonSazonov
        09.11.2021 15:34
        +1

        Может быть просто нужно везде использовать табуляцию или наоборот пробелы а не мешать и то и другое?


        1. nasingfaund Автор
          09.11.2021 15:43

          Да, можно, вопрос в другом. Вторую часть поместил в черновики и спокойно правил, думая, что это то место, где можно доводить до ума статью (переводить из маркдауна в текущий формат публикации), но потом оказалось что из-за этого статья вышла две недели назад от ее реальной "чистовой" публикации. Ессно, она имеет нуль просмотров (почти).

          А еще, почему-то отличается вид в статье и в редакторе. В редакторе вижу так:

          for row in rows:
              try:
                cur.execute('INSERT INTO oborot_2019_fns14 VALUES (?, ?, ?, ?)', row)
            except Exception as e:
                print(e.__class__)

          А в статье сломанное форматирование, которое хочу но не могу поправить, т.к. в редакторе все выглядит норм. Вид кода в статье:

          for row in rows:
          	try:
            	cur.execute('INSERT INTO oborot_2019_fns14 VALUES (?, ?, ?, ?)', row)
            except Exception as e:
            	print(e.__class__)

          Кстати и здесь код выглядит по-другому. Форматирование у кода в редакторе выглядит норм, а вставил сюда в коммент и он стал бэд.


          1. nasingfaund Автор
            09.11.2021 16:40

            Всем сэнкс, похоже разобрался, можно ТОЛЬКО или 2 или 4 пробела. Все. Другие варианты не катят. Сделал 4. Позже проверю можно ли 2, хотя это не по-питоньи, вроде.