PDF에서 표 추출하기
요약
PDF에서 표를 추출하는 것은 단순히 텍스트를 읽는 것보다 복잡하며, PDF가 '행'이나 '열' 개념을 직접 가지고 있지 않기 때문입니다. 본문에서는 가장 많이 사용되는 세 가지 Python 라이브러리(pdfplumber, camelot-py, pymupdf)를 비교 분석하고, 각 라이브러리의 작동 방식과 한계점을 제시합니다.
핵심 포인트
- PDF 표 추출은 위치 기반의 추론 과정이 필요함.
- 세 라이브러리는 각각 다른 방식으로 테이블을 인식함.
- 테두리 없는 복잡한 표는 OCR 또는 레이아웃 인지 파서가 필요할 수 있음.
- 라이브러리별로 가상 환경 설정 및 테스트가 권장됨.
PDF에서 표를 추출한다는 것은, 그 내용을 텍스트의 나열이 아닌 행과 열로 된 데이터(행 목록 또는 pandas DataFrame) 형태로 되돌리는 것을 의미합니다. 이는 보이는 것보다 어렵습니다. 왜냐하면 PDF는 페이지 상의 위치에 문자를 저장할 뿐, '행'이나 '열'이라는 개념을 가지고 있지 않기 때문입니다. 따라서 라이브러리는 셀 주위에 그려진 선이나 단어들이 배열된 방식으로부터 표를 추론해야 합니다.
일반적으로 이러한 작업은 그림(figure)을 스프레드시트나 데이터베이스에 로드하거나, 분석에 사용할 표를 제공하거나, 언어 모델에게 읽을 수 있는 표 형태로 전달하기 위해 필요합니다. 이 페이지에서는 가장 많이 사용되는 세 가지 Python 라이브러리를 동일한 다섯 개의 가상 PDF 파일에 적용해보고, 작동했던 코드를 보여주고, 각 라이브러리가 어느 부분에서 실패했는지 보고합니다.
준비물
- Python 3.10 이상 버전.
- 표가 포함된 PDF 파일 하나. 가능하다면 가상의 또는 샘플 문서여야 합니다. 실제 고객 문서를 사용하면 그 내용이 터미널이나 작업 로그에 남을 수 있기 때문입니다. 가장 어려운 예시를 사용하세요: 테두리 선 없음, 병합된 헤더 셀 또는 페이지 전체에 걸쳐 있는 표가 좋습니다. 깔끔하게 그려진 표는 모든 라이브러리를 좋게 보이게 만듭니다.
- 라이브러리별로 가상 환경을 하나씩 설정하여 의존성 충돌을 방지합니다:
pip install pdfplumber # environment 1
pip install camelot-py # environment 2
pip install pymupdf # environment 3
1단계. PDF에 텍스트 레이어가 있는지 확인하기
세 라이브러리 모두 PDF에 저장된 텍스트를 읽습니다. 스캔본은 텍스트 레이어가 없는 이미지이며, 이 경우 세 라이브러리 모두 오류를 발생시키지 않고 빈 목록을 반환했습니다. 모든 페이지를 먼저 확인해야 합니다. 왜냐하면 텍스트가 포함된 PDF라도 짧은 표지나 공백 페이지가 있을 수 있기 때문입니다:
import pdfplumber
with pdfplumber.open(
거기 나열된 페이지는 표지나 공백 페이지도 텍스트가 거의 있기 때문에 판단하기보다는 한번 살펴볼 가치가 있습니다. 만약 테이블이 있는 페이지에 텍스트 레이어가 없다면, 아래 코드 중 어느 것도 작동하지 않으며 OCR(광학 문자 인식) 또는 레이아웃 인지 파서(layout-aware parser)를 먼저 사용해야 합니다. OCR 옵션은 [최고의 Python OCR 라이브러리](https://anyformat.ai/blog/best-python-ocr-library)를 참고하세요.
## 2단계. 각 라이브러리로 테이블 추출하기
세 가지 중 가장 가벼운 [pdfplumber](https://github.com/jsvine/pdfplumber)는 기본적으로 선이 있는(ruled) 테이블을 읽습니다:
import pdfplumber
with pdfplumber.open(
Camelot은 룰링된 테이블에는 lattice를, 테두리가 없는(borderless) 테이블에는 stream을 사용하여 pandas DataFrame을 반환합니다:
import camelot
tables = camelot.read_pdf("table.pdf", pages="all", flavor="lattice")
[PyMuPDF](https://pymupdf.readthedocs.io/en/latest/page.html#Page.find_tables)는 `find_tables`로 테이블을 찾고, 그 결과도 DataFrame으로 변환됩니다:
import pymupdf
doc = pymupdf.open("table.pdf")
우리가 본 것
우리는 알려진 테이블이 포함된 가상의 PDF 5개를 생성했습니다. 선이 있는(ruling lines) 경우, 아무 선도 없는 경우, 병합된 헤더 셀과 다중 라인 셀이 있는 경우, 반복되는 헤더를 가진 페이지에 걸쳐 실행되는 경우, 그리고 스캔본인 경우입니다. 우리는 우리가 생성한 테이블과 비교하여 모든 출력 셀을 셀별로 비교했습니다. 사용된 버전은 pdfplumber 0.11.10, Camelot 2.0.0, PyMuPDF 1.28.2였으며, 각 라이브러리는 기본 모드와 텍스트 기반 모드에서 실행되었습니다.
| pdfplumber | Camelot | PyMuPDF | |
|---|---|---|---|
| 선이 있는 테이블 | 정확함 | 정확함 | 정확함 |
| ... | |||
| 이것은 작고 깨끗하게 생성된 파일 5개이므로, 각 라이브러리가 실패하는 방식만 보여줄 뿐 사용자의 문서에서 얼마나 자주 실패하는지에 대해서는 아무것도 말해주지 않습니다. |
기본 모드에서는 선이 모든 것을 결정합니다
기본 모드에서는 선이 모든 것을 결정합니다
각 라이브러리의 기본 모드는 이름만 다를 뿐 같은 아이디어를 사용합니다. 즉, 셀 주변에 그려진 선을 찾아 그 안의 텍스트를 읽어내는 방식입니다. 선으로 된 표(ruled table)에서는 세 가지 모두 정확했습니다. 하지만 테두리 선이 없는 표(table with no ruling lines)에서는 세 라이브러리 모두 아무것도 반환하지 않았고, 이 경우에는 단어 위치에서 셀을 추측하는 텍스트 기반 전략(text-based strategy)으로 전환해야 합니다.
텍스트 기반 모드는 작동하지만 정제가 필요합니다
vertical_strategy와 horizontal_strategy를 `
The video는 공공 ParseBench dataset의 실제 금융 문서에 세 가지 라이브러리를 적용하여 실행합니다. 이 문서는 45개 회사, 여러 줄 이름(multi-line names), 병합된 셀(merged cells)을 가진 두 개의 표로 구성되어 있습니다. 모든 행은 정답(ground truth)과 비교되었습니다. 비디오에서 보여준 결과는 다음과 같습니다:
| Approach | Rows correct | What went wrong |
|---|---|---|
| pdfplumber | 51.1% | 병합된 셀의 이름이 누락됨 |
| ... | ||
| It은 하나의 문서와 한 번의 실행으로 이루어졌으므로, 벤치마크가 아닌 패턴으로 이해해야 합니다. 이 패턴은 위의 합성 테스트(synthetic test)와 일치합니다: 규칙선(ruling lines)이 기본 모드(default modes)를 결정하고, 텍스트 기반 모드(text-based modes)는 더 많은 행을 유지하지만 손상시킵니다. |
세 라이브러리 중 어느 것도 어떤 행이 잘못되었는지 알려주지 않습니다. 그들은 어쨌든 표를 반환하기 때문에, 손상된 셀은 정확한 셀과 똑같이 보입니다. 이것이 아래의 확인(check) 과정이 작성할 가치가 있는 이유입니다. anyformat은 각 필드별 신뢰 점수(confidence score)와 해당 값이 읽힌 원본 텍스트를 가진 동일한 표를 반환하며, 이것이 사람이 의심스러운 행만 확인할 수 있게 해줍니다.
자체 PDF 확인하기
자신의 표에 있다고 아는 한 행을 작성하여 테스트해 보세요. 이 함수는 실패한 체크만 보고하고 셀 내용은 절대 보고하지 않으므로, 출력 결과를 로그로 남기기에 안전합니다.
def check_table(table: list[list[str]], expected_row: list[str]) -> list[str]:
problems = []
if not table:
...
이 코드를 동일한 파일로 각 라이브러리에 대해 실행하면, 우리보다 더 가치 있는 문서 비교 자료를 얻게 됩니다.
테스트 재현하기
다섯 개의 PDF는 짧은 스크립트에서 나온 것이므로, 모든 것을 다시 실행할 수 있습니다. pip install reportlab이 필요합니다:
import json
from reportlab.lib.pagesizes import A4
from reportlab.platypus import Table, TableStyle, SimpleDocTemplate, Paragraph, Spacer
...
스캔의 경우, 규칙선이 있는 PDF를 이미지로 렌더링한 다음, 텍스트 레이어 없이 다시 PDF로 저장합니다:
import pymupdf
src = pymupdf.open(
Camelot은 세 라이브러리 중 가장 무겁게 설치되며, OpenCV를 포함하면 약 157MB이고, pdfplumber는 약 42MB, PyMuPDF는 저희 환경에서 53MB입니다. Camelot 2.0.0 버전부터는 기본적으로 Ghostscript가 필요하지 않으며 pypdfium2를 통해 페이지를 변환합니다. 대부분의 튜토리얼이 여전히 다루고 있는 구형 0.x 버전은 `gs` 바이너리가 경로에 있어도 실행 시마다 "Ghostscript is not installed"라는 오류로 실패했으므로, 최신 버전을 설치하는 것이 좋습니다.
pdfplumber는 MIT 라이선스를 따르고 Camelot 역시 MIT 라이선스입니다. PyMuPDF는 AGPL 3.0 또는 Artifex의 상업적 라이선스로 이중 라이선스가 적용되는데, 이는 폐쇄형 소스 소프트웨어를 배포할 경우 중요합니다. 사용 사례에 대해서는 [라이선싱 페이지](https://pymupdf.readthedocs.io/en/latest/about.html)를 참고하십시오. 세 라이브러리 모두 2026년 10월 1일에 출시되었습니다.
## 이 라이브러리들이 충분하지 않을 때: anyformat
이 라이브러리들은 텍스트 레이어와 선으로 구분되거나 깔끔하게 정렬된 표를 가진 PDF에 적합합니다. 하지만 표에 선이 없거나, 셀 병합(merged cells), 여러 줄 셀(multi-line cells), 페이지 나누기(page breaks)가 있거나 파일 자체가 스캔본인 경우에는 작동을 멈춥니다. 이러한 경우 사람들은 레이아웃 인식 파서(layout-aware parser), 비전 언어 모델(vision language model), 또는 표 구조를 찾는 OCR 단계로 전환합니다.
anyformat의 파싱은 하나의 옵션입니다. 그 결과는 문서의 블록들을 나열하며, 표 블록은 SDK 레퍼런스에 따라 해당 행들(rows)을 포함합니다. 저희는 이 기능을 5개의 합성 파일에 대해 실행하지 않았습니다. 비디오에서는 위에서 언급된 실제 재무 PDF에 적용하여 45개 중 43개 회사를 정확하게 찾아냈는데, 이는 하나의 문서와 한 번의 실행 결과입니다. 특히 긴 표(long tables)의 경우 [장문서 추출](https://anyformat.ai/blog/anyformat-long-documents-extraction)을 참고하십시오.
## 테스트 영상 시청
이 테스트 영상 버전은 약 5분 분량이며, 세 라이브러리와 위 실제 PDF 결과를 다루고 있습니다.
[https://www.youtube.com/watch?v=yf3IP0Bx1oI](https://www.youtube.com/watch?v=yf3IP0Bx1oI)
## 자주 묻는 질문
## 자주 묻는 질문
**PDF에서 표를 추출하는 데 가장 좋은 Python 라이브러리는 무엇인가요?**
표의 종류에 따라 다릅니다. 텍스트 기반 PDF의 구분선이 있는(ruled) 표의 경우, 세 가지 모두 테스트에서 작동했으며 pdfplumber가 설치하기 가장 가벼웠습니다. 구분선이 없는 표의 경우, 기본 설정으로는 어느 것도 작동하지 않았고 모든 라이브러리가 텍스트 기반 모드와 추가적인 정리 작업이 필요했습니다.
**pdfplumber는 스캔된 PDF에서 표를 추출할 수 있나요?**
아니요. 텍스트 레이어를 읽기 때문에, 스캔본은 텍스트 레이어가 없으므로 빈 리스트를 반환합니다. 먼저 OCR(광학 문자 인식) 또는 레이아웃 인지 파서가 필요합니다.
**PDF 표를 pandas DataFrame으로 가져오려면 어떻게 해야 하나요?**
Camelot은 직접 반환하고(`tables[0].df`), PyMuPDF는 `to_pandas()`로 변환하며, pdfplumber의 경우 행 리스트를 받아 첫 번째 행을 헤더로 사용하여 `pandas.DataFrame`에 전달합니다.
**제 표가 빈 행과 함께 나오는 이유는 무엇인가요?**
pdfplumber와 PyMuPDF의 텍스트 기반 전략은 단어 위치에서 셀 경계를 찾아내기 때문에 실제 데이터 사이에 빈 행을 추가할 수 있습니다. 모든 셀이 비어 있는 행은 제거하세요.
**여러 페이지에 걸쳐 있는 표는 어떻게 추출하나요?**
각 라이브러리는 페이지당 하나의 표를 반환합니다. 이 표들을 연결(concatenate)하고 중복된 헤더 행은 직접 제거해야 합니다.
_원래 [anyformat.ai/blog/extract-tables-from-pdf](https://anyformat.ai/blog/extract-tables-from-pdf)에 게시되었습니다._
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기