Извлечение только определенного текста из PDF с помощью Python

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

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

Образец PDF-файла:

Нажмите, чтобы просмотреть образец файла

Необходимо извлечь идентификатор счета, дату выдачи, тему, сумму к оплате из всего файла PDF.

Сценарий, который я использовал до сих пор:

import PyPDF2
import re
pdfFileObj = open('test.pdf','rb') 
pdfReader = PyPDF2.PdfFileReader(pdfFileObj)
pageObj = pdfReader.getPage(0)         
text = str(pageObj.extractText())

quotes = re.findall(r'"[^"]*"',text)
print(quotes)

person Manz    schedule 04.10.2020    source источник
comment
Вы пытаетесь зафиксировать значения идентификатора счета-фактуры, даты выдачи, темы, суммы к оплате или только этот текст   -  person Seyi Daniel    schedule 04.10.2020
comment
@SeyiDaniel - Да, именно так, я пытаюсь извлечь значения для этих разделов из всего PDF-файла.   -  person Manz    schedule 05.10.2020


Ответы (1)


У вас есть очень хороший документ в формате PDF, потому что в нем есть поля формы, поэтому вы можете использовать их напрямую для чтения данных:

import PyPDF2


pdfFileObj = open('test.pdf', 'rb')
pdfReader = PyPDF2.PdfFileReader(pdfFileObj)

fields = pdfReader.getFormTextFields()

print(fields["Invoice ID"])
print(fields["Issue Date"])
print(fields["Subject"])
print(fields["Amount Due"])

EDIT: я объединил запрошенные вами данные (отсюда: Как извлечь только определенный текст из файла PDF с помощью python) в небольшом скрипте с 3 возможностями анализа pdf (для ваших 3 pdf). Проблема в том, что ваши PDF-файлы имеют много различий, а пакеты имеют некоторые преимущества в разных PDF-файлах, поэтому я думаю, что вам нужно объединить эти вещи. Дело в том, что вы пробуете все функции, пока не получите результат. Я надеюсь, что это хорошее начало для вас. Возможно, вам придется изменить регулярные выражения, если у вас больше разных PDF-файлов, и, возможно, вам придется хранить все регулярные выражения (для каждого поля) в массиве и использовать их в разных функциях, чтобы у вас было 3 функции для синтаксического анализа и 4 списка регулярных выражений для использования в 2-х функциях.

import PyPDF2
import re
import os

from io import StringIO

from pdfminer.converter import TextConverter
from pdfminer.layout import LAParams
from pdfminer.pdfdocument import PDFDocument
from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter
from pdfminer.pdfpage import PDFPage
from pdfminer.pdfparser import PDFParser


def parse_pdf_by_regex_2(filename: str) -> dict:
    output_string = StringIO()
    with open(filename, 'rb') as in_file:
        parser = PDFParser(in_file)
        doc = PDFDocument(parser)
        rsrcmgr = PDFResourceManager()
        device = TextConverter(rsrcmgr, output_string, laparams=LAParams())
        interpreter = PDFPageInterpreter(rsrcmgr, device)
        for page in PDFPage.create_pages(doc):
            interpreter.process_page(page)

    regex_invoice_no = re.compile(r"Invoice No.:\s*(\w+)\s")
    regex_order_no = re.compile(r"IRN:\s*(\d+)")
    regex_due_date = re.compile(r"Due Date: (\d{2}\.\d{2}\.\d{4})")
    regex_total_due = re.compile(r"([\d,.]+) \n\nTotal Invoice Value\(in words\)")

    try:
        return {"invoice_id": re.search(regex_invoice_no, output_string.getvalue()).group(1),
                "issue_date": re.search(regex_due_date, output_string.getvalue()).group(1),
                "subject": re.search(regex_order_no, output_string.getvalue()).group(1),
                "amount": re.search(regex_total_due, output_string.getvalue()).group(1)}

    except AttributeError as err:
        print("Not all elements have been found")
        return {}


def parse_pdf_by_form_fields(filename: str) -> dict:
    with open(filename, 'rb') as file:
        pdf_reader = PyPDF2.PdfFileReader(file)
        try:
            fields = pdf_reader.getFormTextFields()
        except TypeError as err:
            # print("No FormFields available")
            return {}

    try:
        # You can also check if onyly missing some values, maybe this can happen, but this is up to your data
        return {"invoice_id": fields["Invoice ID"],
                "issue_date": fields["Issue Date"],
                "subject": fields["Subject"],
                "amount": fields["Amount Due"]}
    except KeyError as err:
        # print(f"Key not found: '{err.args[0]}'")
        return {}


def parse_pdf_by_regex(filename: str) -> dict:
    with open(filename, 'rb') as file:
        pdf_reader = PyPDF2.PdfFileReader(file)
        text_data = ""
        for page_no in range(pdf_reader.getNumPages()):
            text_data += pdf_reader.getPage(page_no).extractText()

    regex_invoice_no = re.compile(r"Invoice Number\s*(INV-\d+)")
    regex_order_no = re.compile(r"Order Number(\d+)")
    regex_due_date = re.compile(r"Due Date(\S+ \d{1,2}, \d{4})")
    regex_total_due = re.compile(r"Total Due(\$\d+\.\d{1,2})")

    try:
        return {"invoice_id": re.search(regex_invoice_no, text_data).group(1),
                "issue_date": re.search(regex_due_date, text_data).group(1),
                "subject": re.search(regex_order_no, text_data).group(1),
                "amount": re.search(regex_total_due, text_data).group(1)}

    except AttributeError as err:
        # print("Not all elements have been found")
        return {}


def parse_pdf(filename: str) -> dict:
    # Hint: ':=' is available since pythoon 3.8
    if data := parse_pdf_by_form_fields(filename=fname):
        return data
    elif data := parse_pdf_by_regex(filename=fname):
        return data
    elif data := parse_pdf_by_regex_2(filename=fname):
        return data
    else:
        print("No data found")
        return {}


if __name__ == '__main__':
    for fname in os.listdir("."):
        if fname.startswith("testfile"):
            print(f"check {fname}")
            print(parse_pdf(filename=fname))
person D-E-N    schedule 04.10.2020
comment
Он работает с этим PDF-файлом, но когда мы пытаемся захватить те же поля из другого PDF-файла (имеющего данные в другом формате), возникает ошибка: TypeError: объект «NoneType» не повторяется. Как мы можем преодолеть эту ошибку - person Manz; 05.10.2020
comment
- Как нам справиться со сценарием, когда у нас есть данные в разных файлах формата PDF, поскольку я пытался использовать функцию регулярного выражения для поиска значений, но из-за отсутствия пробелов между конкретными текстами не мог захватить данные. - person Manz; 05.10.2020
comment
Мы не сможем помочь, если вы зададите вопрос с pdf-файлом и у вас возникнут проблемы с другим pdf-файлом, который вы здесь не предоставляете. Вы также должны поделиться этим материалом (вы имели в виду свой перекрестный пост stackoverflow.com/questions/64142307/) - person D-E-N; 05.10.2020
comment
Да Предоставлена ​​ссылка на образец файла PDF на - stackoverflow.com/questions/64142307/ , вы можете ознакомиться с ним. - person Manz; 05.10.2020
comment
Спасибо за ценный ответ, но, поскольку вы новичок в этом языке, у вас есть вопрос, как и где мы должны добавить имя файла PDF в обновленный сценарий. - person Manz; 06.10.2020
comment
Строка, начинающаяся с if __name__, является отправной точкой скрипта. Он перебирает файлы в фактическом каталоге . и проверяет, начинается ли имя файла с testfile, потому что я сохранил ваши 3 файла с такими именами. Вызов функции с именем файла в качестве параметра является последней строкой, поэтому вы можете вызвать функцию с parse_pdf(<your filename here>). Эта функция использует другие функции для анализа файла. - person D-E-N; 06.10.2020
comment
Спасибо за отзыв, но при использовании вышеуказанной функции, если name == 'main': возникает эта ошибка FileNotFoundError: [Errno 2] Нет такого файла или каталога: 'testfile .pdf». Использование версии Python 3.8. но при использовании прямого имени файла это работает. - person Manz; 06.10.2020
comment
материал main предназначен для чтения некоторых файлов, лежащих рядом со скриптом, но если вы сделаете это, явно назвав их, все в порядке - person D-E-N; 06.10.2020