Добавить в корзинуПозвонить
Найти в Дзене
Nuances of programming

Скрейпинг PDF с нуля на Python: библиотеки tabula-py и Pandas

Источник: Nuances of Programming Специалисту в области науки о данных приходится анализировать данные в любой форме, ведь они хранятся как в специальных SQL-базах, вроде PostgreSQL и MySQL, так и в старой доброй электронной таблице Microsoft Excel. Более того, иногда данные сохранены в нетрадиционном формате, например в PDF. В этой статье вы узнаете, как скрейпить данные из файлов PDF и оформлять их подходящим для применения в Data Science образом с помощью специальных библиотек языка программирования Python. Оглавление: Подготовка Ознакомьтесь со списком необходимых для выполнения руководства Python-библиотек. С помощью стандартного менеджера пакетов pip или любого другого установите две библиотеки из списка: pip install tabula-py
pip install pandas Создайте файл программы-скрейпера и импортируйте в него функционал библиотек для скрейпинга: import tabula as tb
import pandas as pd
import re Сохранение структуры данных при скрейпинге из PDF Прежде всего, обсудим скрейпинг данных из файл
Оглавление

Источник: Nuances of Programming

Специалисту в области науки о данных приходится анализировать данные в любой форме, ведь они хранятся как в специальных SQL-базах, вроде PostgreSQL и MySQL, так и в старой доброй электронной таблице Microsoft Excel. Более того, иногда данные сохранены в нетрадиционном формате, например в PDF.

В этой статье вы узнаете, как скрейпить данные из файлов PDF и оформлять их подходящим для применения в Data Science образом с помощью специальных библиотек языка программирования Python.

Оглавление:

  1. Подготовка.
  2. Сохранение структуры данных при скрейпинге из PDF.
  3. Скрейпинг неструктурированных данных из PDF.
    1. Выгрузка данных из файла PDF в объект Pandas.
    2. Cоздание идентификаторов для отдельных записей.
    3. Преобразование таблицы из длинной формы в широкую с помощью Pandas.
    4. Объединение данных в единую финальную таблицу.
  4. Выводы.

Подготовка

Ознакомьтесь со списком необходимых для выполнения руководства Python-библиотек.

  • tabula-py: для скрейпинга текста из файлов PDF.
  • re: для извлечения сугубо нужных данных с помощью регулярных выражений.
  • pandas: для удобной работы с данными.

С помощью стандартного менеджера пакетов pip или любого другого установите две библиотеки из списка:

pip install tabula-py
pip install pandas

Создайте файл программы-скрейпера и импортируйте в него функционал библиотек для скрейпинга:

import tabula as tb
import pandas as pd
import re

Сохранение структуры данных при скрейпинге из PDF

Прежде всего, обсудим скрейпинг данных из файла PDF в изначально структурированном формате. В следующем примере показан скрейпинг таблицы: это хорошо структурированные данные, в них чётко определены строки и столбцы.

Красным обведён набор структурированных (табличных) данных для скрейпинга из файла PDF
Красным обведён набор структурированных (табличных) данных для скрейпинга из файла PDF

Если воспользоваться библиотекой tabula-py, то скрейппинг PDF-данных в структурированном виде упростится в разы. Просто введите в программу расположение табличных данных на PDF-странице, указав верхнюю правую, верхнюю левую, нижнюю правую и нижнюю левую координаты области. Если на странице только одна целевая таблица, то даже не нужно указывать координатами область скрейпинга. Более того, библиотека tabula-py в большинстве случаев определяет строки и столбцы автоматически.

file = 'state_population.pdf' data = tb.read_pdf(file, area = (300, 0, 600, 800), pages = '1')

Скрейпинг неструктурированных данных из PDF

Во второй части руководства обсудим задачу поинтереснее  —  получение текста из PDF-файла в неструктурированном формате.

Для успешного статистического анализа, визуализации данных и создания моделей машинного обучения просто необходимы “панельные данные” (“лонгитюдные данные”), то есть данные социальных исследований в табличной форме. Однако в 2021-м году многие необходимые для анализа данные доступны только в неструктурированном виде.

Например, сотрудники отдела кадров, скорее всего, хранят исторические данные о заработной плате не в табличной форме. На следующем скриншоте приводятся в пример как раз такие данные о заработной плате со смешанной структурой: в левой части находится информация об имени сотрудника, чистой сумме выплаты, дате выплаты и оплаченном времени, а в правой  —  о категории выплаты (PAY), ставке в час (RATE), количестве часов работы (HRS) и фактической сумме выплаты (AMT).

Красным обведён набор структурированных (табличных) данных для скрейпинга из файла PDF
Красным обведён набор структурированных (табличных) данных для скрейпинга из файла PDF

Для преобразования данных в лонгитюдный (табличный, панельный) формат необходимо выполнить несколько шагов.

  • Шаг 1: выгрузка данных из файла PDF в объект Pandas.

Подобно примеру со структурированными данными, в самом начале скрейпинга воспользуемся методом tb.read_pdf() для импорта неструктурированных, но на этот раз для правильного импорта потребуется вручную указать дополнительные параметры.

file = 'payroll_sample.pdf' df = tb.read_pdf(file, pages='1', area=(0, 0, 1000, 1000), columns=[200, 265, 300, 320], pandas_options={'header': None}, stream=True)[0]

  • Параметры area и columns

В первой части руководства упоминалось определение области для скрейпинга с помощью координат. Теперь нам в любом случае потребуется вручную указать columns, чтобы определить местоположение всех необходимых для анализа колонок (одна колонка на левой секции и четыре колонки на правой секции).

  • Параметры stream и lattice

Когда в PDF-файле все ячейки таблицы разделены линиями сетки, стоит указать параметр lattice = True для автоматического определения каждой ячейки в таблице. Если же никакой сетки нет, то стоит указать параметр stream = True вместе с параметром columns для определения каждой ячейки вручную.

  1. lattice (bool, необязательный параметр)  —  принудительное извлечение данных из PDF в “решетчатом” режиме. Применяется, когда каждую ячейку таблицы разделяют линейки, как в PDF из Microsoft Excel).
  2. stream (bool, необязательный параметр)  —  принудительное извлечение данных из PDF в “потоковом” режиме. Применяется, когда нет сетки, разделяющей ячейки в таблице.
Пример структурированных (табличных) данных, разделённых с помощью сетки
Пример структурированных (табличных) данных, разделённых с помощью сетки
  • Шаг 2: создание идентификаторов для отдельных записей.

После выполнения предыдущего шага некоторые данные для работы уже получены. Теперь мы воспользуемся библиотекой Python Pandas для манипулирования табличными данными, хранящимися в экземпляре класса pandas.DataFrame  —  это такой специальный “контейнер-таблица” в идеальном для аналитики данных формате.

Для начала создаем столбец, чтобы разместить в нем новые ячейки с идентификаторами записей о сотрудниках. Легко заметить, что имя сотрудника из примера данных (Супермен и Бэтмен) стоит учитывать при определении границы между записью о Супермене и записью о Бэтмене. Каждое имя сотрудника уникально отформатировано: начинается с заглавной буквы и заканчивается строчной. В таком случае для идентификации по имени сотрудника как раз подойдёт регулярное выражение ‘^[A-Z].*[a-z]$’. После поиска регулярным выражением достаточно применить Pandas-функцию cumsum (кумулятивная сумма), а идентификаторы для записей создадутся сами.

df['border'] = df.apply(lambda x: 1 if re.findall('^[A-Z].*[a-z]$', str(x[0])) else 0, axis = 1)
df['row'] = df['border'].transform('cumsum')

Пример создания идентификаторов для записей в таблице: столбец row содержит номер записи (1 и 2)
Пример создания идентификаторов для записей в таблице: столбец row содержит номер записи (1 и 2)
  • Шаг 3: преобразование таблицы из длинной формы в широкую с помощью Pandas.

Начнём с определения терминов “длинные данные” (“long data”) и “широкие данные” (“wide data”).

  1. Таблица, хранящаяся в “длинной” форме, содержит по одному столбцу для каждой переменной в системе.
  2. Таблица, хранящаяся в “широкой” форме, распределяет данные о переменной по нескольким столбцам.

Теперь постараемся изменить форму данных в левой и правой секциях таблицы из примера выше.

  1. Для левой секции таблицы создаем новый pandas.DataFrame под названием “employee” (сотрудник), состоящий из столбцов employee_name (имя сотрудника), net_amount (чистая сумма выплаты), pay_date (дата выплаты) и pay_period (оплаченный период).
  2. Для правой секции таблицы тоже создаем отдельный pandas.DataFrame под названием “payment”, состоящий из столбцов OT_Rate (ставка в час при категории “OT”), Regular_Rate (ставка в час при категории “Regular”), OT_Hours (количество часов работы категории “OT”), Regular_Hours (количество часов работы категории “Regular”), OT_Amt (фактическая сумма выплаты категории “OT”) и Regular_Amt (фактическая сумма выплаты категории “Regular”).

Преобразовать данные в широкую форму нам поможет функция из библиотеки Pandas под названием pivot.

# изменение формы левой секции таблицы employee = df[[0, 'row']]
employee = employee[employee[0].notnull()]
employee['index'] = employee.groupby('row').cumcount()+1 employee = employee.pivot(index = ['row'], columns = ['index'], values = 0).reset_index()
employee = employee.rename(columns = {1: 'employee_name', 2: 'net_amount', 3: 'pay_date', 4: 'pay_period'})
employee['net_amount'] = employee.apply(lambda x: x['net_amount'].replace('Net', '').strip(), axis = 1)

# изменение формы правой секции таблицы payment = df[[1, 2, 3, 4, 'row']]
payment = payment[payment[1].notnull()]
payment = payment[payment['row']!=0]
payment = payment.pivot(index = ['row'], columns = 1, values = [2, 3, 4]).reset_index()
payment.columns = [str(col[0])+col[1] for col in payment.columns.values]
for i in ['Regular', 'OT']:
payment = payment.rename(columns = {f'2{i}': f'{i}_Rate', f'3{i}': f'{i}_Hours', f'4{i}': f'{i}_Amt'})

pandas.DataFrame под названием “employee” (сотрудник), правая секция изначальной таблицы
pandas.DataFrame под названием “employee” (сотрудник), правая секция изначальной таблицы
pandas.DataFrame под названием “payment” (платеж), левая секция изначальной таблицы
pandas.DataFrame под названием “payment” (платеж), левая секция изначальной таблицы
  • Шаг 4: объединение данных в единую финальную таблицу.

Наконец, на основе идентификаторов строк с помощью функции merge() объединим два pandas.DataFrame  —  employee и payment  —  в единую таблицу данных о сотрудниках и платежах.

df_clean = employee.merge(payment, on = ['row'], how = 'inner')

Пример финальной таблицы с данными о сотрудниках и платежах
Пример финальной таблицы с данными о сотрудниках и платежах

Выводы

В 2021-м году сотрудники многих компаний все еще вручную обрабатывают данные из фалов формата PDF. Сегодня мы показали, как при помощи Python-библиотек tabula-py и Pandas сэкономить время и деньги, автоматизировав не только извлечение данных из файлов PDF, но и преобразование неструктурированных данных в панельные.

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

Читайте также:

Читайте нас в Telegram, VK

Перевод статьи Aaron Zhu: Scrape Data from PDF Files Using Python