0
Обложка: Основные задачи парсинга PDF

Основные задачи парсинга PDF

В этой статье мы выполним задачи парсинга PDF:

  1. Из файла PDF удалить страницы с таблицами.
  2. Страницы без таблиц сохранить отдельными файлами pdf, прибавив к названию основного файла суффикс «_n», где n — порядковый номер страницы.
  3. Страницы без таблиц объединить в один файл pdf с названием основного файла + ‘ no tables’.
  4. Из страниц с таблицами извлечь таблицы и сохранить в файл csv/xlsx c названием «table-n», где n – порядковый номер таблицы.

Для выполнения задания пока потребуются такие модули: pdfplumber — для извлечения таблиц и pdfrw — для чтения/записи файлов pdf. Эти модули не входят в стандартную библиотеку Python, по этому их нужно устанавливать.

Установить их можно командами: pip install pdfrw, pip install pdfplumber.

Работа с библиотекой pdfrw

Чтение документа

from pdfrw import PdfReader

path_pdf = 'path/to/file.pdf'
x = PdfReader(path_pdf)
print(len(x.pages))

Всё просто. Импорт подкласса PdfReader, создание его объекта, который читает файл, атрибут pages возвращает список всех страниц в документе.

Запись одной страницы

# запись выбранной страницы
from pdfrw import PdfWriter

y = PdfWriter()
y.addpage(x.pages[0])
y.write('result1.pdf')

Также всё просто. Объекту подкласса PdfWriter Y передаём через метод .addpage прочитанную объектом X нужную нам страницу для записи, метод .write делает запись.

Запись нескольких страниц в один файл

# запись нескольких страниц в одну
from pdfrw import PdfWriter

y = PdfWriter()
y.addpage(x.pages[0])
y.addpage(x.pages[2])
y.write('result2.pdf')

Думаю, тут комментарий не нужен.

Здесь приведены примеры только тех методов и атрибутов, которые нужны для выполнения задания. Для более глубокого изучения ссылки на документации: https://pypi.org/project/pdfrwhttps://pypi.org/project/pdfplumber/.

Работа с модулем pdfplumber

import pdfplumber

path_pdf = 'path/to/file.pdf'
with pdfplumber.open(path_pdf) as pdf: 
    print(pdf.pages)
    print()
    page = pdf.pages[2]
    table = page.extract_table() 
    print(table)
    print()
    text = page.extract_text()
    print(text)

Здесь метод .open возвращает экземпляр pdfplumber.PDF класса, атрибут pages возвращает список экземпляров pdfplumber.Page каждой страницы документа, метод .extract_table извлекает табличные данные со страницы. Он возвращает двухмерный список, где элементами списка есть списки с данными каждой строки таблицы. Дальше этими данными мы воспользуемся для записи таблицы в файл csv/xlsx. Метод .extract_text возвращает текст таблицы одной строкой с символами перехода на новую строку.

Печать:

[<Page:1>, <Page:2>, <Page:3>, <Page:4>]

[['43', '1', '143112230', 'Ball bearing, 6x19x6', '22'], ['44', '1', '310010660', 'Armature compl.,230V', '31'], ['45', '1', '143115890', 'Ball bearing, 8X 22X  7', '16'], ['46', '1', '343398310', 'Sealing washer', '11'], ['47', '1', '311012030', 'Field coil compl.', '28'], ['48', '1', '341511630', 'Depth stop', '16'], ['49', '1', '314000840', 'Support handle cpl.', '20'], ['50', '1', '343409850', 'Electronic switch', '26'], ['51', '1', '343362490', 'Cable clip', '12'], ['52', '1', '343254560', 'Suppressor', '11'], ['53', '1', '344099380', 'Cable sleeve', '11'], ['54', '1', '344489060', 'Cable with plug', '26'], ['55', '1', '343013250', 'Carbon brush set', '23'], ['56', '1', '338059040', 'Rating plate', '11'], ['57', '1', '316057090', 'Maintenance kit', '43'], ['90', '1', '341059290', 'Catching sleeve', '16'], ['880', '1', '344130800', 'Grease FG 126', '26'], ['890', '1', '344130560', 'Grease SF 011 50Gr 1,8oz', '30'], ['1001', '1', '338505760', 'Diagram', '']]

BHE 2444
Position Amount Type number Description PG
43 1 143112230 Ball bearing, 6x19x6 22
44 1 310010660 Armature compl.,230V 31
45 1 143115890 Ball bearing, 8X 22X  7 16
46 1 343398310 Sealing washer 11
47 1 311012030 Field coil compl. 28
48 1 341511630 Depth stop 16
49 1 314000840 Support handle cpl. 20
50 1 343409850 Electronic switch 26
51 1 343362490 Cable clip 12
52 1 343254560 Suppressor 11
53 1 344099380 Cable sleeve 11
54 1 344489060 Cable with plug 26
55 1 343013250 Carbon brush set 23
56 1 338059040 Rating plate 11
57 1 316057090 Maintenance kit 43
90 1 341059290 Catching sleeve 16
880 1 344130800 Grease FG 126 26
890 1 344130560 Grease SF 011 50Gr 1,8oz 30
1001 1 338505760 Diagram  
3/4

Запись таблицы

Запись в CSV-файл

Для записи в файл с расширением csv, ничего устанавливать не надо.  Модуль CSV уже уставлен в стандартную библиотеку Python.

CSV-модуль имеет два класса: reader — для чтения табличных данных формата csv, writer — для записи.

Нам нужен класс writer:

import csv
with open('path/to/file.csv', 'w') as f:
    writer = csv.writer(f,  delimiter=';')
    writer.writerows(table)

Здесь writer — экземпляр класса writer с параметрами: f — дескриптор открытого файла для записи, delimiter — задаёт значение для разделения полей в строках. Запись делает метод .writerows для всех строк table.

Запись в XLSX-файл

Для записи документов с расширением .xlsx в стандартной библиотеке нет модулей. Здесь будут рассмотрены модуль xlsxWriter и библиотека Pandas.

Модуль xlsxWriter

Установка модуля: pip install XlsxWriter.

import xlsxwriter

workbook = xlsxwriter.Workbook('path/to/file.xlsx)
worksheet = workbook.add_worksheet()

for row, el in enumerate(table):
    for column, data in enumerate(el):
        worksheet.write(row, column, data)
        
workbook.close()

Здесь:

  1. Импортируем модуль.
  2. Создаём объект книги конструктором Workbook().
  3. Метод add_worksheet() добавляет новый рабочий лист книги.
  4. Перебор данных и запись методом write() с параметрами:
    • row — номер строки;
    • column— номер колонки;
    • data — данные для записи в ячейку.

5.   Закрываем объект книги.

Библиотека Pandas

Pandas — это мощная библиотека для работы с данными. Инструкции по установке: https://pandas.pydata.org/docs/getting_started/install.html

Для создания и записи в документ  .xlsx или .csv (pandas предоставляет обе возможности), нужно создать объект класса DataFrme, и сохранить документ с нужным расширением.

import pandas as pd

df = pd.DataFrame(table)

df.to_excel('path/to/file.xlsx')
df.to_csv('path/to/file.csv')

Выполняем задание

import csv
import pdfplumber
from pdfrw import PdfReader, PdfWriter

path_pdf = 'path/to/file.pdf’


def wrt_csv(k, table):
    # функция записывает файл .csv
    # k - номер таблицы
    # table - двухмерный список данных
    with open(f'table-{k}.csv', 'w') as f:
        writer = csv.writer(f,  delimiter=';')
        writer.writerows(table)
        
def wrt_page(path_pdf, i, n, file_name):
    # функция записывает одну страницу .pdf
    # path_pdf - путь к файлу
    # i - номер страницы
    # n - номер страницы для названия
    # file_name - название документа
    x = PdfReader(path_pdf)
    y = PdfWriter()
    y.addpage(x.pages[i])
    y.write(f'{file_name}_{str(n)}.pdf')  
    
def wrt_pages(path_pdf, list_page, file_name):
    x = PdfReader(path_pdf)
    y = PdfWriter() 
    for n in list_page:
        y.addpage(x.pages[n])
    y.write(f'{file_name} no tables.pdf') 
    
def rezult(path_pdf):
    file_name = path_pdf.split('\')[-1].split('.')[0]
    with pdfplumber.open(path_pdf) as pdf:
        n = 0
        list_page = []
        k = 0
        for i in range(len(pdf.pages)):
            page = pdf.pages[i]
            table = page.extract_table() # None, list
            
            if not table:
                list_page.append(i)
                n += 1
                # запись одной страницы.pdf без таблицы
                wrt_page(path_pdf, i, n, file_name)
                
                # объединение страниц в одну страницу .pdf
                wrt_pages(path_pdf, list_page, file_name)
            else:
                k += 1
                # функция для записи таблицы .csv
                wrt_csv(k, table)

                
if __name__ == '__main__':
    rezult(path_pdf)

Итоги

Мы рассмотрели основные задачи парсинга PDF:

  • разбиение документа PDF на отдельные страницы и их сохранение;
  • объединение нескольких листов .pdf в один лист;
  • извлечение текста и таблиц из PDF.

Ещё очень часто приходится извлекать из PDF картинки, но это уже тема для следующей статьи.