CoderCastrov logo
CoderCastrov
Анализ данных

Используйте свои данные: Парсинг PDF

Используйте свои данные: Парсинг PDF
просмотров
5 мин чтение
#Анализ данных

Часть 2: Извлечение данных из PDF, очистка и анализ

Привет. В этой статье, продолжение Части 1, показывается пошагово, как извлечь данные из PDF. Если вы еще не прочитали предыдущую статью, которая показывает, как автоматизировать загрузку вложений из электронных писем, вы можете прочитать ее здесь.

Прежде чем мы продолжим, давайте проверим, какова наша цель, что мы уже сделали и что осталось сделать.

Цель: Сколько я задолжал за детский сад? И как предотвратить повторение этих расхождений в платежах?

Шаги для достижения цели:

Пункт 1 достигнут. Давайте работать над оставшимися пунктами. Мы будем использовать Python и некоторые сторонние библиотеки для достижения нашей цели.

Извлечение данных из PDF

Здесь предоставлен образец PDF - оригинальный документ не может быть использован из-за его конфиденциального характера. Наша цель заключается в извлечении всех таблиц из PDF. Обратите внимание, что PDF должен быть основанным на тексте, а не на отсканированных документах. Если вы можете выделить текст в таблице в PDF-просмотрщике, то PDF основан на тексте. Ниже приведен фрагмент образца PDF.

Для извлечения таблиц из PDF мы будем использовать мощную стороннюю библиотеку под названием Camelot. Синтаксис Camelot прост:

table = camelot.read_pdf('foo.pdf')

Просто используйте метод read_pdf и у вас есть таблица. Если страниц в PDF больше одной, синтаксис становится следующим:

tables = camelot.read_pdf('foo.pdf', pages='1,2,3 и так далее')
# или вы можете просто установить pages="all", чтобы прочитать все страницы в PDF.

Вы также можете установить страницы как диапазон (1, 4–10, ...).

Если ячейки таблицы в PDF имеют разделительные линии между ними, то Camelot автоматически работает с PDF, используя lattice в качестве значения по умолчанию. Если же ячейки не имеют разделительных линий, просто установите значение flavor как stream:

tables = camelot.read_pdf('foo.pdf', pages='1-3', flavor='stream')

Чтобы узнать, сколько таблиц было извлечено, просто выполните следующий код:

tables.n

Чтобы просмотреть таблицу в виде объекта данных pandas, просто вызовите метод df на таблице:

tables[0].df

На сайте библиотеки здесь есть еще много других параметров и процедура установки.

Для нашей цели в PDF есть несколько страниц и он не имеет разделительных линий, поэтому мы будем использовать flavor='stream'. Но как узнать количество страниц? Просто передайте строку 'all' в аргумент pages.

Итак, давайте извлечем наши таблицы:

import camelottable = camelot.read_pdf(file,
                         pages='all',
                         flavor='stream',
                         edge_tol = 500)

В приведенном выше коде мы импортировали camelot и прочитали PDF-файл с помощью метода read_pdf. Обратите внимание, что flavor установлен как stream, и есть дополнительный аргумент - edge_tol, который помогает улучшить обнаруженную область. Это дает нам все необходимые таблицы.

Неплохо. Мы достигли пункта 2, который заключается в извлечении PDF. Однако наши данные запутаны и довольно неструктурированы, что иногда бывает, когда извлекаются таблицы из PDF. Ниже показано, как выглядит одна из таблиц - довольно беспорядочно - у нас есть строки, которые не являются значимыми - строки 4 и 5, например; нам нужны только строки с датами. Также есть некоторые столбцы, которые пусты или содержат '....'; они не имеют значения и должны быть отброшены.

Это приводит к пункту 3 - очистке данных. И у нас есть инструмент для достижения этой цели в Python - Pandas. Хорошо, давайте разберем задачи по очистке. Что мы делаем, чтобы сделать это "чистым"?

Хорошо, включаем шланги! Давайте очистим.

def filter_index(dataframe):    '''
    фильтруем только индексы, которые начинаются с дня недели
    '''    date_filter = ('Пн','Вт','Ср','Чт','Пт','Сб','Вс')    index_filter = dataframe.index.str.strip().str.startswith(date_filter)    dataframe = dataframe.loc[index_filter]    return dataframe

Вышеуказанная функция фильтрует фрейм данных только для строк, у которых индексы начинаются с 'Пн', 'Вт', ... и отбрасывает остальные.

  1. Очищаем ячейки с несколькими точками и избавляемся от столбцов, в которых все ячейки пусты/нулевые
def clean_columns(dataframe):
    
    '''
    Избавляемся от точек и удаляем пустые столбцы
    '''
    
    for col in dataframe.columns:        dataframe[col] = dataframe[col].str.strip('.')
    
    cols = [col for col in dataframe.columns 
                if (dataframe[col]=="").all()]    dataframe = dataframe.drop(cols,axis=1)
    
    return dataframe

Объяснение: Цикл for выше удаляет префикс и суффикс ячеек точками, если они существуют. Следующие две строки после цикла for ищут столбцы, в которых все ячейки пусты (""), и удаляют их из фрейма данных.

  1. Устанавливаем одинаковые заголовки для всех извлеченных таблиц.
def column_rename(dataframe):
    
    '''
    изменяем заголовки столбцов на более осмысленные.
    '''    col_names = ['Начало','Конец','Часы',
                 'Посещение_Вход',
                 'Посещение_Выход', 
                 'Посещение_Часы',
                 'Стоимость_До_Скидок',               
                 'Общая_Стоимость_За_CCS',
                 'Почасовая_Стоимость',
                 'CCS_Часы',
                 'Скидка_на_Сервис_CCS', 
                 'Оплата_Родителя',
                 'ID_Воспитателя'
                ]    dataframe.columns = col_names
    return dataframe

Объяснение: Это делает то, что говорит - переименовывает столбцы таблицы. Это будет применяться к каждой извлеченной таблице.

Еще одна вещь - изменяем тип данных фрейма данных - делаем их в правильном формате - тип данных даты, числовой тип

def change_dtype(dataframe):    #здесь я изменил типы данных
    
    dataframe.index = pd.to_datetime(dataframe.index,
                                     dayfirst=True)
                        .rename('Дата')    dataframe['Стоимость_До_Скидок']=       pd.to_numeric(dataframe['Стоимость_До_Скидок'].str.replace('$',''))    dataframe['CCS_Часы'] = pd.to_numeric(dataframe['CCS_Часы'])    dataframe['Общая_Стоимость_За_CCS'] = pd.to_numeric(dataframe['Общая_Стоимость_За_CCS'].str.replace('$',''))    dataframe['Почасовая_Стоимость'] = pd.to_numeric(dataframe['Почасовая_Стоимость'])    dataframe['Скидка_на_Сервис_CCS'] = pd.to_numeric(dataframe['Скидка_на_Сервис_CCS'])    dataframe['Оплата_Родителя'] = pd.to_numeric(dataframe['Оплата_Родителя'])    dataframe.insert(0,'День',dataframe.index.day_name())
    
    return dataframe

У нас есть все наши функции, теперь давайте применим их к таблицам и очистим их. Мы будем использовать цикл for для достижения этого:

import pandas as pd data_list = []for t in range(table.n):    data = table[t].df    if len(data.columns)<13:  
        continue    data_fixed = (data.set_index(0)
                      .pipe(filter_index)
                      .pipe(clean_columns)
                      .pipe(column_rename)
                      .pipe(change_dtype)                     )
    
    data_list.append(data_fixed)

У нас есть наши очищенные таблицы в списке. Последний шаг - объединить их все в один фрейм данных:

data_final = pd.concat(data_list, ignore_index=False, sort=False)

Тадааа! Наша работа сделана. На изображении ниже показана часть нашего окончательного вывода.

Теперь мы можем провести анализ и узнать, сколько нам нужно заплатить за детский сад. Я не буду проводить здесь анализ, так как это всего лишь образец PDF с глупыми цифрами.

Итак, вот и все. Теперь мы знаем, как автоматизировать загрузку из нашей электронной почты, парсить PDF и очищать данные. Весь код находится в моем репозитории на GitHub; вы можете посмотреть его здесь. Разберите его, протестируйте и дайте мне знать ваши мысли. Обратная связь будет очень полезна.

Ссылки:

Camelot: https://camelot-py.readthedocs.io/en/master/

Pandas: https://pandas.pydata.org

Мой репозиторий на GitHub: https://github.com/samukweku/PDF_Extraction