Достаём подчёркнутый текст из PDF с помощью Python: инструкция нашего разработчика

Достаём подчёркнутый текст из PDF с помощью Python: инструкция нашего разработчика

Искать данные в PDF-файлах – это настоящая головная боль, и проблема становится ещё труднее когда нужно извлечь подчёркнутый текст. Вы не поверите, но всё ещё нет идеального решения для такой, казалось бы, лёгкой задачи. Не волнуйтесь, ведь мы спросили нашего разработчика – Сашу Коровкину, которая поделилась с нами своим решением.

Начнём с теории.

Существует несколько путей решения. Можно использовать машинное зрение (OCR), чтобы определить элементы текста с нижним подчёркиванием, или обратиться к PyMuPDF. Тем не менее, мы поняли, что OCR склонен к ошибкам и неточностям. PyMyPDF требует очень много времени, чтобы разобраться с настройками. А одна неправильно нажатая клавиша равна потере важной информации.

Два важнейших аспектf PDF – это:

1. Отсутствие структуры. Элементы часто не группируются и не обладают категориями, что усложняет попытки найти информацию в содержании.

2. Распознавание форматирования текста, а точнее его отсутствие. PDF почти не распознаёт слова, выделенные жирным шрифтом, так же как и подчёркивания. Python так же не поддерживает этот формат.

Не волнуйтесь. Перейдём к алгоритму действий!

Стратегия.

1. Необходимо конвертировать PDF-файл в структурированный XML-формат. Так можно намного легче разбираться с данными.

2. Выделите нужные компоненты. Нужно определить и изолировать те, которые представляют для вас ценность.

3. Используйте OCR на выделенных «координатах», чтобы получить выделенный текст в качестве множественного значения.

4. Извлеките и выведите подчёркнутый текст.

Звучит просто, но на деле надо ещё разобраться с кодом.

Код.

1. PDF в XML.

Будем использовать библиотеку pdfquery, которая наиболее легко применяется в переформатировании расширений.

from dfquery import PDFQuery # Путь файла file_path = r"C:\Users\sasha\projects\pdfUnderlinedExtractor\softwareSpec.pdf" pdf = PDFQuery(file_path) pdf.load() pdf.tree.write(r"C:\Users\sasha\projects\pdfUnderlinedExtractor\outXML.xml", pretty_print=True)

2. Изучаем XML.

XML обладает несколькими важными компонентами, которые нам нужны:

LTRect — иногда библиотека анализировала подчеркнутый текст как прямоугольник минимальной ширины под текстом.

LTLine —в других случаях она распознает контур как отдельный компонент линии.

Результат кода выглядит так.
Результат кода выглядит так.

Пример компонента LTRect:

Таким образом, преобразуя весь документ в формат XML, мы можем воспроизвести его структуру в виде компонентов XML. Давайте сделаем именно это.

Репликация структуры.

Теперь мы воссоздадим структуру нашего документа в виде координат ограничивающего прямоугольника. Для этого мы проанализируем XML, чтобы определить страницу, поля компонентов, линии и прямоугольники. Код:

for page in root.findall('.//LTPage'): page_num = int(page.get('page_index')) pdf_page = pdf_reader.pages[page_num] page_height = float(pdf_page.mediabox[3]) packet = io.BytesIO() can = canvas.Canvas(packet, pagesize=(pdf_page.mediabox[2], page_height)) can.setPageSize((pdf_page.mediabox[2], page_height)) for elem in page.findall('.//*[@bbox]'): bbox = eval(elem.get('bbox')) x0, y0, x1, y1 = map(float, bbox) can.rect(x0, y0, x1 - x0, y1 - y0, stroke=1, fill=0) text = elem.text.strip() if elem.text else "" text_x = x0 text_y = y0 # can.drawString(text_x, text_y, text) if text: all_text.append([text_x, text_y, x1, y1, text]) for elem in page.findall('.//LTRect[@bbox]'): bbox = eval(elem.get('bbox')) x0, y0, x1, y1 = map(float, bbox) can.setStrokeColor(blue) can.setLineWidth(1) can.line(x0, y0, x1, y1) underline_text.append([x0, y0, x1, y1]) for elem in page.findall('.//LTLine[@bbox]'): # Adjust this XPath if necessary bbox = eval(elem.get('bbox')) # Safely parse 'bbox' attribute to get coordinates x0, y0, x1, y1 = map(float, bbox) # Convert all coordinates to floats can.setStrokeColor(pink) can.setLineWidth(1) # Set line width, adjust as needed can.line(x0, y0, x1, y1) # Draw a line from start to end point can.save() packet.seek(0) new_pdf = PdfReader(packet) new_page = new_pdf.pages[0] pdf_writer.add_page(new_page) with open(output_pdf_path, 'wb') as output_pdf: pdf_writer.write(output_pdf)

Вот наш исходный PDF-файл, он был создан в Microsoft Word путем экспорта документа с некоторыми подчеркиваниями в формат файла PDF:

Начальный документ.
Начальный документ.

После применения описанного выше алгоритма мы получаем вот такое визуальное представление:

Видим все компоненты документа, выделенные рамками, синим цветом выделены подчёркивания.
Видим все компоненты документа, выделенные рамками, синим цветом выделены подчёркивания.

Это изображение представляет структуру нашего документа, где черный прямоугольник используется для описания всех компонентов на странице, а синий — для описания элементов LTRect, отсюда и подчеркнутый текст.

Наложение текста.

Теперь давайте визуализируем весь текст в PDF-файле в соответствующих позициях с помощью следующей строки кода:

can.drawString(text_x, text_y, text)

Вот результат:

Текст вставляется в ячейки.
Текст вставляется в ячейки.

Обратите внимание, что текст не совсем там, где он был в исходном документе, из-за разницы в размере и шрифте языка разметки в библиотеке pdfquery.

Саша

Координированное извлечение

В результате нашего XML у нас будет массив координат подчеркнутых регионов, в моем случае мы назвали его underline_text.

def get_coordinates(pdf_path): pdf = PDFQuery(pdf_path) pdf.load() pdf.tree.write(xml_path, pretty_print=True) tree = ET.parse(xml_path) root = tree.getroot() pdf_reader = PdfReader(pdf_path) for page in root.findall('.//LTPage'): page_num = int(page.get('page_index')) pdf_page = pdf_reader.pages[page_num] page_height = float(pdf_page.mediabox[3]) packet = io.BytesIO() can = canvas.Canvas(packet, pagesize=(pdf_page.mediabox[2], page_height)) can.setPageSize((pdf_page.mediabox[2], page_height)) for elem in page.findall('.//*[@bbox]'): bbox = eval(elem.get('bbox')) x0, y0, x1, y1 = map(float, bbox) # can.rect(x0, y0, x1 - x0, y1 - y0, stroke=1, fill=0) text = elem.text.strip() if elem.text else "" text_x = x0 text_y = y0 can.drawString(text_x, text_y, text) for elem in page.findall('.//LTRect[@bbox]'): bbox = eval(elem.get('bbox')) x0, y0, x1, y1 = map(float, bbox) underline_text.append([x0, y0, x1, y1]) return underline_text

Это часть кода, которая формирует массив координат подчеркнутого текста в файле PDF.

Извлечение текста.

Процесс следующий:

1. Определим координатные прямоугольники, как было определено ранее.

2. Извлекаем эти разделы из PDF.

3. Мы применяем Tesseract OCR для извлечения текста из каждого извлеченного раздела.

Этот метод извлечения текста из PDF-файлов с использованием координатных прямоугольников и Tesseract OCR эффективен по нескольким причинам:

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

2. Эффективность. Извлечение предопределенных разделов непосредственно из PDF-файла происходит намного быстрее, чем обработка всего документа. Этот метод экономит вычислительные ресурсы и время, что особенно полезно при работе с большими документами.

3. Точность с помощью OCR. Tesseract OCR — это надежный инструмент оптического распознавания символов, который может преобразовывать изображения текста в машиночитаемый текст. Подавая ему точные фрагменты текста, он может работать более точно, поскольку устраняет меньше фонового шума и проблем с форматированием, которые могут затруднить процесс распознавания в больших несегментированных документах.

Вот код:

def extract_region_from_pdf(pdf_path, page_number, record): # Open the PDF file doc = fitz.open(pdf_path) page = doc.load_page(page_number) # page numbering starts from 0 page_rect = page.rect y1_coordinate = page_rect.y1 y0 = y1_coordinate - record[3] - 10 y1 = y1_coordinate - record[3] x0 = record[0] x1 = record[2] coordinates = [x0, y0, x1, y1] # Create a rectangle for the specific area to be extracted clip_rect = fitz.Rect(coordinates) pix = page.get_pixmap(clip=clip_rect) # Convert the pixmap to an in-memory image img_bytes = io.BytesIO(pix.tobytes("png")) # Save image to a bytes buffer img = Image.open(img_bytes) # Use pytesseract to perform OCR on the image text = pytesseract.image_to_string(img) doc.close() return text

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

Или в моём репозитории GitHub: https://github.com/sasha-korovkina/pdfUnderlinedExtractor

Собрав все это вместе…

Теперь, если мы возьмем любой PDF-файл, например этот файл:

Полный текст файла.
Полный текст файла.

Имеем несколько подчёркнутых слов в файле:

Достаём подчёркнутый текст из PDF с помощью Python: инструкция нашего разработчика

После запуска описанного выше кода мы получаем это:

Массив всех подчёркнутых слов в документе.
Массив всех подчёркнутых слов в документе.

Получив этот массив, вы можете использовать эти слова для дальнейшей обработки!

Надеемся, что наша инструкция вам пригодится! обязательно поделитесь своим мнением в комментариях.

Вступайте в наш телеграм-канал, где мы создаём российскую нейросеть, которая может обрабатывать все виды файлов:

Саша Коровкина
Основатель и разработчик Elmento AI
11
Начать дискуссию