Последний велосипедно-питоний бой с ошибками импорта sqlite за 2 174 433 строчки. Часть 3

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

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

Видим:

 '<СведНП НаимОрг="&#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, добавить аналитические модули, импортироват данные из других подобных источников, сделать анализ данных в разных разрезах)

Источник: https://habr.com/ru/post/588066/


Интересные статьи

Интересные статьи

25 февраля автор курса «Разработчик C++» в Яндекс.Практикуме Георгий Осипов рассказал о новом этапе языка C++ — Стандарте C++20. В лекции сделан обзор всех основных нововведений Станд...
Сегодня мы хотим поделиться опытом решения задачи детекции дефектов на снимках промышленных объектов методами современного компьютерного зрения. Наш рассказ будет состоять из нескольк...
Вопрос в заголовке включает в себя неочевидную часть, ведь перед тем, как рассказывать про создание хорошей интеграции стоит определить, какую интеграцию мы считаем хорошей. А ответ на эт...
В своей статье "Использование GitHub в обучении студентов" я кратко коснулся темы использования GitHub'а именно как инструмента для обучения, а не как темы в обучении. Се...
Изобретаем велосипед или пишем персептрон на C++. Часть 1 Напишем простую библиотеку для реализации персептрона на C++ Читать дальше →