대규모 언어 모델(Large Language Models, LLMs)과 그 다양한 응용 분야의 시대에서, 간단한 텍스트 요약 및 번역부터 감정 및 금융 보고서 주제에 기반한 주식 성능 예측에 이르기까지, 텍스트 데이터의 중요성은 그 어느 때보다 높아졌습니다.
이러한 비정형 정보를 공유하는 문서 유형에는 웹 기사, 블로그 포스트, 손글씨 편지, 시 등이 있습니다. 그러나 이 텍스트 데이터의 상당 부분은 PDF 형식으로 저장 및 전송됩니다. 구체적으로, 매년 아웃룩에서 20억 개 이상의 PDF가 열리고, 구글 드라이브와 이메일에는 매일 7300만 개의 새로운 PDF 파일이 저장된다고 합니다.
따라서, 이러한 문서를 체계적으로 처리하고 정보를 추출하는 더 체계적인 방법을 개발하는 것은 자동화된 흐름을 가질 수 있게 하고 이 방대한 양의 텍스트 데이터를 더 잘 이해하고 활용할 수 있는 능력을 줄 것입니다. 이 작업에 가장 적합한 도구는 물론 파이썬일 것입니다.
그러나 작업을 시작하기 전에, 현재 유통되고 있는 PDF의 다양한 유형을 명시할 필요가 있습니다. 특히, 가장 자주 등장하는 세 가지 유형은 다음과 같습니다:
- 프로그래밍 방식으로 생성된 PDF: 이러한 PDF는 HTML, CSS, Javascript와 같은 W3C 기술이나 Adobe Acrobat과 같은 다른 소프트웨어를 사용하여 컴퓨터에서 생성됩니다. 이 파일 유형에는 이미지, 텍스트, 링크와 같은 다양한 구성 요소가 포함될 수 있으며, 모두 검색 가능하고 편집하기 쉽습니다.
- 전통적인 스캔 문서: 이 PDF는 스캐너 기계나 모바일 앱을 통해 비전자 매체에서 생성됩니다. 이 파일은 PDF 파일 안에 함께 저장된 이미지 모음에 불과합니다. 이 이미지에 나타나는 텍스트나 링크와 같은 요소들은 선택하거나 검색할 수 없습니다. 본질적으로, PDF는 이러한 이미지를 담는 용기 역할을 합니다.
- OCR이 적용된 스캔 문서: 이 경우, 문서를 스캔한 후 광학 문자 인식(OCR) 소프트웨어를 사용하여 파일의 각 이미지 내의 텍스트를 식별하고 검색 가능하고 편집 가능한 텍스트로 변환합니다. 그런 다음 소프트웨어는 실제 텍스트를 이미지에 레이어로 추가하여 파일을 탐색할 때 별도의 구성 요소로 선택할 수 있습니다.
현재 많은 기계에는 스캔된 문서에서 텍스트를 식별하는 OCR 시스템이 설치되어 있지만, 여전히 전체 페이지가 이미지 형식으로 되어 있는 문서가 있습니다. 훌륭한 기사를 읽다가 문장을 선택하려고 할 때 전체 페이지가 선택되는 것을 보신 적이 있을 것입니다. 이는 특정 OCR 기계의 한계나 완전한 부재 때문일 수 있습니다. 이러한 정보를 간과하지 않기 위해, 이 글에서는 이러한 경우도 고려하고 우리의 소중하고 정보가 풍부한 PDF에서 최대한을 얻어내는 과정을 만들려고 합니다.
이론적 접근 방법
다양한 유형의 PDF 파일과 그 구성 요소를 염두에 두고, 각 구성 요소에 필요한 적절한 도구를 식별하기 위해 PDF의 레이아웃에 대한 초기 분석을 수행하는 것이 중요합니다. 더 구체적으로, 이 분석의 결과를 바탕으로, 메타데이터와 함께 코퍼스 블록에 렌더링된 텍스트, 이미지 내의 텍스트, 테이블 내의 구조화된 텍스트 등 PDF에서 텍스트를 추출하기 위한 적절한 방법을 적용할 것입니다. OCR이 없는 스캔된 문서에서는 이미지에서 텍스트를 식별하고 추출하는 접근 방식이 모든 중요한 작업을 수행합니다. 이 과정의 결과는 PDF 파일의 각 페이지에서 추출된 정보를 담고 있는 Python 사전이 될 것입니다. 이 사전의 각 키는 문서의 페이지 번호를 나타내며, 해당하는 값은 다음과 같은 5가지 중첩된 목록을 포함하는 목록이 될 것입니다:
- 코퍼스의 텍스트 블록별로 추출된 텍스트
- 각 텍스트 블록의 폰트 패밀리와 크기 측면에서의 텍스트 형식
- 페이지의 이미지에서 추출된 텍스트
- 구조화된 형식의 테이블에서 추출된 텍스트
- 페이지의 전체 텍스트 내용
그렇게 함으로써 소스 구성 요소별로 추출된 텍스트를 보다 논리적으로 분리할 수 있으며, 때로는 특정 구성 요소에 일반적으로 나타나는 정보(예: 로고 이미지의 회사 이름)를 더 쉽게 검색하는 데 도움이 될 수 있습니다. 또한, 텍스트에서 추출된 메타데이터(예: 폰트 패밀리 및 크기)는 텍스트 헤더나 중요도가 높은 강조 텍스트를 쉽게 식별하는 데 사용될 수 있어, 텍스트를 여러 다른 덩어리로 더 분리하거나 후처리하는 데 도움이 될 것입니다. 마지막으로, 대규모 언어 모델(LLM)이 이해할 수 있는 방식으로 구조화된 테이블 정보를 유지하는 것은 추출된 데이터 내의 관계에 대한 추론의 질을 현저하게 향상시킬 것입니다. 그런 다음 이러한 결과들을 각 페이지에 나타난 모든 텍스트 정보로 구성하여 출력할 수 있습니다.
아래 이미지에서 이 접근 방식의 플로우차트를 확인할 수 있습니다.
필요한 라이브러리 설치
이 프로젝트를 시작하기 전에, 필요한 라이브러리를 설치해야 합니다. 사용자의 컴퓨터에 Python 3.10 이상이 설치되어 있어야 한다고 가정합니다. 그런 다음 다음 라이브러리를 설치합시다:
- PyPDF2: 저장소 경로에서 PDF 파일을 읽기 위해 사용합니다.
pip install PyPDF2
- Pdfminer: PDF에서 레이아웃 분석을 수행하고 텍스트 및 형식을 추출합니다. (이 라이브러리의 .six 버전은 Python 3을 지원합니다.)
pip install pdfminer.six
- Pdfplumber: PDF 페이지에서 테이블을 식별하고 그 정보를 추출합니다.
pip install pdfplumber
- Pdf2image: PDF 이미지를 PNG 이미지로 변환합니다.
pip install pdf2image
- PIL (Python Imaging Library): PNG 이미지를 읽습니다.
pip install Pillow
위 명령들을 터미널이나 명령 프롬프트에 입력하여 각 라이브러리를 설치할 수 있습니다. 각 라이브러리는 PDF 파일을 다루는데 특화된 다양한 기능을 제공하여, PDF 문서의 텍스트, 이미지, 테이블 등을 추출하는 데 도움을 줍니다.
Pytesseract: OCR 기술을 사용하여 이미지에서 텍스트를 추출하기 위해 사용합니다.
이것은 먼저 Google Tesseract OCR을 설치해야 하기 때문에 설치가 조금 까다롭습니다. 이는 줄 인식 및 문자 패턴을 식별하기 위해 LSTM 모델을 기반으로 하는 OCR 기계입니다.
Mac 사용자인 경우 터미널에서 Brew를 통해 이것을 머신에 설치할 수 있으며, 바로 사용할 수 있습니다.
brew install tesseract
Windows 사용자의 경우 링크를 설치하려면 다음 단계를 따를 수 있습니다. 그런 다음 소프트웨어를 다운로드하고 설치할 때 해당 실행 파일 경로를 컴퓨터의 환경 변수에 추가해야 합니다. 또는 다음 코드를 사용하여 Python 스크립트에 직접 해당 경로를 포함할 수 있습니다:
pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe'
그런 다음 Python 라이브러리를 설치할 수 있습니다.
pip install pytesseract
마지막으로, 스크립트 시작 부분에서 모든 라이브러리를 가져오겠습니다.
# PDF 읽기 위해
import PyPDF2
# PDF 레이아웃 분석 및 텍스트 추출을 위해
from pdfminer.high_level import extract_pages, extract_text
from pdfminer.layout import LTTextContainer, LTChar, LTRect, LTFigure
# PDF의 테이블에서 텍스트 추출을 위해
import pdfplumber
# PDF에서 이미지 추출을 위해
from PIL import Image
from pdf2image import convert_from_path
# 이미지에서 텍스트 추출을 위한 OCR 수행을 위해
import pytesseract
# 추가로 생성된 파일 제거를 위해
import os
이제 모든 준비가 끝났습니다. 재미있는 부분으로 넘어가 보겠습니다.
Python을 사용한 문서 레이아웃 분석
예비 분석을 위해 PDFMiner Python 라이브러리를 사용하여 문서 객체의 텍스트를 여러 페이지 객체로 분리한 다음 각 페이지의 레이아웃을 분석하고 검사했습니다. PDF 파일은 본질적으로 사람의 눈으로 보이는 단락, 문장 또는 단어와 같은 구조화된 정보가 부족합니다. 대신 페이지에서의 위치와 함께 텍스트의 개별 문자만 이해합니다. 이 방식으로 PDFMiner는 파일 내에서 그들의 위치와 함께 페이지의 내용을 개별 문자로 재구성하려고 합니다. 그런 다음 다른 문자와의 거리를 비교하여 적절한 단어, 문장, 줄 및 텍스트 단락을 구성합니다. 이를 달성하기 위해 라이브러리는 다음과 같은 작업을 수행합니다:
- PDF 파일에서 개별 페이지를 분리하고 high-level 함수
extract_pages()
를 사용하여 이를 LTPage 객체로 변환합니다. - 그런 다음 각 LTPage 객체에 대해, 상단에서 하단까지 각 요소를 반복하면서 다음과 같은 적절한 구성 요소를 식별하려고 시도합니다:
LTFigure
는 페이지 내에 다른 PDF 문서로 포함된 그림이나 이미지를 표시할 수 있는 PDF 영역을 나타냅니다.LTTextContainer
는 직사각형 영역에서 텍스트 라인의 그룹을 나타내며, 이는LTTextLine
객체 목록으로 더 분석됩니다. 각각의LTTextLine
객체는 메타데이터와 함께 텍스트의 단일 문자를 저장하는LTChar
객체의 목록을 나타냅니다.LTRect
는 2차원 직사각형을 나타내며, LTPage 객체에서 이미지와 그림을 프레임하거나 테이블을 생성하는 데 사용할 수 있습니다.
따라서 이러한 페이지의 재구성과 페이지의 이미지나 그림을 포함하는 LTFigure, 페이지의 텍스트 정보를 나타내는 LTTextContainer, 또는 표의 존재를 강력하게 나타내는 LTRect로 요소를 분류하는 것에 기반하여 정보를 더 잘 추출하기 위해 적절한 함수를 적용할 수 있습니다.
for pagenum, page in enumerate(extract_pages(pdf_path)):
# 페이지를 구성하는 요소들을 반복
for element in page:
# 요소가 텍스트 요소인지 확인
if isinstance(element, LTTextContainer):
# 텍스트 블록에서 텍스트를 추출하는 함수
pass
# 텍스트 형식을 추출하는 함수
pass
# 이미지에 대한 요소 확인
if isinstance(element, LTFigure):
# PDF를 이미지로 변환하는 함수
pass
# OCR로 텍스트를 추출하는 함수
pass
# 테이블에 대한 요소 확인
if isinstance(element, LTRect):
# 테이블을 추출하는 함수
pass
# 테이블 내용을 문자열로 변환하는 함수
pass
이제 프로세스의 분석 부분을 이해했으니 각 구성 요소에서 텍스트를 추출하는 데 필요한 함수를 만들어 보겠습니다.
PDF에서 텍스트를 추출하는 함수 정의
PDF에서 텍스트 추출을 위한 함수를 정의하면, 텍스트 컨테이너에서 텍스트를 추출하는 것이 매우 간단해집니다.
# 텍스트 추출 함수 생성
def text_extraction(element):
# 인라인 텍스트 요소에서 텍스트 추출
line_text = element.get_text()
# 텍스트의 형식 찾기
# 텍스트 줄에 나타난 모든 형식으로 목록 초기화
line_formats = []
for text_line in element:
if isinstance(text_line, LTTextContainer):
# 텍스트 줄의 각 문자 반복
for character in text_line:
if isinstance(character, LTChar):
# 문자의 글꼴 이름 추가
line_formats.append(character.fontname)
# 문자의 글꼴 크기 추가
line_formats.append(character.size)
# 줄에서 고유한 글꼴 크기와 이름 찾기
format_per_line = list(set(line_formats))
# 각 줄의 텍스트와 형식이 포함된 튜플 반환
return (line_text, format_per_line)
텍스트 컨테이너에서 텍스트를 추출하려면 LTTextContainer 요소의 get_text() 메서드를 간단히 사용하면 됩니다. 이 메서드는 특정 코퍼스 상자 내의 단어를 구성하는 모든 문자를 검색하여 출력을 텍스트 데이터 목록에 저장합니다. 이 목록의 각 요소는 컨테이너에 포함된 원시 텍스트 정보를 나타냅니다.
이제 이 텍스트의 형식을 식별하기 위해 LTTextContainer 객체를 반복하여 이 코퍼스의 각 텍스트 줄에 개별적으로 액세스합니다. 각 반복에서 이 코퍼스 청크에서 텍스트 줄을 나타내는 새로운 LTTextLine 객체가 생성됩니다. 그런 다음 중첩된 줄 요소에 텍스트가 포함되어 있는지 검사합니다. 포함되어 있는 경우 각 개별 문자 요소를 해당 문자의 모든 메타데이터를 포함하는 LTChar로 액세스합니다. 이 메타데이터에서 두 가지 유형의 형식을 추출하고 검사된 텍스트에 해당하는 위치에 별도의 목록에 저장합니다:
- 문자가 굵게 또는 기울임꼴 형식인지 여부를 포함하는 문자의 글꼴 패밀리
- 문자의 글꼴 크기
일반적으로 특정 텍스트 청크 내의 문자는 일부가 굵게 강조 표시되지 않는 한 일관된 서식을 갖는 경향이 있습니다. 추가 분석을 용이하게 하기 위해 텍스트 내의 모든 문자에 대한 텍스트 서식의 고유한 값을 캡처하여 적절한 목록에 저장합니다.
이미지에서 텍스트를 추출하는 함수 정의
PDF 내 이미지에서 텍스트를 추출하는 기능을 정의하겠습니다. PDF에서 이미지를 찾을 때 텍스트를 처리하는 것은 좀 더 까다로운 부분입니다.
PDF에서 발견된 이미지의 텍스트를 어떻게 처리할까요?
먼저, PDF에 저장된 이미지 요소는 JPEG나 PNG와 같은 파일과 다른 형식이 아니라는 점을 확인해야 합니다. 그렇게 하면 OCR 소프트웨어를 적용하기 위해 먼저 파일에서 이미지를 분리한 다음 이미지 형식으로 변환해야 합니다.
# PDF에서 이미지 요소를 자르는 함수 생성
def crop_image(element, pageObj):
# PDF에서 이미지를 자를 좌표 가져오기
[image_left, image_top, image_right, image_bottom] = [element.x0,element.y0,element.x1,element.y1]
# 좌표를 사용하여 페이지 자르기 (왼쪽, 아래쪽, 오른쪽, 위쪽)
pageObj.mediabox.lower_left = (image_left, image_bottom)
pageObj.mediabox.upper_right = (image_right, image_top)
# 자른 페이지를 새 PDF로 저장
cropped_pdf_writer = PyPDF2.PdfWriter()
cropped_pdf_writer.add_page(pageObj)
# 자른 PDF를 새 파일로 저장
with open('cropped_image.pdf', 'wb') as cropped_pdf_file:
cropped_pdf_writer.write(cropped_pdf_file)
# PDF를 이미지로 변환하는 함수 생성
def convert_to_images(input_file,):
images = convert_from_path(input_file)
image = images[0]
output_file = "PDF_image.png"
image.save(output_file, "PNG")
# 이미지에서 텍스트를 읽는 함수 생성
def image_to_text(image_path):
# 이미지 읽기
img = Image.open(image_path)
# 이미지에서 텍스트 추출
text = pytesseract.image_to_string(img)
return text
이 작업을 수행하기 위해 다음 과정을 따릅니다:
- PDFMiner에서 감지된 LTFigure 객체의 메타데이터를 사용하여 페이지 레이아웃의 좌표를 활용하여 이미지 상자를 자릅니다. 그런 다음 PyPDF2 라이브러리를 사용하여 디렉토리에 새 PDF로 저장합니다.
- 그런 다음 pdf2image 라이브러리의 convert_from_file() 함수를 사용하여 디렉토리의 모든 PDF 파일을 이미지 목록으로 변환하고 PNG 형식으로 저장합니다.
- 마지막으로, 이제 이미지 파일이 있으므로 PIL 모듈의 Image 패키지를 사용하여 스크립트에서 이미지를 읽고 pytesseract의 image_to_string() 함수를 구현하여 tesseract OCR 엔진을 사용하여 이미지에서 텍스트를 추출합니다.
그 결과로 이 프로세스는 이미지에서 텍스트를 반환하며, 이를 출력 사전의 세 번째 목록에 저장합니다. 이 목록에는 검사된 페이지의 이미지에서 추출한 텍스트 정보가 포함됩니다.
테이블에서 텍스트를 추출하는 함수 정의
이 섹션에서는 PDF 페이지의 테이블에서 보다 논리적으로 구조화된 텍스트를 추출할 것입니다. 이는 코퍼스에서 텍스트를 추출하는 것보다 약간 더 복잡한 작업입니다. 정보의 세분성과 테이블에 제시된 데이터 포인트 간에 형성된 관계를 고려해야 하기 때문입니다.
PDF에서 테이블 데이터를 추출하는 데 사용되는 여러 라이브러리가 있지만, Tabula-py가 가장 잘 알려진 라이브러리 중 하나입니다. 그러나 우리는 그들의 기능에 특정 제한이 있다는 것을 확인했습니다.
우리가 생각하기에 가장 눈에 띄는 것은 라이브러리가 테이블의 텍스트에서 줄바꿈 특수 문자 \n을 사용하여 테이블의 다른 행을 식별하는 방식에서 비롯됩니다. 이것은 대부분의 경우에 꽤 잘 작동하지만, 셀의 텍스트가 2개 이상의 행으로 래핑될 때 올바르게 캡처하는 데 실패하여 불필요한 빈 행이 추가되고 추출된 셀의 컨텍스트가 손실됩니다.
tabula-py를 사용하여 테이블에서 데이터를 추출하려고 할 때의 예를 아래에서 볼 수 있습니다:
그런 다음 추출된 정보는 문자열 대신 Pandas DataFrame으로 출력됩니다. 대부분의 경우 이것은 바람직한 형식일 수 있지만, 텍스트를 고려하는 변환기의 경우에는 이러한 결과를 모델에 입력하기 전에 변환해야 합니다.
이러한 이유로 이 작업을 처리하기 위해 우리는 pdfplumber 라이브러리를 사용했습니다. 첫째, 이것은 예비 분석에 사용한 pdfminer.six를 기반으로 구축되었기 때문에 유사한 객체를 포함합니다. 또한 이 라이브러리의 테이블 감지 접근 방식은 텍스트를 포함하는 셀과 테이블 자체를 구성하는 교차점과 함께 선 요소를 기반으로 합니다. 그렇게 하면 테이블의 셀을 식별한 후 렌더링해야 하는 행 수에 관계없이 셀 내부의 내용만 추출할 수 있습니다. 그런 다음 테이블의 내용을 가지고 있을 때, 우리는 그것을 테이블과 유사한 문자열로 형식화하고 적절한 목록에 저장할 것입니다.
# 페이지에서 테이블 추출
def extract_table(pdf_path, page_num, table_num):
# pdf 파일 열기
pdf = pdfplumber.open(pdf_path)
# 검사할 페이지 찾기
table_page = pdf.pages[page_num]
# 적절한 테이블 추출
table = table_page.extract_tables()[table_num]
return table
# 테이블을 적절한 형식으로 변환
def table_converter(table):
table_string = ''
# 테이블의 각 행 반복
for row_num in range(len(table)):
row = table[row_num]
# 래핑된 텍스트에서 줄 바꿈 제거
cleaned_row = [item.replace('\n', ' ') if item is not None and '\n' in item else 'None' if item is None else item for item in row]
# 테이블을 문자열로 변환
table_string+=('|'+'|'.join(cleaned_row)+'|'+'\n')
# 마지막 줄 바꿈 제거
table_string = table_string[:-1]
return table_string
이를 달성하기 위해 우리는 테이블의 내용을 목록의 목록으로 추출하는 extract_table() 함수와 이러한 목록의 내용을 테이블과 유사한 문자열로 결합하는 table_converter() 함수, 두 가지 함수를 만들었습니다.
extract_table() 함수에서:
- PDF 파일을 엽니다.
- PDF 파일의 검사할 페이지로 이동합니다.
- pdfplumber에 의해 페이지에서 발견된 테이블 목록에서 원하는 테이블을 선택합니다.
- 테이블의 내용을 추출하고 테이블의 각 행을 나타내는 중첩된 목록의 목록으로 출력합니다.
table_converter() 함수에서:
- 각 중첩된 목록을 반복하고 래핑된 텍스트에서 오는 원치 않는 줄 바꿈으로부터 컨텍스트를 정리합니다.
- 테이블 셀의 구조를 만들기 위해 | 기호를 사용하여 행의 각 요소를 구분합니다.
- 마지막으로 다음 행으로 이동하기 위해 끝에 줄 바꿈을 추가합니다.
이렇게 하면 테이블에 제시된 데이터의 세분성을 잃지 않고 테이블의 내용을 나타내는 텍스트 문자열이 생성됩니다.